@mswjs/interceptors
Advanced tools
| const require_createRequestId = require('./createRequestId-DOf8Ktjs.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-DdfaiPVH.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-BS75Oulv.cjs'); | ||
| const require_handleRequest = require('./handleRequest-tUNU616J.cjs'); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let _open_draft_until = require("@open-draft/until"); | ||
| //#region src/interceptors/fetch/utils/createNetworkError.ts | ||
| function createNetworkError(cause) { | ||
| return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/followRedirect.ts | ||
| const REQUEST_BODY_HEADERS = [ | ||
| "content-encoding", | ||
| "content-language", | ||
| "content-location", | ||
| "content-type", | ||
| "content-length" | ||
| ]; | ||
| const kRedirectCount = Symbol("kRedirectCount"); | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210 | ||
| */ | ||
| 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"); | ||
| } | ||
| /** | ||
| * @note Undici "safely" extracts the request body. | ||
| * I suspect we cannot dispatch this request again | ||
| * since its body has been read and the stream is locked. | ||
| */ | ||
| requestInit.headers = request.headers; | ||
| const finalResponse = await fetch(new Request(locationUrl, requestInit)); | ||
| Object.defineProperty(finalResponse, "redirected", { | ||
| value: true, | ||
| configurable: true | ||
| }); | ||
| return finalResponse; | ||
| } | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761 | ||
| */ | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/brotli-decompress.browser.ts | ||
| var BrotliDecompressionStream = class extends TransformStream { | ||
| constructor() { | ||
| console.warn("[Interceptors]: Brotli decompression of response streams is not supported in the browser"); | ||
| super({ transform(chunk, controller) { | ||
| controller.enqueue(chunk); | ||
| } }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/decompression.ts | ||
| var PipelineStream = class extends TransformStream { | ||
| constructor(transformStreams, ...strategies) { | ||
| super({}, ...strategies); | ||
| const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform)); | ||
| Object.defineProperty(this, "readable", { get() { | ||
| return readable; | ||
| } }); | ||
| } | ||
| }; | ||
| function parseContentEncoding(contentEncoding) { | ||
| return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim()); | ||
| } | ||
| function createDecompressionStream(contentEncoding) { | ||
| if (contentEncoding === "") return null; | ||
| const codings = parseContentEncoding(contentEncoding); | ||
| if (codings.length === 0) return null; | ||
| return new PipelineStream(codings.reduceRight((transformers, coding) => { | ||
| if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip")); | ||
| else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate")); | ||
| else if (coding === "br") return transformers.concat(new BrotliDecompressionStream()); | ||
| else transformers.length = 0; | ||
| return transformers; | ||
| }, [])); | ||
| } | ||
| function decompressResponse(response) { | ||
| if (response.body === null) return null; | ||
| const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || ""); | ||
| if (!decompressionStream) return null; | ||
| response.body.pipeTo(decompressionStream.writable); | ||
| return decompressionStream.readable; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/index.ts | ||
| var FetchInterceptor = class FetchInterceptor extends require_createRequestId.Interceptor { | ||
| static { | ||
| this.symbol = Symbol("fetch"); | ||
| } | ||
| constructor() { | ||
| super(FetchInterceptor.symbol); | ||
| } | ||
| checkEnvironment() { | ||
| return require_hasConfigurableGlobal.hasConfigurableGlobal("fetch"); | ||
| } | ||
| async setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| const pureFetch = globalThis.fetch; | ||
| const fetchProxy = async (input, init) => { | ||
| const requestId = require_createRequestId.createRequestId(); | ||
| const request = new require_getRawRequest.FetchRequest(typeof input === "string" && typeof location !== "undefined" && !require_getRawRequest.canParseUrl(input) ? new URL(input, location.href) : input, init); | ||
| /** | ||
| * @note Set the raw request only if a Request instance was provided to fetch. | ||
| */ | ||
| if (input instanceof Request) require_getRawRequest.setRawRequest(request, input); | ||
| const responsePromise = new _open_draft_deferred_promise.DeferredPromise(); | ||
| const controller = new require_getRawRequest.RequestController(request, { | ||
| passthrough: async () => { | ||
| this.logger.info("request has not been handled, passthrough..."); | ||
| /** | ||
| * @note Clone the request instance right before performing it. | ||
| * This preserves any modifications made to the intercepted request | ||
| * in the "request" listener. This also allows the user to read the | ||
| * request body in the "response" listener (otherwise "unusable"). | ||
| */ | ||
| const requestCloneForResponseEvent = request.clone(); | ||
| const { error: responseError, data: originalResponse } = await (0, _open_draft_until.until)(() => pureFetch(request)); | ||
| if (responseError) return responsePromise.reject(responseError); | ||
| this.logger.info("original fetch performed", originalResponse); | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| const responseClone = require_getRawRequest.FetchResponse.clone(originalResponse); | ||
| await require_hasConfigurableGlobal.emitAsync(this.emitter, "response", { | ||
| response: responseClone, | ||
| isMockedResponse: false, | ||
| request: requestCloneForResponseEvent, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(originalResponse); | ||
| }, | ||
| respondWith: async (rawResponse) => { | ||
| if (require_handleRequest.isResponseError(rawResponse)) { | ||
| this.logger.info("request has errored!", { response: rawResponse }); | ||
| responsePromise.reject(createNetworkError(rawResponse)); | ||
| return; | ||
| } | ||
| this.logger.info("received mocked response!", { rawResponse }); | ||
| const response = new require_getRawRequest.FetchResponse(decompressResponse(rawResponse) || rawResponse.body, { | ||
| url: request.url, | ||
| status: rawResponse.status, | ||
| statusText: rawResponse.statusText, | ||
| headers: rawResponse.headers | ||
| }); | ||
| /** | ||
| * Undici's handling of following redirect responses. | ||
| * Treat the "manual" redirect mode as a regular mocked response. | ||
| * This way, the client can manually follow the redirect it receives. | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173 | ||
| */ | ||
| if (require_getRawRequest.FetchResponse.isRedirectResponse(response.status)) { | ||
| if (request.redirect === "error") { | ||
| responsePromise.reject(createNetworkError("unexpected redirect")); | ||
| return; | ||
| } | ||
| if (request.redirect === "follow") { | ||
| followFetchRedirect(request, response).then((response$1) => { | ||
| responsePromise.resolve(response$1); | ||
| }, (reason) => { | ||
| responsePromise.reject(reason); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| await require_hasConfigurableGlobal.emitAsync(this.emitter, "response", { | ||
| response: require_getRawRequest.FetchResponse.clone(response), | ||
| isMockedResponse: true, | ||
| request, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request has been aborted!", { reason }); | ||
| responsePromise.reject(reason); | ||
| } | ||
| }); | ||
| 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")); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId, | ||
| emitter: this.emitter, | ||
| controller | ||
| }); | ||
| return responsePromise; | ||
| }; | ||
| logger.info("patching global fetch..."); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.patchesRegistry.applyPatch(globalThis, "fetch", () => fetchProxy)); | ||
| logger.info("global fetch patched!", globalThis.fetch.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'FetchInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=fetch-CtkfZi4Z.cjs.map |
| {"version":3,"file":"fetch-CtkfZi4Z.cjs","names":["locationUrl: URL","requestInit: RequestInit","readable","Interceptor","hasConfigurableGlobal","fetchProxy: typeof fetch","createRequestId","FetchRequest","canParseUrl","DeferredPromise","RequestController","FetchResponse","emitAsync","isResponseError","response","handleRequest","patchesRegistry"],"sources":["../../src/interceptors/fetch/utils/createNetworkError.ts","../../src/interceptors/fetch/utils/followRedirect.ts","../../src/interceptors/fetch/utils/brotli-decompress.browser.ts","../../src/interceptors/fetch/utils/decompression.ts","../../src/interceptors/fetch/index.ts"],"sourcesContent":["export function createNetworkError(cause?: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","import { createNetworkError } from './createNetworkError'\n\nconst REQUEST_BODY_HEADERS = [\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-type',\n 'content-length',\n]\n\nconst kRedirectCount = Symbol('kRedirectCount')\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210\n */\nexport async function followFetchRedirect(\n request: Request,\n response: Response\n): Promise<Response> {\n if (response.status !== 303 && request.body != null) {\n return Promise.reject(createNetworkError())\n }\n\n const requestUrl = new URL(request.url)\n\n let locationUrl: URL\n try {\n // If the location is a relative URL, use the request URL as the base URL.\n locationUrl = new URL(response.headers.get('location')!, request.url) \n } catch (error) {\n return Promise.reject(createNetworkError(error))\n }\n\n if (\n !(locationUrl.protocol === 'http:' || locationUrl.protocol === 'https:')\n ) {\n return Promise.reject(\n createNetworkError('URL scheme must be a HTTP(S) scheme')\n )\n }\n\n if (Reflect.get(request, kRedirectCount) > 20) {\n return Promise.reject(createNetworkError('redirect count exceeded'))\n }\n\n Object.defineProperty(request, kRedirectCount, {\n value: (Reflect.get(request, kRedirectCount) || 0) + 1,\n })\n\n if (\n request.mode === 'cors' &&\n (locationUrl.username || locationUrl.password) &&\n !sameOrigin(requestUrl, locationUrl)\n ) {\n return Promise.reject(\n createNetworkError('cross origin not allowed for request mode \"cors\"')\n )\n }\n\n const requestInit: RequestInit = {}\n\n if (\n ([301, 302].includes(response.status) && request.method === 'POST') ||\n (response.status === 303 && !['HEAD', 'GET'].includes(request.method))\n ) {\n requestInit.method = 'GET'\n requestInit.body = null\n\n REQUEST_BODY_HEADERS.forEach((headerName) => {\n request.headers.delete(headerName)\n })\n }\n\n if (!sameOrigin(requestUrl, locationUrl)) {\n request.headers.delete('authorization')\n request.headers.delete('proxy-authorization')\n request.headers.delete('cookie')\n request.headers.delete('host')\n }\n\n /**\n * @note Undici \"safely\" extracts the request body.\n * I suspect we cannot dispatch this request again\n * since its body has been read and the stream is locked.\n */\n\n requestInit.headers = request.headers\n const finalResponse = await fetch(new Request(locationUrl, requestInit))\n Object.defineProperty(finalResponse, 'redirected', {\n value: true,\n configurable: true,\n })\n\n return finalResponse\n}\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761\n */\nfunction sameOrigin(left: URL, right: URL): boolean {\n if (left.origin === right.origin && left.origin === 'null') {\n return true\n }\n\n if (\n left.protocol === right.protocol &&\n left.hostname === right.hostname &&\n left.port === right.port\n ) {\n return true\n }\n\n return false\n}\n","export class BrotliDecompressionStream extends TransformStream {\n constructor() {\n console.warn(\n '[Interceptors]: Brotli decompression of response streams is not supported in the browser'\n )\n\n super({\n transform(chunk, controller) {\n // Keep the stream as passthrough, it does nothing.\n controller.enqueue(chunk)\n },\n })\n }\n}\n","// Import from an internal alias that resolves to different modules\n// depending on the environment. This way, we can keep the fetch interceptor\n// intact while using different strategies for Brotli decompression.\nimport { BrotliDecompressionStream } from 'internal:brotli-decompress'\n\nclass PipelineStream extends TransformStream {\n constructor(\n transformStreams: Array<TransformStream>,\n ...strategies: Array<QueuingStrategy>\n ) {\n super({}, ...strategies)\n\n const readable = [super.readable as any, ...transformStreams].reduce(\n (readable, transform) => readable.pipeThrough(transform)\n )\n\n Object.defineProperty(this, 'readable', {\n get() {\n return readable\n },\n })\n }\n}\n\nexport function parseContentEncoding(contentEncoding: string): Array<string> {\n return contentEncoding\n .toLowerCase()\n .split(',')\n .map((coding) => coding.trim())\n}\n\nfunction createDecompressionStream(\n contentEncoding: string\n): TransformStream | null {\n if (contentEncoding === '') {\n return null\n }\n\n const codings = parseContentEncoding(contentEncoding)\n\n if (codings.length === 0) {\n return null\n }\n\n const transformers = codings.reduceRight<Array<TransformStream>>(\n (transformers, coding) => {\n if (coding === 'gzip' || coding === 'x-gzip') {\n return transformers.concat(new DecompressionStream('gzip'))\n } else if (coding === 'deflate') {\n return transformers.concat(new DecompressionStream('deflate'))\n } else if (coding === 'br') {\n return transformers.concat(new BrotliDecompressionStream())\n } else {\n transformers.length = 0\n }\n\n return transformers\n },\n []\n )\n\n return new PipelineStream(transformers)\n}\n\nexport function decompressResponse(\n response: Response\n): ReadableStream<any> | null {\n if (response.body === null) {\n return null\n }\n\n const decompressionStream = createDecompressionStream(\n response.headers.get('content-encoding') || ''\n )\n\n if (!decompressionStream) {\n return null\n }\n\n // Use `pipeTo` and return the decompression stream's readable\n // instead of `pipeThrough` because that will lock the original\n // response stream, making it unusable as the input to Response.\n response.body.pipeTo(decompressionStream.writable)\n return decompressionStream.readable\n}\n","import { until } from '@open-draft/until'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport { createNetworkError } from './utils/createNetworkError'\nimport { followFetchRedirect } from './utils/followRedirect'\nimport { decompressResponse } from './utils/decompression'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { FetchRequest, FetchResponse } from '../../utils/fetchUtils'\nimport { setRawRequest } from '../../getRawRequest'\nimport { isResponseError } from '../../utils/responseUtils'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('fetch')\n }\n\n protected async setup() {\n const logger = this.logger.extend('setup')\n\n const pureFetch = globalThis.fetch\n\n const fetchProxy: typeof fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.href)\n : input\n\n const request = new FetchRequest(resolvedInput, init)\n\n /**\n * @note Set the raw request only if a Request instance was provided to fetch.\n */\n if (input instanceof Request) {\n setRawRequest(request, input)\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const controller = new RequestController(request, {\n passthrough: async () => {\n this.logger.info('request has not been handled, passthrough...')\n\n /**\n * @note Clone the request instance right before performing it.\n * This preserves any modifications made to the intercepted request\n * in the \"request\" listener. This also allows the user to read the\n * request body in the \"response\" listener (otherwise \"unusable\").\n */\n const requestCloneForResponseEvent = request.clone()\n\n // Perform the intercepted request as-is.\n const { error: responseError, data: originalResponse } = await until(\n () => pureFetch(request)\n )\n\n if (responseError) {\n return responsePromise.reject(responseError)\n }\n\n this.logger.info('original fetch performed', originalResponse)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = FetchResponse.clone(originalResponse)\n await emitAsync(this.emitter, 'response', {\n response: responseClone,\n isMockedResponse: false,\n request: requestCloneForResponseEvent,\n requestId,\n })\n }\n\n // Resolve the response promise with the original response\n // since the `fetch()` return this internal promise.\n responsePromise.resolve(originalResponse)\n },\n respondWith: async (rawResponse) => {\n // Handle mocked `Response.error()` (i.e. request errors).\n if (isResponseError(rawResponse)) {\n this.logger.info('request has errored!', { response: rawResponse })\n responsePromise.reject(createNetworkError(rawResponse))\n return\n }\n\n this.logger.info('received mocked response!', {\n rawResponse,\n })\n\n // Decompress the mocked response body, if applicable.\n const decompressedStream = decompressResponse(rawResponse)\n const response = new FetchResponse(\n decompressedStream || rawResponse.body,\n {\n url: request.url,\n status: rawResponse.status,\n statusText: rawResponse.statusText,\n headers: rawResponse.headers,\n }\n )\n\n /**\n * Undici's handling of following redirect responses.\n * Treat the \"manual\" redirect mode as a regular mocked response.\n * This way, the client can manually follow the redirect it receives.\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173\n */\n if (FetchResponse.isRedirectResponse(response.status)) {\n // Reject the request promise if its `redirect` is set to `error`\n // and it receives a mocked redirect response.\n if (request.redirect === 'error') {\n responsePromise.reject(createNetworkError('unexpected redirect'))\n return\n }\n\n if (request.redirect === 'follow') {\n followFetchRedirect(request, response).then(\n (response) => {\n responsePromise.resolve(response)\n },\n (reason) => {\n responsePromise.reject(reason)\n }\n )\n return\n }\n }\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: FetchResponse.clone(response),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n responsePromise.resolve(response)\n },\n errorWith: (reason) => {\n this.logger.info('request has been aborted!', { reason })\n responsePromise.reject(reason)\n },\n })\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n })\n\n return responsePromise\n }\n\n logger.info('patching global fetch...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'fetch', () => fetchProxy)\n )\n\n logger.info('global fetch patched!', globalThis.fetch.name)\n }\n}\n"],"mappings":"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"} |
| import { r as Interceptor, t as createRequestId } from "./createRequestId-DYCsFHOi.mjs"; | ||
| import { a as canParseUrl, i as FetchResponse, n as setRawRequest, o as RequestController, r as FetchRequest } from "./getRawRequest-B1BqgWG6.mjs"; | ||
| import { n as patchesRegistry, r as emitAsync, t as hasConfigurableGlobal } from "./hasConfigurableGlobal-FTYwno1G.mjs"; | ||
| import { n as isResponseError, t as handleRequest } from "./handleRequest-BHrC8Flw.mjs"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { until } from "@open-draft/until"; | ||
| //#region src/interceptors/fetch/utils/createNetworkError.ts | ||
| function createNetworkError(cause) { | ||
| return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/followRedirect.ts | ||
| const REQUEST_BODY_HEADERS = [ | ||
| "content-encoding", | ||
| "content-language", | ||
| "content-location", | ||
| "content-type", | ||
| "content-length" | ||
| ]; | ||
| const kRedirectCount = Symbol("kRedirectCount"); | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210 | ||
| */ | ||
| 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"); | ||
| } | ||
| /** | ||
| * @note Undici "safely" extracts the request body. | ||
| * I suspect we cannot dispatch this request again | ||
| * since its body has been read and the stream is locked. | ||
| */ | ||
| requestInit.headers = request.headers; | ||
| const finalResponse = await fetch(new Request(locationUrl, requestInit)); | ||
| Object.defineProperty(finalResponse, "redirected", { | ||
| value: true, | ||
| configurable: true | ||
| }); | ||
| return finalResponse; | ||
| } | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761 | ||
| */ | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/brotli-decompress.browser.ts | ||
| var BrotliDecompressionStream = class extends TransformStream { | ||
| constructor() { | ||
| console.warn("[Interceptors]: Brotli decompression of response streams is not supported in the browser"); | ||
| super({ transform(chunk, controller) { | ||
| controller.enqueue(chunk); | ||
| } }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/decompression.ts | ||
| var PipelineStream = class extends TransformStream { | ||
| constructor(transformStreams, ...strategies) { | ||
| super({}, ...strategies); | ||
| const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform)); | ||
| Object.defineProperty(this, "readable", { get() { | ||
| return readable; | ||
| } }); | ||
| } | ||
| }; | ||
| function parseContentEncoding(contentEncoding) { | ||
| return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim()); | ||
| } | ||
| function createDecompressionStream(contentEncoding) { | ||
| if (contentEncoding === "") return null; | ||
| const codings = parseContentEncoding(contentEncoding); | ||
| if (codings.length === 0) return null; | ||
| return new PipelineStream(codings.reduceRight((transformers, coding) => { | ||
| if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip")); | ||
| else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate")); | ||
| else if (coding === "br") return transformers.concat(new BrotliDecompressionStream()); | ||
| else transformers.length = 0; | ||
| return transformers; | ||
| }, [])); | ||
| } | ||
| function decompressResponse(response) { | ||
| if (response.body === null) return null; | ||
| const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || ""); | ||
| if (!decompressionStream) return null; | ||
| response.body.pipeTo(decompressionStream.writable); | ||
| return decompressionStream.readable; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/index.ts | ||
| var FetchInterceptor = class FetchInterceptor extends Interceptor { | ||
| static { | ||
| this.symbol = Symbol("fetch"); | ||
| } | ||
| constructor() { | ||
| super(FetchInterceptor.symbol); | ||
| } | ||
| checkEnvironment() { | ||
| return hasConfigurableGlobal("fetch"); | ||
| } | ||
| async setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| const pureFetch = globalThis.fetch; | ||
| const fetchProxy = async (input, init) => { | ||
| const requestId = createRequestId(); | ||
| const request = new FetchRequest(typeof input === "string" && typeof location !== "undefined" && !canParseUrl(input) ? new URL(input, location.href) : input, init); | ||
| /** | ||
| * @note Set the raw request only if a Request instance was provided to fetch. | ||
| */ | ||
| if (input instanceof Request) setRawRequest(request, input); | ||
| const responsePromise = new DeferredPromise(); | ||
| const controller = new RequestController(request, { | ||
| passthrough: async () => { | ||
| this.logger.info("request has not been handled, passthrough..."); | ||
| /** | ||
| * @note Clone the request instance right before performing it. | ||
| * This preserves any modifications made to the intercepted request | ||
| * in the "request" listener. This also allows the user to read the | ||
| * request body in the "response" listener (otherwise "unusable"). | ||
| */ | ||
| const requestCloneForResponseEvent = request.clone(); | ||
| const { error: responseError, data: originalResponse } = await until(() => pureFetch(request)); | ||
| if (responseError) return responsePromise.reject(responseError); | ||
| this.logger.info("original fetch performed", originalResponse); | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| const responseClone = FetchResponse.clone(originalResponse); | ||
| await emitAsync(this.emitter, "response", { | ||
| response: responseClone, | ||
| isMockedResponse: false, | ||
| request: requestCloneForResponseEvent, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(originalResponse); | ||
| }, | ||
| respondWith: async (rawResponse) => { | ||
| if (isResponseError(rawResponse)) { | ||
| this.logger.info("request has errored!", { response: rawResponse }); | ||
| responsePromise.reject(createNetworkError(rawResponse)); | ||
| return; | ||
| } | ||
| this.logger.info("received mocked response!", { rawResponse }); | ||
| const response = new FetchResponse(decompressResponse(rawResponse) || rawResponse.body, { | ||
| url: request.url, | ||
| status: rawResponse.status, | ||
| statusText: rawResponse.statusText, | ||
| headers: rawResponse.headers | ||
| }); | ||
| /** | ||
| * Undici's handling of following redirect responses. | ||
| * Treat the "manual" redirect mode as a regular mocked response. | ||
| * This way, the client can manually follow the redirect it receives. | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173 | ||
| */ | ||
| if (FetchResponse.isRedirectResponse(response.status)) { | ||
| if (request.redirect === "error") { | ||
| responsePromise.reject(createNetworkError("unexpected redirect")); | ||
| return; | ||
| } | ||
| if (request.redirect === "follow") { | ||
| followFetchRedirect(request, response).then((response$1) => { | ||
| responsePromise.resolve(response$1); | ||
| }, (reason) => { | ||
| responsePromise.reject(reason); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| await emitAsync(this.emitter, "response", { | ||
| response: FetchResponse.clone(response), | ||
| isMockedResponse: true, | ||
| request, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request has been aborted!", { reason }); | ||
| responsePromise.reject(reason); | ||
| } | ||
| }); | ||
| 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")); | ||
| await handleRequest({ | ||
| request, | ||
| requestId, | ||
| emitter: this.emitter, | ||
| controller | ||
| }); | ||
| return responsePromise; | ||
| }; | ||
| logger.info("patching global fetch..."); | ||
| this.subscriptions.push(patchesRegistry.applyPatch(globalThis, "fetch", () => fetchProxy)); | ||
| logger.info("global fetch patched!", globalThis.fetch.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { FetchInterceptor as t }; | ||
| //# sourceMappingURL=fetch-Ddj5TV04.mjs.map |
| {"version":3,"file":"fetch-Ddj5TV04.mjs","names":["locationUrl: URL","requestInit: RequestInit","readable","fetchProxy: typeof fetch","response"],"sources":["../../src/interceptors/fetch/utils/createNetworkError.ts","../../src/interceptors/fetch/utils/followRedirect.ts","../../src/interceptors/fetch/utils/brotli-decompress.browser.ts","../../src/interceptors/fetch/utils/decompression.ts","../../src/interceptors/fetch/index.ts"],"sourcesContent":["export function createNetworkError(cause?: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","import { createNetworkError } from './createNetworkError'\n\nconst REQUEST_BODY_HEADERS = [\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-type',\n 'content-length',\n]\n\nconst kRedirectCount = Symbol('kRedirectCount')\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210\n */\nexport async function followFetchRedirect(\n request: Request,\n response: Response\n): Promise<Response> {\n if (response.status !== 303 && request.body != null) {\n return Promise.reject(createNetworkError())\n }\n\n const requestUrl = new URL(request.url)\n\n let locationUrl: URL\n try {\n // If the location is a relative URL, use the request URL as the base URL.\n locationUrl = new URL(response.headers.get('location')!, request.url) \n } catch (error) {\n return Promise.reject(createNetworkError(error))\n }\n\n if (\n !(locationUrl.protocol === 'http:' || locationUrl.protocol === 'https:')\n ) {\n return Promise.reject(\n createNetworkError('URL scheme must be a HTTP(S) scheme')\n )\n }\n\n if (Reflect.get(request, kRedirectCount) > 20) {\n return Promise.reject(createNetworkError('redirect count exceeded'))\n }\n\n Object.defineProperty(request, kRedirectCount, {\n value: (Reflect.get(request, kRedirectCount) || 0) + 1,\n })\n\n if (\n request.mode === 'cors' &&\n (locationUrl.username || locationUrl.password) &&\n !sameOrigin(requestUrl, locationUrl)\n ) {\n return Promise.reject(\n createNetworkError('cross origin not allowed for request mode \"cors\"')\n )\n }\n\n const requestInit: RequestInit = {}\n\n if (\n ([301, 302].includes(response.status) && request.method === 'POST') ||\n (response.status === 303 && !['HEAD', 'GET'].includes(request.method))\n ) {\n requestInit.method = 'GET'\n requestInit.body = null\n\n REQUEST_BODY_HEADERS.forEach((headerName) => {\n request.headers.delete(headerName)\n })\n }\n\n if (!sameOrigin(requestUrl, locationUrl)) {\n request.headers.delete('authorization')\n request.headers.delete('proxy-authorization')\n request.headers.delete('cookie')\n request.headers.delete('host')\n }\n\n /**\n * @note Undici \"safely\" extracts the request body.\n * I suspect we cannot dispatch this request again\n * since its body has been read and the stream is locked.\n */\n\n requestInit.headers = request.headers\n const finalResponse = await fetch(new Request(locationUrl, requestInit))\n Object.defineProperty(finalResponse, 'redirected', {\n value: true,\n configurable: true,\n })\n\n return finalResponse\n}\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761\n */\nfunction sameOrigin(left: URL, right: URL): boolean {\n if (left.origin === right.origin && left.origin === 'null') {\n return true\n }\n\n if (\n left.protocol === right.protocol &&\n left.hostname === right.hostname &&\n left.port === right.port\n ) {\n return true\n }\n\n return false\n}\n","export class BrotliDecompressionStream extends TransformStream {\n constructor() {\n console.warn(\n '[Interceptors]: Brotli decompression of response streams is not supported in the browser'\n )\n\n super({\n transform(chunk, controller) {\n // Keep the stream as passthrough, it does nothing.\n controller.enqueue(chunk)\n },\n })\n }\n}\n","// Import from an internal alias that resolves to different modules\n// depending on the environment. This way, we can keep the fetch interceptor\n// intact while using different strategies for Brotli decompression.\nimport { BrotliDecompressionStream } from 'internal:brotli-decompress'\n\nclass PipelineStream extends TransformStream {\n constructor(\n transformStreams: Array<TransformStream>,\n ...strategies: Array<QueuingStrategy>\n ) {\n super({}, ...strategies)\n\n const readable = [super.readable as any, ...transformStreams].reduce(\n (readable, transform) => readable.pipeThrough(transform)\n )\n\n Object.defineProperty(this, 'readable', {\n get() {\n return readable\n },\n })\n }\n}\n\nexport function parseContentEncoding(contentEncoding: string): Array<string> {\n return contentEncoding\n .toLowerCase()\n .split(',')\n .map((coding) => coding.trim())\n}\n\nfunction createDecompressionStream(\n contentEncoding: string\n): TransformStream | null {\n if (contentEncoding === '') {\n return null\n }\n\n const codings = parseContentEncoding(contentEncoding)\n\n if (codings.length === 0) {\n return null\n }\n\n const transformers = codings.reduceRight<Array<TransformStream>>(\n (transformers, coding) => {\n if (coding === 'gzip' || coding === 'x-gzip') {\n return transformers.concat(new DecompressionStream('gzip'))\n } else if (coding === 'deflate') {\n return transformers.concat(new DecompressionStream('deflate'))\n } else if (coding === 'br') {\n return transformers.concat(new BrotliDecompressionStream())\n } else {\n transformers.length = 0\n }\n\n return transformers\n },\n []\n )\n\n return new PipelineStream(transformers)\n}\n\nexport function decompressResponse(\n response: Response\n): ReadableStream<any> | null {\n if (response.body === null) {\n return null\n }\n\n const decompressionStream = createDecompressionStream(\n response.headers.get('content-encoding') || ''\n )\n\n if (!decompressionStream) {\n return null\n }\n\n // Use `pipeTo` and return the decompression stream's readable\n // instead of `pipeThrough` because that will lock the original\n // response stream, making it unusable as the input to Response.\n response.body.pipeTo(decompressionStream.writable)\n return decompressionStream.readable\n}\n","import { until } from '@open-draft/until'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport { createNetworkError } from './utils/createNetworkError'\nimport { followFetchRedirect } from './utils/followRedirect'\nimport { decompressResponse } from './utils/decompression'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { FetchRequest, FetchResponse } from '../../utils/fetchUtils'\nimport { setRawRequest } from '../../getRawRequest'\nimport { isResponseError } from '../../utils/responseUtils'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('fetch')\n }\n\n protected async setup() {\n const logger = this.logger.extend('setup')\n\n const pureFetch = globalThis.fetch\n\n const fetchProxy: typeof fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.href)\n : input\n\n const request = new FetchRequest(resolvedInput, init)\n\n /**\n * @note Set the raw request only if a Request instance was provided to fetch.\n */\n if (input instanceof Request) {\n setRawRequest(request, input)\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const controller = new RequestController(request, {\n passthrough: async () => {\n this.logger.info('request has not been handled, passthrough...')\n\n /**\n * @note Clone the request instance right before performing it.\n * This preserves any modifications made to the intercepted request\n * in the \"request\" listener. This also allows the user to read the\n * request body in the \"response\" listener (otherwise \"unusable\").\n */\n const requestCloneForResponseEvent = request.clone()\n\n // Perform the intercepted request as-is.\n const { error: responseError, data: originalResponse } = await until(\n () => pureFetch(request)\n )\n\n if (responseError) {\n return responsePromise.reject(responseError)\n }\n\n this.logger.info('original fetch performed', originalResponse)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = FetchResponse.clone(originalResponse)\n await emitAsync(this.emitter, 'response', {\n response: responseClone,\n isMockedResponse: false,\n request: requestCloneForResponseEvent,\n requestId,\n })\n }\n\n // Resolve the response promise with the original response\n // since the `fetch()` return this internal promise.\n responsePromise.resolve(originalResponse)\n },\n respondWith: async (rawResponse) => {\n // Handle mocked `Response.error()` (i.e. request errors).\n if (isResponseError(rawResponse)) {\n this.logger.info('request has errored!', { response: rawResponse })\n responsePromise.reject(createNetworkError(rawResponse))\n return\n }\n\n this.logger.info('received mocked response!', {\n rawResponse,\n })\n\n // Decompress the mocked response body, if applicable.\n const decompressedStream = decompressResponse(rawResponse)\n const response = new FetchResponse(\n decompressedStream || rawResponse.body,\n {\n url: request.url,\n status: rawResponse.status,\n statusText: rawResponse.statusText,\n headers: rawResponse.headers,\n }\n )\n\n /**\n * Undici's handling of following redirect responses.\n * Treat the \"manual\" redirect mode as a regular mocked response.\n * This way, the client can manually follow the redirect it receives.\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173\n */\n if (FetchResponse.isRedirectResponse(response.status)) {\n // Reject the request promise if its `redirect` is set to `error`\n // and it receives a mocked redirect response.\n if (request.redirect === 'error') {\n responsePromise.reject(createNetworkError('unexpected redirect'))\n return\n }\n\n if (request.redirect === 'follow') {\n followFetchRedirect(request, response).then(\n (response) => {\n responsePromise.resolve(response)\n },\n (reason) => {\n responsePromise.reject(reason)\n }\n )\n return\n }\n }\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: FetchResponse.clone(response),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n responsePromise.resolve(response)\n },\n errorWith: (reason) => {\n this.logger.info('request has been aborted!', { reason })\n responsePromise.reject(reason)\n },\n })\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n })\n\n return responsePromise\n }\n\n logger.info('patching global fetch...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'fetch', () => fetchProxy)\n )\n\n logger.info('global fetch patched!', globalThis.fetch.name)\n }\n}\n"],"mappings":"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"} |
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { invariant } from "outvariant"; | ||
| //#region src/InterceptorError.ts | ||
| var InterceptorError = class InterceptorError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "InterceptorError"; | ||
| Object.setPrototypeOf(this, InterceptorError.prototype); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/RequestController.ts | ||
| var RequestController = class RequestController { | ||
| static { | ||
| this.PENDING = 0; | ||
| } | ||
| static { | ||
| this.PASSTHROUGH = 1; | ||
| } | ||
| static { | ||
| this.RESPONSE = 2; | ||
| } | ||
| static { | ||
| this.ERROR = 3; | ||
| } | ||
| constructor(request, source) { | ||
| this.request = request; | ||
| this.source = source; | ||
| this.readyState = RequestController.PENDING; | ||
| this.handled = new DeferredPromise(); | ||
| } | ||
| get #handled() { | ||
| return this.handled; | ||
| } | ||
| /** | ||
| * Perform this request as-is. | ||
| */ | ||
| async passthrough() { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url); | ||
| this.readyState = RequestController.PASSTHROUGH; | ||
| await this.source.passthrough(); | ||
| this.#handled.resolve(); | ||
| } | ||
| /** | ||
| * Respond to this request with the given `Response` instance. | ||
| * | ||
| * @example | ||
| * controller.respondWith(new Response()) | ||
| * controller.respondWith(Response.json({ id })) | ||
| * controller.respondWith(Response.error()) | ||
| */ | ||
| respondWith(response) { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState); | ||
| this.readyState = RequestController.RESPONSE; | ||
| this.#handled.resolve(); | ||
| /** | ||
| * @note Although `source.respondWith()` is potentially asynchronous, | ||
| * do NOT await it for backward-compatibility. Awaiting it will short-circuit | ||
| * the request listener invocation as soon as a listener responds to a request. | ||
| * Ideally, that's what we want, but that's not what we promise the user. | ||
| */ | ||
| this.source.respondWith(response); | ||
| } | ||
| /** | ||
| * Error this request with the given reason. | ||
| * | ||
| * @example | ||
| * controller.errorWith() | ||
| * controller.errorWith(new Error('Oops!')) | ||
| * controller.errorWith({ message: 'Oops!'}) | ||
| */ | ||
| errorWith(reason) { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState); | ||
| this.readyState = RequestController.ERROR; | ||
| this.source.errorWith(reason); | ||
| this.#handled.resolve(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/utils/canParseUrl.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given URL string | ||
| * can be parsed into a `URL` instance. | ||
| * A substitute for `URL.canParse()` for Node.js 18. | ||
| */ | ||
| function canParseUrl(url) { | ||
| try { | ||
| new URL(url); | ||
| return true; | ||
| } catch (_error) { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/getValueBySymbol.ts | ||
| /** | ||
| * Returns the value behind the symbol with the given name. | ||
| */ | ||
| function getValueBySymbol(symbolName, source) { | ||
| const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => { | ||
| return symbol$1.description === symbolName; | ||
| }); | ||
| if (symbol) return Reflect.get(source, symbol); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/fetchUtils.ts | ||
| var FetchRequest = class FetchRequest extends Request { | ||
| static #resolveProperty(input, init = {}, key) { | ||
| return init[key] ?? (input instanceof Request ? input[key] : void 0); | ||
| } | ||
| /** | ||
| * Check if the given request method is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#methods | ||
| */ | ||
| static isConfigurableMethod(method) { | ||
| return method !== "CONNECT" && method !== "TRACE" && method !== "TRACK"; | ||
| } | ||
| static isMethodWithBody(method) { | ||
| return method !== "HEAD" && method !== "GET" && FetchRequest.isConfigurableMethod(method); | ||
| } | ||
| /** | ||
| * Check if the given request `mode` is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#concept-request-mode | ||
| */ | ||
| static isConfigurableMode(mode) { | ||
| return mode !== "navigate" && mode !== "websocket" && mode !== "webtransport"; | ||
| } | ||
| constructor(input, init) { | ||
| const method = FetchRequest.#resolveProperty(input, init, "method") || "GET"; | ||
| const safeMethod = FetchRequest.isConfigurableMethod(method) ? method : "GET"; | ||
| const hasExplicitBody = init != null && "body" in init; | ||
| /** | ||
| * Only include `body` in the super init when it needs to be overridden. | ||
| * When `input` is a Request and no explicit body is in `init`, let the | ||
| * Request constructor handle body transfer naturally so it properly | ||
| * marks the original request's body as consumed (bodyUsed = true). | ||
| */ | ||
| const bodyInit = !FetchRequest.isMethodWithBody(method) ? { body: void 0 } : hasExplicitBody ? { body: init.body } : {}; | ||
| const mode = FetchRequest.#resolveProperty(input, init, "mode") ?? void 0; | ||
| const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : void 0; | ||
| super(input, { | ||
| ...init || {}, | ||
| method: safeMethod, | ||
| mode: safeMode, | ||
| duplex: init?.duplex ?? (FetchRequest.isMethodWithBody(method) ? "half" : void 0), | ||
| ...bodyInit | ||
| }); | ||
| if (method !== safeMethod) this.#setInternalProperty("method", method); | ||
| if (method === "CONNECT") { | ||
| const url = new URL(input instanceof Request ? input.url : input); | ||
| let authority; | ||
| /** | ||
| * @note Node.js has a bug parsing raw CONNECT requests URLs like | ||
| * "http://127.0.0.1:1337/localhost:80". It would treat "localhost:" as a protocol. | ||
| */ | ||
| if (url.protocol === "localhost:") authority = url.href; | ||
| else authority = url.pathname.replace(/^\/+/, ""); | ||
| /** | ||
| * @note Define "url" as a getter because Undici uses their own | ||
| * logic to resolve the "request.url" property. Simply reassigning | ||
| * its value doesn't do anything. This is a destructive action | ||
| * but it's safe because "CONNECT" requests are forbidden per fetch. | ||
| */ | ||
| Object.defineProperty(this, "url", { | ||
| get: () => authority, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| if (mode != null && mode !== safeMode) this.#setInternalProperty("mode", mode); | ||
| } | ||
| #setInternalProperty(key, value) { | ||
| const internalState = getValueBySymbol("state", this); | ||
| if (internalState) Reflect.set(internalState, key, value); | ||
| else Object.defineProperty(this, key, { | ||
| value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| }; | ||
| const kStatus = Symbol("kStatus"); | ||
| const kUrl = Symbol("kUrl"); | ||
| var FetchResponse = class FetchResponse extends Response { | ||
| static { | ||
| this.STATUS_CODES_WITHOUT_BODY = [ | ||
| 101, | ||
| 103, | ||
| 204, | ||
| 205, | ||
| 304 | ||
| ]; | ||
| } | ||
| static { | ||
| this.STATUS_CODES_WITH_REDIRECT = [ | ||
| 301, | ||
| 302, | ||
| 303, | ||
| 307, | ||
| 308 | ||
| ]; | ||
| } | ||
| static isConfigurableStatusCode(status) { | ||
| return status >= 200 && status <= 599; | ||
| } | ||
| static isRedirectResponse(status) { | ||
| return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status); | ||
| } | ||
| /** | ||
| * Returns a boolean indicating whether the given response status | ||
| * code represents a response that can have a body. | ||
| */ | ||
| static isResponseWithBody(status) { | ||
| return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status); | ||
| } | ||
| static setStatus(status, response) { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const internalState = getValueBySymbol("state", response); | ||
| if (internalState) internalState.status = status; | ||
| else Object.defineProperty(response, "status", { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| Object.defineProperty(response, kStatus, { | ||
| value: status, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| static setUrl(url, response) { | ||
| if (!url || url === "about:" || !canParseUrl(url)) return; | ||
| const state = getValueBySymbol("state", response); | ||
| if (state) state.urlList.push(new URL(url)); | ||
| else Object.defineProperty(response, "url", { | ||
| value: url, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| Object.defineProperty(response, kUrl, { | ||
| value: url, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| /** | ||
| * Parses the given raw HTTP headers into a Fetch API `Headers` instance. | ||
| */ | ||
| static parseRawHeaders(rawHeaders) { | ||
| const headers = new Headers(); | ||
| for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]); | ||
| return headers; | ||
| } | ||
| /** | ||
| * Safely clones the given `Response`. | ||
| * Coerces response clone exceptions into 500 mocked responses. | ||
| * Handy in the environments that introduce arbitrary response | ||
| * cloning restrictions, like "101 Switching Protocols" cloning | ||
| * in "miniflare". | ||
| */ | ||
| static clone(response) { | ||
| try { | ||
| return response.clone(); | ||
| } catch (error) { | ||
| return Response.json(error instanceof Error ? { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| } : {}, { | ||
| status: 500, | ||
| statusText: "Unclonable Response" | ||
| }); | ||
| } | ||
| } | ||
| constructor(body, init = {}) { | ||
| const status = init.status ?? 200; | ||
| const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200; | ||
| const finalBody = FetchResponse.isResponseWithBody(status) ? body : null; | ||
| super(finalBody, { | ||
| status: safeStatus, | ||
| statusText: init.statusText, | ||
| headers: init.headers | ||
| }); | ||
| /** | ||
| * Since Node.js v24, Undici stores the Response state in an inaccessible field "#state". | ||
| * Forward the modified status/URL to the cloned response manually. | ||
| * @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242 | ||
| */ | ||
| if (status !== safeStatus) FetchResponse.setStatus(status, this); | ||
| FetchResponse.setUrl(init.url, this); | ||
| } | ||
| clone() { | ||
| const clonedResponse = super.clone(); | ||
| const customStatus = Reflect.get(this, kStatus); | ||
| if (customStatus) FetchResponse.setStatus(customStatus, clonedResponse); | ||
| const customUrl = Reflect.get(this, kUrl); | ||
| if (customUrl) FetchResponse.setUrl(customUrl, clonedResponse); | ||
| return clonedResponse; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/getRawRequest.ts | ||
| const kRawRequest = Symbol("kRawRequest"); | ||
| /** | ||
| * Returns a raw request instance associated with this request. | ||
| * | ||
| * @example | ||
| * interceptor.on('request', ({ request }) => { | ||
| * const rawRequest = getRawRequest(request) | ||
| * | ||
| * if (rawRequest instanceof http.ClientRequest) { | ||
| * console.log(rawRequest.rawHeaders) | ||
| * } | ||
| * }) | ||
| */ | ||
| function getRawRequest(request) { | ||
| return Reflect.get(request, kRawRequest); | ||
| } | ||
| function setRawRequest(request, rawRequest) { | ||
| Reflect.set(request, kRawRequest, rawRequest); | ||
| } | ||
| //#endregion | ||
| export { canParseUrl as a, FetchResponse as i, setRawRequest as n, RequestController as o, FetchRequest as r, InterceptorError as s, getRawRequest as t }; | ||
| //# sourceMappingURL=getRawRequest-B1BqgWG6.mjs.map |
| {"version":3,"file":"getRawRequest-B1BqgWG6.mjs","names":["request: Request","source: RequestControllerSource","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts","../../src/getRawRequest.ts"],"sourcesContent":["export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nconst kStatus = Symbol('kStatus')\nconst kUrl = Symbol('kUrl')\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setStatus(status: number, response: Response): void {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const internalState = getValueBySymbol<UndiciResponseState>(\n 'state',\n response\n )\n\n if (internalState) {\n internalState.status = status\n } else {\n Object.defineProperty(response, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n Object.defineProperty(response, kStatus, {\n value: status,\n enumerable: false,\n })\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n Object.defineProperty(response, kUrl, {\n value: url,\n enumerable: false,\n })\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n /**\n * Since Node.js v24, Undici stores the Response state in an inaccessible field \"#state\".\n * Forward the modified status/URL to the cloned response manually.\n * @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242\n */\n if (status !== safeStatus) {\n FetchResponse.setStatus(status, this)\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n\n public clone() {\n const clonedResponse = super.clone()\n\n const customStatus = Reflect.get(this, kStatus) as number | undefined\n\n if (customStatus) {\n FetchResponse.setStatus(customStatus, clonedResponse)\n }\n\n const customUrl = Reflect.get(this, kUrl) as string | undefined\n\n if (customUrl) {\n FetchResponse.setUrl(customUrl, clonedResponse)\n }\n\n return clonedResponse\n }\n}\n","const kRawRequest = Symbol('kRawRequest')\n\n/**\n * Returns a raw request instance associated with this request.\n *\n * @example\n * interceptor.on('request', ({ request }) => {\n * const rawRequest = getRawRequest(request)\n *\n * if (rawRequest instanceof http.ClientRequest) {\n * console.log(rawRequest.rawHeaders)\n * }\n * })\n */\nexport function getRawRequest(request: Request): unknown | undefined {\n return Reflect.get(request, kRawRequest)\n}\n\nexport function setRawRequest(request: Request, rawRequest: unknown): void {\n Reflect.set(request, kRawRequest, rawRequest)\n}\n"],"mappings":"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"} |
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let outvariant = require("outvariant"); | ||
| //#region src/InterceptorError.ts | ||
| var InterceptorError = class InterceptorError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "InterceptorError"; | ||
| Object.setPrototypeOf(this, InterceptorError.prototype); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/RequestController.ts | ||
| var RequestController = class RequestController { | ||
| static { | ||
| this.PENDING = 0; | ||
| } | ||
| static { | ||
| this.PASSTHROUGH = 1; | ||
| } | ||
| static { | ||
| this.RESPONSE = 2; | ||
| } | ||
| static { | ||
| this.ERROR = 3; | ||
| } | ||
| constructor(request, source) { | ||
| this.request = request; | ||
| this.source = source; | ||
| this.readyState = RequestController.PENDING; | ||
| this.handled = new _open_draft_deferred_promise.DeferredPromise(); | ||
| } | ||
| get #handled() { | ||
| return this.handled; | ||
| } | ||
| /** | ||
| * Perform this request as-is. | ||
| */ | ||
| async passthrough() { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url); | ||
| this.readyState = RequestController.PASSTHROUGH; | ||
| await this.source.passthrough(); | ||
| this.#handled.resolve(); | ||
| } | ||
| /** | ||
| * Respond to this request with the given `Response` instance. | ||
| * | ||
| * @example | ||
| * controller.respondWith(new Response()) | ||
| * controller.respondWith(Response.json({ id })) | ||
| * controller.respondWith(Response.error()) | ||
| */ | ||
| respondWith(response) { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState); | ||
| this.readyState = RequestController.RESPONSE; | ||
| this.#handled.resolve(); | ||
| /** | ||
| * @note Although `source.respondWith()` is potentially asynchronous, | ||
| * do NOT await it for backward-compatibility. Awaiting it will short-circuit | ||
| * the request listener invocation as soon as a listener responds to a request. | ||
| * Ideally, that's what we want, but that's not what we promise the user. | ||
| */ | ||
| this.source.respondWith(response); | ||
| } | ||
| /** | ||
| * Error this request with the given reason. | ||
| * | ||
| * @example | ||
| * controller.errorWith() | ||
| * controller.errorWith(new Error('Oops!')) | ||
| * controller.errorWith({ message: 'Oops!'}) | ||
| */ | ||
| errorWith(reason) { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState); | ||
| this.readyState = RequestController.ERROR; | ||
| this.source.errorWith(reason); | ||
| this.#handled.resolve(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/utils/canParseUrl.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given URL string | ||
| * can be parsed into a `URL` instance. | ||
| * A substitute for `URL.canParse()` for Node.js 18. | ||
| */ | ||
| function canParseUrl(url) { | ||
| try { | ||
| new URL(url); | ||
| return true; | ||
| } catch (_error) { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/getValueBySymbol.ts | ||
| /** | ||
| * Returns the value behind the symbol with the given name. | ||
| */ | ||
| function getValueBySymbol(symbolName, source) { | ||
| const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => { | ||
| return symbol$1.description === symbolName; | ||
| }); | ||
| if (symbol) return Reflect.get(source, symbol); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/fetchUtils.ts | ||
| var FetchRequest = class FetchRequest extends Request { | ||
| static #resolveProperty(input, init = {}, key) { | ||
| return init[key] ?? (input instanceof Request ? input[key] : void 0); | ||
| } | ||
| /** | ||
| * Check if the given request method is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#methods | ||
| */ | ||
| static isConfigurableMethod(method) { | ||
| return method !== "CONNECT" && method !== "TRACE" && method !== "TRACK"; | ||
| } | ||
| static isMethodWithBody(method) { | ||
| return method !== "HEAD" && method !== "GET" && FetchRequest.isConfigurableMethod(method); | ||
| } | ||
| /** | ||
| * Check if the given request `mode` is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#concept-request-mode | ||
| */ | ||
| static isConfigurableMode(mode) { | ||
| return mode !== "navigate" && mode !== "websocket" && mode !== "webtransport"; | ||
| } | ||
| constructor(input, init) { | ||
| const method = FetchRequest.#resolveProperty(input, init, "method") || "GET"; | ||
| const safeMethod = FetchRequest.isConfigurableMethod(method) ? method : "GET"; | ||
| const hasExplicitBody = init != null && "body" in init; | ||
| /** | ||
| * Only include `body` in the super init when it needs to be overridden. | ||
| * When `input` is a Request and no explicit body is in `init`, let the | ||
| * Request constructor handle body transfer naturally so it properly | ||
| * marks the original request's body as consumed (bodyUsed = true). | ||
| */ | ||
| const bodyInit = !FetchRequest.isMethodWithBody(method) ? { body: void 0 } : hasExplicitBody ? { body: init.body } : {}; | ||
| const mode = FetchRequest.#resolveProperty(input, init, "mode") ?? void 0; | ||
| const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : void 0; | ||
| super(input, { | ||
| ...init || {}, | ||
| method: safeMethod, | ||
| mode: safeMode, | ||
| duplex: init?.duplex ?? (FetchRequest.isMethodWithBody(method) ? "half" : void 0), | ||
| ...bodyInit | ||
| }); | ||
| if (method !== safeMethod) this.#setInternalProperty("method", method); | ||
| if (method === "CONNECT") { | ||
| const url = new URL(input instanceof Request ? input.url : input); | ||
| let authority; | ||
| /** | ||
| * @note Node.js has a bug parsing raw CONNECT requests URLs like | ||
| * "http://127.0.0.1:1337/localhost:80". It would treat "localhost:" as a protocol. | ||
| */ | ||
| if (url.protocol === "localhost:") authority = url.href; | ||
| else authority = url.pathname.replace(/^\/+/, ""); | ||
| /** | ||
| * @note Define "url" as a getter because Undici uses their own | ||
| * logic to resolve the "request.url" property. Simply reassigning | ||
| * its value doesn't do anything. This is a destructive action | ||
| * but it's safe because "CONNECT" requests are forbidden per fetch. | ||
| */ | ||
| Object.defineProperty(this, "url", { | ||
| get: () => authority, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| if (mode != null && mode !== safeMode) this.#setInternalProperty("mode", mode); | ||
| } | ||
| #setInternalProperty(key, value) { | ||
| const internalState = getValueBySymbol("state", this); | ||
| if (internalState) Reflect.set(internalState, key, value); | ||
| else Object.defineProperty(this, key, { | ||
| value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| }; | ||
| const kStatus = Symbol("kStatus"); | ||
| const kUrl = Symbol("kUrl"); | ||
| var FetchResponse = class FetchResponse extends Response { | ||
| static { | ||
| this.STATUS_CODES_WITHOUT_BODY = [ | ||
| 101, | ||
| 103, | ||
| 204, | ||
| 205, | ||
| 304 | ||
| ]; | ||
| } | ||
| static { | ||
| this.STATUS_CODES_WITH_REDIRECT = [ | ||
| 301, | ||
| 302, | ||
| 303, | ||
| 307, | ||
| 308 | ||
| ]; | ||
| } | ||
| static isConfigurableStatusCode(status) { | ||
| return status >= 200 && status <= 599; | ||
| } | ||
| static isRedirectResponse(status) { | ||
| return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status); | ||
| } | ||
| /** | ||
| * Returns a boolean indicating whether the given response status | ||
| * code represents a response that can have a body. | ||
| */ | ||
| static isResponseWithBody(status) { | ||
| return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status); | ||
| } | ||
| static setStatus(status, response) { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const internalState = getValueBySymbol("state", response); | ||
| if (internalState) internalState.status = status; | ||
| else Object.defineProperty(response, "status", { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| Object.defineProperty(response, kStatus, { | ||
| value: status, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| static setUrl(url, response) { | ||
| if (!url || url === "about:" || !canParseUrl(url)) return; | ||
| const state = getValueBySymbol("state", response); | ||
| if (state) state.urlList.push(new URL(url)); | ||
| else Object.defineProperty(response, "url", { | ||
| value: url, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| Object.defineProperty(response, kUrl, { | ||
| value: url, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| /** | ||
| * Parses the given raw HTTP headers into a Fetch API `Headers` instance. | ||
| */ | ||
| static parseRawHeaders(rawHeaders) { | ||
| const headers = new Headers(); | ||
| for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]); | ||
| return headers; | ||
| } | ||
| /** | ||
| * Safely clones the given `Response`. | ||
| * Coerces response clone exceptions into 500 mocked responses. | ||
| * Handy in the environments that introduce arbitrary response | ||
| * cloning restrictions, like "101 Switching Protocols" cloning | ||
| * in "miniflare". | ||
| */ | ||
| static clone(response) { | ||
| try { | ||
| return response.clone(); | ||
| } catch (error) { | ||
| return Response.json(error instanceof Error ? { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| } : {}, { | ||
| status: 500, | ||
| statusText: "Unclonable Response" | ||
| }); | ||
| } | ||
| } | ||
| constructor(body, init = {}) { | ||
| const status = init.status ?? 200; | ||
| const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200; | ||
| const finalBody = FetchResponse.isResponseWithBody(status) ? body : null; | ||
| super(finalBody, { | ||
| status: safeStatus, | ||
| statusText: init.statusText, | ||
| headers: init.headers | ||
| }); | ||
| /** | ||
| * Since Node.js v24, Undici stores the Response state in an inaccessible field "#state". | ||
| * Forward the modified status/URL to the cloned response manually. | ||
| * @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242 | ||
| */ | ||
| if (status !== safeStatus) FetchResponse.setStatus(status, this); | ||
| FetchResponse.setUrl(init.url, this); | ||
| } | ||
| clone() { | ||
| const clonedResponse = super.clone(); | ||
| const customStatus = Reflect.get(this, kStatus); | ||
| if (customStatus) FetchResponse.setStatus(customStatus, clonedResponse); | ||
| const customUrl = Reflect.get(this, kUrl); | ||
| if (customUrl) FetchResponse.setUrl(customUrl, clonedResponse); | ||
| return clonedResponse; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/getRawRequest.ts | ||
| const kRawRequest = Symbol("kRawRequest"); | ||
| /** | ||
| * Returns a raw request instance associated with this request. | ||
| * | ||
| * @example | ||
| * interceptor.on('request', ({ request }) => { | ||
| * const rawRequest = getRawRequest(request) | ||
| * | ||
| * if (rawRequest instanceof http.ClientRequest) { | ||
| * console.log(rawRequest.rawHeaders) | ||
| * } | ||
| * }) | ||
| */ | ||
| function getRawRequest(request) { | ||
| return Reflect.get(request, kRawRequest); | ||
| } | ||
| function setRawRequest(request, rawRequest) { | ||
| Reflect.set(request, kRawRequest, rawRequest); | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'FetchRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'FetchResponse', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchResponse; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'InterceptorError', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return InterceptorError; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'RequestController', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return RequestController; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'canParseUrl', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return canParseUrl; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'getRawRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return getRawRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'setRawRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return setRawRequest; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=getRawRequest-DdfaiPVH.cjs.map |
| {"version":3,"file":"getRawRequest-DdfaiPVH.cjs","names":["request: Request","source: RequestControllerSource","DeferredPromise","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts","../../src/getRawRequest.ts"],"sourcesContent":["export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nconst kStatus = Symbol('kStatus')\nconst kUrl = Symbol('kUrl')\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setStatus(status: number, response: Response): void {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const internalState = getValueBySymbol<UndiciResponseState>(\n 'state',\n response\n )\n\n if (internalState) {\n internalState.status = status\n } else {\n Object.defineProperty(response, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n Object.defineProperty(response, kStatus, {\n value: status,\n enumerable: false,\n })\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n Object.defineProperty(response, kUrl, {\n value: url,\n enumerable: false,\n })\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n /**\n * Since Node.js v24, Undici stores the Response state in an inaccessible field \"#state\".\n * Forward the modified status/URL to the cloned response manually.\n * @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242\n */\n if (status !== safeStatus) {\n FetchResponse.setStatus(status, this)\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n\n public clone() {\n const clonedResponse = super.clone()\n\n const customStatus = Reflect.get(this, kStatus) as number | undefined\n\n if (customStatus) {\n FetchResponse.setStatus(customStatus, clonedResponse)\n }\n\n const customUrl = Reflect.get(this, kUrl) as string | undefined\n\n if (customUrl) {\n FetchResponse.setUrl(customUrl, clonedResponse)\n }\n\n return clonedResponse\n }\n}\n","const kRawRequest = Symbol('kRawRequest')\n\n/**\n * Returns a raw request instance associated with this request.\n *\n * @example\n * interceptor.on('request', ({ request }) => {\n * const rawRequest = getRawRequest(request)\n *\n * if (rawRequest instanceof http.ClientRequest) {\n * console.log(rawRequest.rawHeaders)\n * }\n * })\n */\nexport function getRawRequest(request: Request): unknown | undefined {\n return Reflect.get(request, kRawRequest)\n}\n\nexport function setRawRequest(request: Request, rawRequest: unknown): void {\n Reflect.set(request, kRawRequest, rawRequest)\n}\n"],"mappings":"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"} |
| import { o as RequestController, s as InterceptorError } from "./getRawRequest-B1BqgWG6.mjs"; | ||
| import { r as emitAsync } from "./hasConfigurableGlobal-FTYwno1G.mjs"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { until } from "@open-draft/until"; | ||
| //#region src/utils/isObject.ts | ||
| /** | ||
| * Determines if a given value is an instance of object. | ||
| */ | ||
| function isObject(value, loose = false) { | ||
| return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]"; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isPropertyAccessible.ts | ||
| /** | ||
| * A function that validates if property access is possible on an object | ||
| * without throwing. It returns `true` if the property access is possible | ||
| * and `false` otherwise. | ||
| * | ||
| * Environments like miniflare will throw on property access on certain objects | ||
| * like Request and Response, for unimplemented properties. | ||
| */ | ||
| function isPropertyAccessible(obj, key) { | ||
| try { | ||
| obj[key]; | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/responseUtils.ts | ||
| /** | ||
| * Creates a generic 500 Unhandled Exception response. | ||
| */ | ||
| function createServerErrorResponse(body) { | ||
| return new Response(JSON.stringify(body instanceof Error ? { | ||
| name: body.name, | ||
| message: body.message, | ||
| stack: body.stack | ||
| } : body), { | ||
| status: 500, | ||
| statusText: "Unhandled Exception", | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| /** | ||
| * Check if the given response is a `Response.error()`. | ||
| * | ||
| * @note Some environments, like Miniflare (Cloudflare) do not | ||
| * implement the "Response.type" property and throw on its access. | ||
| * Safely check if we can access "type" on "Response" before continuing. | ||
| * @see https://github.com/mswjs/msw/issues/1834 | ||
| */ | ||
| function isResponseError(response) { | ||
| return response != null && response instanceof Response && isPropertyAccessible(response, "type") && response.type === "error"; | ||
| } | ||
| /** | ||
| * Check if the given value is a `Response` or a Response-like object. | ||
| * This is different from `value instanceof Response` because it supports | ||
| * custom `Response` constructors, like the one when using Undici directly. | ||
| */ | ||
| function isResponseLike(value) { | ||
| return isObject(value, true) && isPropertyAccessible(value, "status") && isPropertyAccessible(value, "statusText") && isPropertyAccessible(value, "bodyUsed"); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isNodeLikeError.ts | ||
| function isNodeLikeError(error) { | ||
| if (error == null) return false; | ||
| if (!(error instanceof Error)) return false; | ||
| return "code" in error && "errno" in error; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/handleRequest.ts | ||
| async function handleRequest(options) { | ||
| const handleResponse = async (response) => { | ||
| if (response instanceof Error) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| if (isResponseError(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| /** | ||
| * Handle normal responses or response-like objects. | ||
| * @note This must come before the arbitrary object check | ||
| * since Response instances are, in fact, objects. | ||
| */ | ||
| if (isResponseLike(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| if (isObject(response)) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| const handleResponseError = async (error) => { | ||
| if (error instanceof InterceptorError) throw result.error; | ||
| if (isNodeLikeError(error)) { | ||
| await options.controller.errorWith(error); | ||
| return true; | ||
| } | ||
| if (error instanceof Response) return await handleResponse(error); | ||
| return false; | ||
| }; | ||
| const requestAbortPromise = new DeferredPromise(); | ||
| /** | ||
| * @note `signal` is not always defined in React Native. | ||
| */ | ||
| if (options.request.signal) { | ||
| if (options.request.signal.aborted) { | ||
| await options.controller.errorWith(options.request.signal.reason); | ||
| return; | ||
| } | ||
| options.request.signal.addEventListener("abort", () => { | ||
| requestAbortPromise.reject(options.request.signal.reason); | ||
| }, { once: true }); | ||
| } | ||
| const result = await until(async () => { | ||
| const requestListenersPromise = emitAsync(options.emitter, "request", { | ||
| requestId: options.requestId, | ||
| request: options.request, | ||
| controller: options.controller | ||
| }); | ||
| await Promise.race([ | ||
| requestAbortPromise, | ||
| requestListenersPromise, | ||
| options.controller.handled | ||
| ]); | ||
| }); | ||
| if (requestAbortPromise.state === "rejected") { | ||
| await options.controller.errorWith(requestAbortPromise.rejectionReason); | ||
| return; | ||
| } | ||
| if (result.error) { | ||
| if (await handleResponseError(result.error)) return; | ||
| if (options.emitter.listenerCount("unhandledException") > 0) { | ||
| const unhandledExceptionController = new RequestController(options.request, { | ||
| passthrough() {}, | ||
| async respondWith(response) { | ||
| await handleResponse(response); | ||
| }, | ||
| async errorWith(reason) { | ||
| /** | ||
| * @note Handle the result of the unhandled controller | ||
| * in the same way as the original request controller. | ||
| * The exception here is that thrown errors within the | ||
| * "unhandledException" event do NOT result in another | ||
| * emit of the same event. They are forwarded as-is. | ||
| */ | ||
| await options.controller.errorWith(reason); | ||
| } | ||
| }); | ||
| await emitAsync(options.emitter, "unhandledException", { | ||
| error: result.error, | ||
| request: options.request, | ||
| requestId: options.requestId, | ||
| controller: unhandledExceptionController | ||
| }); | ||
| if (unhandledExceptionController.readyState !== RequestController.PENDING) return; | ||
| } | ||
| await options.controller.respondWith(createServerErrorResponse(result.error)); | ||
| return; | ||
| } | ||
| if (options.controller.readyState === RequestController.PENDING) return await options.controller.passthrough(); | ||
| return options.controller.handled; | ||
| } | ||
| //#endregion | ||
| export { isResponseError as n, handleRequest as t }; | ||
| //# sourceMappingURL=handleRequest-BHrC8Flw.mjs.map |
| {"version":3,"file":"handleRequest-BHrC8Flw.mjs","names":[],"sources":["../../src/utils/isObject.ts","../../src/utils/isPropertyAccessible.ts","../../src/utils/responseUtils.ts","../../src/utils/isNodeLikeError.ts","../../src/utils/handleRequest.ts"],"sourcesContent":["/**\n * Determines if a given value is an instance of object.\n */\nexport function isObject<T>(value: any, loose = false): value is T {\n return loose\n ? Object.prototype.toString.call(value).startsWith('[object ')\n : Object.prototype.toString.call(value) === '[object Object]'\n}\n","/**\n * A function that validates if property access is possible on an object\n * without throwing. It returns `true` if the property access is possible\n * and `false` otherwise.\n *\n * Environments like miniflare will throw on property access on certain objects\n * like Request and Response, for unimplemented properties.\n */\nexport function isPropertyAccessible<Obj extends Record<string, any>>(\n obj: Obj,\n key: keyof Obj\n) {\n try {\n obj[key]\n return true\n } catch {\n return false\n }\n}\n","import { isObject } from './isObject'\nimport { isPropertyAccessible } from './isPropertyAccessible'\n\n/**\n * Creates a generic 500 Unhandled Exception response.\n */\nexport function createServerErrorResponse(body: unknown): Response {\n return new Response(\n JSON.stringify(\n body instanceof Error\n ? {\n name: body.name,\n message: body.message,\n stack: body.stack,\n }\n : body\n ),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n}\n\nexport type ResponseError = Response & { type: 'error' }\n\n/**\n * Check if the given response is a `Response.error()`.\n *\n * @note Some environments, like Miniflare (Cloudflare) do not\n * implement the \"Response.type\" property and throw on its access.\n * Safely check if we can access \"type\" on \"Response\" before continuing.\n * @see https://github.com/mswjs/msw/issues/1834\n */\nexport function isResponseError(response: unknown): response is ResponseError {\n return (\n response != null &&\n response instanceof Response &&\n isPropertyAccessible(response, 'type') &&\n response.type === 'error'\n )\n}\n\n/**\n * Check if the given value is a `Response` or a Response-like object.\n * This is different from `value instanceof Response` because it supports\n * custom `Response` constructors, like the one when using Undici directly.\n */\nexport function isResponseLike(value: unknown): value is Response {\n return (\n isObject<Record<string, any>>(value, true) &&\n isPropertyAccessible(value, 'status') &&\n isPropertyAccessible(value, 'statusText') &&\n isPropertyAccessible(value, 'bodyUsed')\n )\n}\n","export function isNodeLikeError(\n error: unknown\n): error is NodeJS.ErrnoException {\n if (error == null) {\n return false\n }\n\n if (!(error instanceof Error)) {\n return false\n }\n\n return 'code' in error && 'errno' in error\n}\n","import type { Emitter } from 'strict-event-emitter'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport type { HttpRequestEventMap } from '../glossary'\nimport { emitAsync } from './emitAsync'\nimport { RequestController } from '../RequestController'\nimport {\n createServerErrorResponse,\n isResponseError,\n isResponseLike,\n} from './responseUtils'\nimport { InterceptorError } from '../InterceptorError'\nimport { isNodeLikeError } from './isNodeLikeError'\nimport { isObject } from './isObject'\n\ninterface HandleRequestOptions {\n requestId: string\n request: Request\n emitter: Emitter<HttpRequestEventMap>\n controller: RequestController\n}\n\nexport async function handleRequest(\n options: HandleRequestOptions\n): Promise<void> {\n const handleResponse = async (\n response: Response | Error | Record<string, any>\n ) => {\n if (response instanceof Error) {\n await options.controller.errorWith(response)\n return true\n }\n\n // Handle \"Response.error()\" instances.\n if (isResponseError(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n /**\n * Handle normal responses or response-like objects.\n * @note This must come before the arbitrary object check\n * since Response instances are, in fact, objects.\n */\n if (isResponseLike(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n // Handle arbitrary objects provided to `.errorWith(reason)`.\n if (isObject(response)) {\n await options.controller.errorWith(response)\n return true\n }\n\n return false\n }\n\n const handleResponseError = async (error: unknown): Promise<boolean> => {\n // Forward the special interceptor error instances\n // to the developer. These must not be handled in any way.\n if (error instanceof InterceptorError) {\n throw result.error\n }\n\n // Support mocking Node.js-like errors.\n if (isNodeLikeError(error)) {\n await options.controller.errorWith(error)\n return true\n }\n\n // Handle thrown responses.\n if (error instanceof Response) {\n return await handleResponse(error)\n }\n\n return false\n }\n\n // Add the last \"request\" listener to check if the request\n // has been handled in any way. If it hasn't, resolve the\n // response promise with undefined.\n // options.emitter.once('request', async ({ requestId: pendingRequestId }) => {\n // if (\n // pendingRequestId === options.requestId &&\n // options.controller.readyState === RequestController.PENDING\n // ) {\n // await options.controller.passthrough()\n // }\n // })\n\n const requestAbortPromise = new DeferredPromise<void, unknown>()\n\n /**\n * @note `signal` is not always defined in React Native.\n */\n if (options.request.signal) {\n if (options.request.signal.aborted) {\n await options.controller.errorWith(options.request.signal.reason)\n return\n }\n\n options.request.signal.addEventListener(\n 'abort',\n () => {\n requestAbortPromise.reject(options.request.signal.reason)\n },\n { once: true }\n )\n }\n\n const result = await until(async () => {\n // Emit the \"request\" event and wait until all the listeners\n // for that event are finished (e.g. async listeners awaited).\n // By the end of this promise, the developer cannot affect the\n // request anymore.\n const requestListenersPromise = emitAsync(options.emitter, 'request', {\n requestId: options.requestId,\n request: options.request,\n controller: options.controller,\n })\n\n await Promise.race([\n // Short-circuit the request handling promise if the request gets aborted.\n requestAbortPromise,\n requestListenersPromise,\n options.controller.handled,\n ])\n })\n\n // Handle the request being aborted while waiting for the request listeners.\n if (requestAbortPromise.state === 'rejected') {\n await options.controller.errorWith(requestAbortPromise.rejectionReason)\n return\n }\n\n if (result.error) {\n // Handle the error during the request listener execution.\n // These can be thrown responses or request errors.\n if (await handleResponseError(result.error)) {\n return\n }\n\n // If the developer has added \"unhandledException\" listeners,\n // allow them to handle the error. They can translate it to a\n // mocked response, network error, or forward it as-is.\n if (options.emitter.listenerCount('unhandledException') > 0) {\n // Create a new request controller just for the unhandled exception case.\n // This is needed because the original controller might have been already\n // interacted with (e.g. \"respondWith\" or \"errorWith\" called on it).\n const unhandledExceptionController = new RequestController(\n options.request,\n {\n /**\n * @note Intentionally empty passthrough handle.\n * This controller is created within another controller and we only need\n * to know if `unhandledException` listeners handled the request.\n */\n passthrough() {},\n async respondWith(response) {\n await handleResponse(response)\n },\n async errorWith(reason) {\n /**\n * @note Handle the result of the unhandled controller\n * in the same way as the original request controller.\n * The exception here is that thrown errors within the\n * \"unhandledException\" event do NOT result in another\n * emit of the same event. They are forwarded as-is.\n */\n await options.controller.errorWith(reason)\n },\n }\n )\n\n await emitAsync(options.emitter, 'unhandledException', {\n error: result.error,\n request: options.request,\n requestId: options.requestId,\n controller: unhandledExceptionController,\n })\n\n // If all the \"unhandledException\" listeners have finished\n // but have not handled the request in any way, passthrough.\n if (\n unhandledExceptionController.readyState !== RequestController.PENDING\n ) {\n return\n }\n }\n\n // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.\n await options.controller.respondWith(\n createServerErrorResponse(result.error)\n )\n return\n }\n\n // If the request hasn't been handled by this point, passthrough.\n if (options.controller.readyState === RequestController.PENDING) {\n return await options.controller.passthrough()\n }\n\n return options.controller.handled\n}\n"],"mappings":"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"} |
| const require_getRawRequest = require('./getRawRequest-DdfaiPVH.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-BS75Oulv.cjs'); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let _open_draft_until = require("@open-draft/until"); | ||
| //#region src/utils/isObject.ts | ||
| /** | ||
| * Determines if a given value is an instance of object. | ||
| */ | ||
| function isObject(value, loose = false) { | ||
| return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]"; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isPropertyAccessible.ts | ||
| /** | ||
| * A function that validates if property access is possible on an object | ||
| * without throwing. It returns `true` if the property access is possible | ||
| * and `false` otherwise. | ||
| * | ||
| * Environments like miniflare will throw on property access on certain objects | ||
| * like Request and Response, for unimplemented properties. | ||
| */ | ||
| function isPropertyAccessible(obj, key) { | ||
| try { | ||
| obj[key]; | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/responseUtils.ts | ||
| /** | ||
| * Creates a generic 500 Unhandled Exception response. | ||
| */ | ||
| function createServerErrorResponse(body) { | ||
| return new Response(JSON.stringify(body instanceof Error ? { | ||
| name: body.name, | ||
| message: body.message, | ||
| stack: body.stack | ||
| } : body), { | ||
| status: 500, | ||
| statusText: "Unhandled Exception", | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| /** | ||
| * Check if the given response is a `Response.error()`. | ||
| * | ||
| * @note Some environments, like Miniflare (Cloudflare) do not | ||
| * implement the "Response.type" property and throw on its access. | ||
| * Safely check if we can access "type" on "Response" before continuing. | ||
| * @see https://github.com/mswjs/msw/issues/1834 | ||
| */ | ||
| function isResponseError(response) { | ||
| return response != null && response instanceof Response && isPropertyAccessible(response, "type") && response.type === "error"; | ||
| } | ||
| /** | ||
| * Check if the given value is a `Response` or a Response-like object. | ||
| * This is different from `value instanceof Response` because it supports | ||
| * custom `Response` constructors, like the one when using Undici directly. | ||
| */ | ||
| function isResponseLike(value) { | ||
| return isObject(value, true) && isPropertyAccessible(value, "status") && isPropertyAccessible(value, "statusText") && isPropertyAccessible(value, "bodyUsed"); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isNodeLikeError.ts | ||
| function isNodeLikeError(error) { | ||
| if (error == null) return false; | ||
| if (!(error instanceof Error)) return false; | ||
| return "code" in error && "errno" in error; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/handleRequest.ts | ||
| async function handleRequest(options) { | ||
| const handleResponse = async (response) => { | ||
| if (response instanceof Error) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| if (isResponseError(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| /** | ||
| * Handle normal responses or response-like objects. | ||
| * @note This must come before the arbitrary object check | ||
| * since Response instances are, in fact, objects. | ||
| */ | ||
| if (isResponseLike(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| if (isObject(response)) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| const handleResponseError = async (error) => { | ||
| if (error instanceof require_getRawRequest.InterceptorError) throw result.error; | ||
| if (isNodeLikeError(error)) { | ||
| await options.controller.errorWith(error); | ||
| return true; | ||
| } | ||
| if (error instanceof Response) return await handleResponse(error); | ||
| return false; | ||
| }; | ||
| const requestAbortPromise = new _open_draft_deferred_promise.DeferredPromise(); | ||
| /** | ||
| * @note `signal` is not always defined in React Native. | ||
| */ | ||
| if (options.request.signal) { | ||
| if (options.request.signal.aborted) { | ||
| await options.controller.errorWith(options.request.signal.reason); | ||
| return; | ||
| } | ||
| options.request.signal.addEventListener("abort", () => { | ||
| requestAbortPromise.reject(options.request.signal.reason); | ||
| }, { once: true }); | ||
| } | ||
| const result = await (0, _open_draft_until.until)(async () => { | ||
| const requestListenersPromise = require_hasConfigurableGlobal.emitAsync(options.emitter, "request", { | ||
| requestId: options.requestId, | ||
| request: options.request, | ||
| controller: options.controller | ||
| }); | ||
| await Promise.race([ | ||
| requestAbortPromise, | ||
| requestListenersPromise, | ||
| options.controller.handled | ||
| ]); | ||
| }); | ||
| if (requestAbortPromise.state === "rejected") { | ||
| await options.controller.errorWith(requestAbortPromise.rejectionReason); | ||
| return; | ||
| } | ||
| if (result.error) { | ||
| if (await handleResponseError(result.error)) return; | ||
| if (options.emitter.listenerCount("unhandledException") > 0) { | ||
| const unhandledExceptionController = new require_getRawRequest.RequestController(options.request, { | ||
| passthrough() {}, | ||
| async respondWith(response) { | ||
| await handleResponse(response); | ||
| }, | ||
| async errorWith(reason) { | ||
| /** | ||
| * @note Handle the result of the unhandled controller | ||
| * in the same way as the original request controller. | ||
| * The exception here is that thrown errors within the | ||
| * "unhandledException" event do NOT result in another | ||
| * emit of the same event. They are forwarded as-is. | ||
| */ | ||
| await options.controller.errorWith(reason); | ||
| } | ||
| }); | ||
| await require_hasConfigurableGlobal.emitAsync(options.emitter, "unhandledException", { | ||
| error: result.error, | ||
| request: options.request, | ||
| requestId: options.requestId, | ||
| controller: unhandledExceptionController | ||
| }); | ||
| if (unhandledExceptionController.readyState !== require_getRawRequest.RequestController.PENDING) return; | ||
| } | ||
| await options.controller.respondWith(createServerErrorResponse(result.error)); | ||
| return; | ||
| } | ||
| if (options.controller.readyState === require_getRawRequest.RequestController.PENDING) return await options.controller.passthrough(); | ||
| return options.controller.handled; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'handleRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return handleRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isResponseError', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isResponseError; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=handleRequest-tUNU616J.cjs.map |
| {"version":3,"file":"handleRequest-tUNU616J.cjs","names":["InterceptorError","DeferredPromise","emitAsync","RequestController"],"sources":["../../src/utils/isObject.ts","../../src/utils/isPropertyAccessible.ts","../../src/utils/responseUtils.ts","../../src/utils/isNodeLikeError.ts","../../src/utils/handleRequest.ts"],"sourcesContent":["/**\n * Determines if a given value is an instance of object.\n */\nexport function isObject<T>(value: any, loose = false): value is T {\n return loose\n ? Object.prototype.toString.call(value).startsWith('[object ')\n : Object.prototype.toString.call(value) === '[object Object]'\n}\n","/**\n * A function that validates if property access is possible on an object\n * without throwing. It returns `true` if the property access is possible\n * and `false` otherwise.\n *\n * Environments like miniflare will throw on property access on certain objects\n * like Request and Response, for unimplemented properties.\n */\nexport function isPropertyAccessible<Obj extends Record<string, any>>(\n obj: Obj,\n key: keyof Obj\n) {\n try {\n obj[key]\n return true\n } catch {\n return false\n }\n}\n","import { isObject } from './isObject'\nimport { isPropertyAccessible } from './isPropertyAccessible'\n\n/**\n * Creates a generic 500 Unhandled Exception response.\n */\nexport function createServerErrorResponse(body: unknown): Response {\n return new Response(\n JSON.stringify(\n body instanceof Error\n ? {\n name: body.name,\n message: body.message,\n stack: body.stack,\n }\n : body\n ),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n}\n\nexport type ResponseError = Response & { type: 'error' }\n\n/**\n * Check if the given response is a `Response.error()`.\n *\n * @note Some environments, like Miniflare (Cloudflare) do not\n * implement the \"Response.type\" property and throw on its access.\n * Safely check if we can access \"type\" on \"Response\" before continuing.\n * @see https://github.com/mswjs/msw/issues/1834\n */\nexport function isResponseError(response: unknown): response is ResponseError {\n return (\n response != null &&\n response instanceof Response &&\n isPropertyAccessible(response, 'type') &&\n response.type === 'error'\n )\n}\n\n/**\n * Check if the given value is a `Response` or a Response-like object.\n * This is different from `value instanceof Response` because it supports\n * custom `Response` constructors, like the one when using Undici directly.\n */\nexport function isResponseLike(value: unknown): value is Response {\n return (\n isObject<Record<string, any>>(value, true) &&\n isPropertyAccessible(value, 'status') &&\n isPropertyAccessible(value, 'statusText') &&\n isPropertyAccessible(value, 'bodyUsed')\n )\n}\n","export function isNodeLikeError(\n error: unknown\n): error is NodeJS.ErrnoException {\n if (error == null) {\n return false\n }\n\n if (!(error instanceof Error)) {\n return false\n }\n\n return 'code' in error && 'errno' in error\n}\n","import type { Emitter } from 'strict-event-emitter'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport type { HttpRequestEventMap } from '../glossary'\nimport { emitAsync } from './emitAsync'\nimport { RequestController } from '../RequestController'\nimport {\n createServerErrorResponse,\n isResponseError,\n isResponseLike,\n} from './responseUtils'\nimport { InterceptorError } from '../InterceptorError'\nimport { isNodeLikeError } from './isNodeLikeError'\nimport { isObject } from './isObject'\n\ninterface HandleRequestOptions {\n requestId: string\n request: Request\n emitter: Emitter<HttpRequestEventMap>\n controller: RequestController\n}\n\nexport async function handleRequest(\n options: HandleRequestOptions\n): Promise<void> {\n const handleResponse = async (\n response: Response | Error | Record<string, any>\n ) => {\n if (response instanceof Error) {\n await options.controller.errorWith(response)\n return true\n }\n\n // Handle \"Response.error()\" instances.\n if (isResponseError(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n /**\n * Handle normal responses or response-like objects.\n * @note This must come before the arbitrary object check\n * since Response instances are, in fact, objects.\n */\n if (isResponseLike(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n // Handle arbitrary objects provided to `.errorWith(reason)`.\n if (isObject(response)) {\n await options.controller.errorWith(response)\n return true\n }\n\n return false\n }\n\n const handleResponseError = async (error: unknown): Promise<boolean> => {\n // Forward the special interceptor error instances\n // to the developer. These must not be handled in any way.\n if (error instanceof InterceptorError) {\n throw result.error\n }\n\n // Support mocking Node.js-like errors.\n if (isNodeLikeError(error)) {\n await options.controller.errorWith(error)\n return true\n }\n\n // Handle thrown responses.\n if (error instanceof Response) {\n return await handleResponse(error)\n }\n\n return false\n }\n\n // Add the last \"request\" listener to check if the request\n // has been handled in any way. If it hasn't, resolve the\n // response promise with undefined.\n // options.emitter.once('request', async ({ requestId: pendingRequestId }) => {\n // if (\n // pendingRequestId === options.requestId &&\n // options.controller.readyState === RequestController.PENDING\n // ) {\n // await options.controller.passthrough()\n // }\n // })\n\n const requestAbortPromise = new DeferredPromise<void, unknown>()\n\n /**\n * @note `signal` is not always defined in React Native.\n */\n if (options.request.signal) {\n if (options.request.signal.aborted) {\n await options.controller.errorWith(options.request.signal.reason)\n return\n }\n\n options.request.signal.addEventListener(\n 'abort',\n () => {\n requestAbortPromise.reject(options.request.signal.reason)\n },\n { once: true }\n )\n }\n\n const result = await until(async () => {\n // Emit the \"request\" event and wait until all the listeners\n // for that event are finished (e.g. async listeners awaited).\n // By the end of this promise, the developer cannot affect the\n // request anymore.\n const requestListenersPromise = emitAsync(options.emitter, 'request', {\n requestId: options.requestId,\n request: options.request,\n controller: options.controller,\n })\n\n await Promise.race([\n // Short-circuit the request handling promise if the request gets aborted.\n requestAbortPromise,\n requestListenersPromise,\n options.controller.handled,\n ])\n })\n\n // Handle the request being aborted while waiting for the request listeners.\n if (requestAbortPromise.state === 'rejected') {\n await options.controller.errorWith(requestAbortPromise.rejectionReason)\n return\n }\n\n if (result.error) {\n // Handle the error during the request listener execution.\n // These can be thrown responses or request errors.\n if (await handleResponseError(result.error)) {\n return\n }\n\n // If the developer has added \"unhandledException\" listeners,\n // allow them to handle the error. They can translate it to a\n // mocked response, network error, or forward it as-is.\n if (options.emitter.listenerCount('unhandledException') > 0) {\n // Create a new request controller just for the unhandled exception case.\n // This is needed because the original controller might have been already\n // interacted with (e.g. \"respondWith\" or \"errorWith\" called on it).\n const unhandledExceptionController = new RequestController(\n options.request,\n {\n /**\n * @note Intentionally empty passthrough handle.\n * This controller is created within another controller and we only need\n * to know if `unhandledException` listeners handled the request.\n */\n passthrough() {},\n async respondWith(response) {\n await handleResponse(response)\n },\n async errorWith(reason) {\n /**\n * @note Handle the result of the unhandled controller\n * in the same way as the original request controller.\n * The exception here is that thrown errors within the\n * \"unhandledException\" event do NOT result in another\n * emit of the same event. They are forwarded as-is.\n */\n await options.controller.errorWith(reason)\n },\n }\n )\n\n await emitAsync(options.emitter, 'unhandledException', {\n error: result.error,\n request: options.request,\n requestId: options.requestId,\n controller: unhandledExceptionController,\n })\n\n // If all the \"unhandledException\" listeners have finished\n // but have not handled the request in any way, passthrough.\n if (\n unhandledExceptionController.readyState !== RequestController.PENDING\n ) {\n return\n }\n }\n\n // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.\n await options.controller.respondWith(\n createServerErrorResponse(result.error)\n )\n return\n }\n\n // If the request hasn't been handled by this point, passthrough.\n if (options.controller.readyState === RequestController.PENDING) {\n return await options.controller.passthrough()\n }\n\n return options.controller.handled\n}\n"],"mappings":"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"} |
| let outvariant = require("outvariant"); | ||
| //#region src/utils/emitAsync.ts | ||
| /** | ||
| * Emits an event on the given emitter but executes | ||
| * the listeners sequentially. This accounts for asynchronous | ||
| * listeners (e.g. those having "sleep" and handling the request). | ||
| */ | ||
| async function emitAsync(emitter, eventName, ...data) { | ||
| const listeners = emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| for (const listener of listeners) await listener.apply(emitter, data); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/patchesRegistry.ts | ||
| var PatchesRegistry = class { | ||
| #replacements = /* @__PURE__ */ new Map(); | ||
| applyPatch(owner, key, getNextValue) { | ||
| const ownerReplacements = this.#replacements.get(owner); | ||
| (0, outvariant.invariant)(!ownerReplacements?.has(key), `Failed to replace a global value at "${String(key)}": already replaced.`); | ||
| const match = getDeepPropertyDescriptor(owner, key); | ||
| if (typeof match === "undefined") { | ||
| console.warn(`Failed to replace a global value at "${String(key)}": not a global value.`); | ||
| return () => {}; | ||
| } | ||
| Object.defineProperty(owner, key, { | ||
| value: getNextValue(owner[key]), | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| const restorePatch = () => { | ||
| const currentReplacements = this.#replacements.get(owner); | ||
| if (!currentReplacements?.has(key)) return; | ||
| if (match.owner === owner) Object.defineProperty(match.owner, key, match.descriptor); | ||
| else | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the match's owner isn't the original owner, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(owner, key); | ||
| currentReplacements.delete(key); | ||
| if (currentReplacements.size === 0) this.#replacements.delete(owner); | ||
| }; | ||
| if (ownerReplacements) ownerReplacements.set(key, restorePatch); | ||
| else this.#replacements.set(owner, new Map([[key, restorePatch]])); | ||
| return restorePatch; | ||
| } | ||
| restoreAllPatches() { | ||
| const errors = []; | ||
| for (const [, ownerReplacements] of this.#replacements) for (const [, restorePatch] of ownerReplacements) try { | ||
| restorePatch(); | ||
| } catch (error) { | ||
| if (error instanceof Error) errors.push(error); | ||
| else throw error; | ||
| } | ||
| if (errors.length > 0) throw new AggregateError(errors, "FOO!"); | ||
| } | ||
| }; | ||
| const patchesRegistry = new PatchesRegistry(); | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| function getDeepPropertyDescriptor(owner, key) { | ||
| let currentOwner = owner; | ||
| let descriptor; | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key); | ||
| if (descriptor) return { | ||
| owner: currentOwner, | ||
| descriptor | ||
| }; | ||
| currentOwner = Object.getPrototypeOf(currentOwner); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/hasConfigurableGlobal.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given global property | ||
| * is defined and is configurable. | ||
| */ | ||
| function hasConfigurableGlobal(propertyName) { | ||
| const match = getDeepPropertyDescriptor(globalThis, propertyName); | ||
| if (typeof match === "undefined") return false; | ||
| const { descriptor } = match; | ||
| if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false; | ||
| if (typeof descriptor.get === "undefined" && descriptor.value == null) return false; | ||
| if (typeof descriptor.set === "undefined" && !descriptor.configurable) { | ||
| console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'emitAsync', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return emitAsync; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'hasConfigurableGlobal', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return hasConfigurableGlobal; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'patchesRegistry', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return patchesRegistry; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=hasConfigurableGlobal-BS75Oulv.cjs.map |
| {"version":3,"file":"hasConfigurableGlobal-BS75Oulv.cjs","names":["#replacements","errors: Array<Error>","currentOwner: Owner | null","descriptor: PropertyDescriptor | undefined"],"sources":["../../src/utils/emitAsync.ts","../../src/utils/patchesRegistry.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","import { invariant } from 'outvariant'\n\nclass PatchesRegistry {\n #replacements = new Map<object, Map<PropertyKey, () => void>>()\n\n public applyPatch<Owner extends object, K extends keyof Owner>(\n owner: Owner,\n key: K,\n getNextValue: (realValue: Owner[K]) => Owner[K]\n ): () => void {\n const ownerReplacements = this.#replacements.get(owner)\n\n invariant(\n !ownerReplacements?.has(key),\n `Failed to replace a global value at \"${String(key)}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(owner, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${String(key)}\": not a global value.`\n )\n return () => {}\n }\n\n Object.defineProperty(owner, key, {\n value: getNextValue(owner[key]),\n enumerable: true,\n configurable: true,\n })\n\n const restorePatch = () => {\n const currentReplacements = this.#replacements.get(owner)\n\n if (!currentReplacements?.has(key)) {\n return\n }\n\n if (match.owner === owner) {\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the match's owner isn't the original owner, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(owner, key)\n }\n\n currentReplacements.delete(key)\n\n if (currentReplacements.size === 0) {\n this.#replacements.delete(owner)\n }\n }\n\n if (ownerReplacements) {\n ownerReplacements.set(key, restorePatch)\n } else {\n this.#replacements.set(owner, new Map([[key, restorePatch]]))\n }\n\n return restorePatch\n }\n\n public restoreAllPatches(): void {\n const errors: Array<Error> = []\n\n for (const [, ownerReplacements] of this.#replacements) {\n for (const [, restorePatch] of ownerReplacements) {\n try {\n restorePatch()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const patchesRegistry = new PatchesRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: keyof Owner\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n","import { getDeepPropertyDescriptor } from './patchesRegistry'\n\n/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(\n propertyName: keyof typeof globalThis\n): boolean {\n const match = getDeepPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof match === 'undefined') {\n return false\n }\n\n const { descriptor } = match\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;;;;;;AAOA,eAAsB,UAIpB,SACA,WACA,GAAG,MACY;CACf,MAAM,YAAY,QAAQ,UAAU,UAAU;AAE9C,KAAI,UAAU,WAAW,EACvB;AAGF,MAAK,MAAM,YAAY,UACrB,OAAM,SAAS,MAAM,SAAS,KAAK;;;;;ACpBvC,IAAM,kBAAN,MAAsB;CACpB,gCAAgB,IAAI,KAA2C;CAE/D,AAAO,WACL,OACA,KACA,cACY;EACZ,MAAM,oBAAoB,MAAKA,aAAc,IAAI,MAAM;AAEvD,4BACE,CAAC,mBAAmB,IAAI,IAAI,EAC5B,wCAAwC,OAAO,IAAI,CAAC,sBACrD;EAED,MAAM,QAAQ,0BAA0B,OAAO,IAAI;AAEnD,MAAI,OAAO,UAAU,aAAa;AAChC,WAAQ,KACN,wCAAwC,OAAO,IAAI,CAAC,wBACrD;AACD,gBAAa;;AAGf,SAAO,eAAe,OAAO,KAAK;GAChC,OAAO,aAAa,MAAM,KAAK;GAC/B,YAAY;GACZ,cAAc;GACf,CAAC;EAEF,MAAM,qBAAqB;GACzB,MAAM,sBAAsB,MAAKA,aAAc,IAAI,MAAM;AAEzD,OAAI,CAAC,qBAAqB,IAAI,IAAI,CAChC;AAGF,OAAI,MAAM,UAAU,MAClB,QAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;;;;;;;AAOzD,WAAQ,eAAe,OAAO,IAAI;AAGpC,uBAAoB,OAAO,IAAI;AAE/B,OAAI,oBAAoB,SAAS,EAC/B,OAAKA,aAAc,OAAO,MAAM;;AAIpC,MAAI,kBACF,mBAAkB,IAAI,KAAK,aAAa;MAExC,OAAKA,aAAc,IAAI,OAAO,IAAI,IAAI,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC;AAG/D,SAAO;;CAGT,AAAO,oBAA0B;EAC/B,MAAMC,SAAuB,EAAE;AAE/B,OAAK,MAAM,GAAG,sBAAsB,MAAKD,aACvC,MAAK,MAAM,GAAG,iBAAiB,kBAC7B,KAAI;AACF,iBAAc;WACP,OAAO;AACd,OAAI,iBAAiB,MACnB,QAAO,KAAK,MAAM;OAElB,OAAM;;AAMd,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,QAAQ,OAAO;;;AAK9C,MAAa,kBAAkB,IAAI,iBAAiB;;;;;;;AAapD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAIE,eAA6B;CACjC,IAAIC;AAEJ,QAAO,cAAc;AACnB,eAAa,OAAO,yBAAyB,cAAc,IAAI;AAE/D,MAAI,WACF,QAAO;GACL,OAAO;GACP;GACD;AAGH,iBAAe,OAAO,eAAe,aAAa;;;;;;;;;;ACjHtD,SAAgB,sBACd,cACS;CACT,MAAM,QAAQ,0BAA0B,YAAY,aAAa;AAGjE,KAAI,OAAO,UAAU,YACnB,QAAO;CAGT,MAAM,EAAE,eAAe;AAGvB,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"} |
| import { invariant } from "outvariant"; | ||
| //#region src/utils/emitAsync.ts | ||
| /** | ||
| * Emits an event on the given emitter but executes | ||
| * the listeners sequentially. This accounts for asynchronous | ||
| * listeners (e.g. those having "sleep" and handling the request). | ||
| */ | ||
| async function emitAsync(emitter, eventName, ...data) { | ||
| const listeners = emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| for (const listener of listeners) await listener.apply(emitter, data); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/patchesRegistry.ts | ||
| var PatchesRegistry = class { | ||
| #replacements = /* @__PURE__ */ new Map(); | ||
| applyPatch(owner, key, getNextValue) { | ||
| const ownerReplacements = this.#replacements.get(owner); | ||
| invariant(!ownerReplacements?.has(key), `Failed to replace a global value at "${String(key)}": already replaced.`); | ||
| const match = getDeepPropertyDescriptor(owner, key); | ||
| if (typeof match === "undefined") { | ||
| console.warn(`Failed to replace a global value at "${String(key)}": not a global value.`); | ||
| return () => {}; | ||
| } | ||
| Object.defineProperty(owner, key, { | ||
| value: getNextValue(owner[key]), | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| const restorePatch = () => { | ||
| const currentReplacements = this.#replacements.get(owner); | ||
| if (!currentReplacements?.has(key)) return; | ||
| if (match.owner === owner) Object.defineProperty(match.owner, key, match.descriptor); | ||
| else | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the match's owner isn't the original owner, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(owner, key); | ||
| currentReplacements.delete(key); | ||
| if (currentReplacements.size === 0) this.#replacements.delete(owner); | ||
| }; | ||
| if (ownerReplacements) ownerReplacements.set(key, restorePatch); | ||
| else this.#replacements.set(owner, new Map([[key, restorePatch]])); | ||
| return restorePatch; | ||
| } | ||
| restoreAllPatches() { | ||
| const errors = []; | ||
| for (const [, ownerReplacements] of this.#replacements) for (const [, restorePatch] of ownerReplacements) try { | ||
| restorePatch(); | ||
| } catch (error) { | ||
| if (error instanceof Error) errors.push(error); | ||
| else throw error; | ||
| } | ||
| if (errors.length > 0) throw new AggregateError(errors, "FOO!"); | ||
| } | ||
| }; | ||
| const patchesRegistry = new PatchesRegistry(); | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| function getDeepPropertyDescriptor(owner, key) { | ||
| let currentOwner = owner; | ||
| let descriptor; | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key); | ||
| if (descriptor) return { | ||
| owner: currentOwner, | ||
| descriptor | ||
| }; | ||
| currentOwner = Object.getPrototypeOf(currentOwner); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/hasConfigurableGlobal.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given global property | ||
| * is defined and is configurable. | ||
| */ | ||
| function hasConfigurableGlobal(propertyName) { | ||
| const match = getDeepPropertyDescriptor(globalThis, propertyName); | ||
| if (typeof match === "undefined") return false; | ||
| const { descriptor } = match; | ||
| if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false; | ||
| if (typeof descriptor.get === "undefined" && descriptor.value == null) return false; | ||
| if (typeof descriptor.set === "undefined" && !descriptor.configurable) { | ||
| console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| //#endregion | ||
| export { patchesRegistry as n, emitAsync as r, hasConfigurableGlobal as t }; | ||
| //# sourceMappingURL=hasConfigurableGlobal-FTYwno1G.mjs.map |
| {"version":3,"file":"hasConfigurableGlobal-FTYwno1G.mjs","names":["#replacements","errors: Array<Error>","currentOwner: Owner | null","descriptor: PropertyDescriptor | undefined"],"sources":["../../src/utils/emitAsync.ts","../../src/utils/patchesRegistry.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","import { invariant } from 'outvariant'\n\nclass PatchesRegistry {\n #replacements = new Map<object, Map<PropertyKey, () => void>>()\n\n public applyPatch<Owner extends object, K extends keyof Owner>(\n owner: Owner,\n key: K,\n getNextValue: (realValue: Owner[K]) => Owner[K]\n ): () => void {\n const ownerReplacements = this.#replacements.get(owner)\n\n invariant(\n !ownerReplacements?.has(key),\n `Failed to replace a global value at \"${String(key)}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(owner, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${String(key)}\": not a global value.`\n )\n return () => {}\n }\n\n Object.defineProperty(owner, key, {\n value: getNextValue(owner[key]),\n enumerable: true,\n configurable: true,\n })\n\n const restorePatch = () => {\n const currentReplacements = this.#replacements.get(owner)\n\n if (!currentReplacements?.has(key)) {\n return\n }\n\n if (match.owner === owner) {\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the match's owner isn't the original owner, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(owner, key)\n }\n\n currentReplacements.delete(key)\n\n if (currentReplacements.size === 0) {\n this.#replacements.delete(owner)\n }\n }\n\n if (ownerReplacements) {\n ownerReplacements.set(key, restorePatch)\n } else {\n this.#replacements.set(owner, new Map([[key, restorePatch]]))\n }\n\n return restorePatch\n }\n\n public restoreAllPatches(): void {\n const errors: Array<Error> = []\n\n for (const [, ownerReplacements] of this.#replacements) {\n for (const [, restorePatch] of ownerReplacements) {\n try {\n restorePatch()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const patchesRegistry = new PatchesRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: keyof Owner\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n","import { getDeepPropertyDescriptor } from './patchesRegistry'\n\n/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(\n propertyName: keyof typeof globalThis\n): boolean {\n const match = getDeepPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof match === 'undefined') {\n return false\n }\n\n const { descriptor } = match\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;;;;;;AAOA,eAAsB,UAIpB,SACA,WACA,GAAG,MACY;CACf,MAAM,YAAY,QAAQ,UAAU,UAAU;AAE9C,KAAI,UAAU,WAAW,EACvB;AAGF,MAAK,MAAM,YAAY,UACrB,OAAM,SAAS,MAAM,SAAS,KAAK;;;;;ACpBvC,IAAM,kBAAN,MAAsB;CACpB,gCAAgB,IAAI,KAA2C;CAE/D,AAAO,WACL,OACA,KACA,cACY;EACZ,MAAM,oBAAoB,MAAKA,aAAc,IAAI,MAAM;AAEvD,YACE,CAAC,mBAAmB,IAAI,IAAI,EAC5B,wCAAwC,OAAO,IAAI,CAAC,sBACrD;EAED,MAAM,QAAQ,0BAA0B,OAAO,IAAI;AAEnD,MAAI,OAAO,UAAU,aAAa;AAChC,WAAQ,KACN,wCAAwC,OAAO,IAAI,CAAC,wBACrD;AACD,gBAAa;;AAGf,SAAO,eAAe,OAAO,KAAK;GAChC,OAAO,aAAa,MAAM,KAAK;GAC/B,YAAY;GACZ,cAAc;GACf,CAAC;EAEF,MAAM,qBAAqB;GACzB,MAAM,sBAAsB,MAAKA,aAAc,IAAI,MAAM;AAEzD,OAAI,CAAC,qBAAqB,IAAI,IAAI,CAChC;AAGF,OAAI,MAAM,UAAU,MAClB,QAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;;;;;;;AAOzD,WAAQ,eAAe,OAAO,IAAI;AAGpC,uBAAoB,OAAO,IAAI;AAE/B,OAAI,oBAAoB,SAAS,EAC/B,OAAKA,aAAc,OAAO,MAAM;;AAIpC,MAAI,kBACF,mBAAkB,IAAI,KAAK,aAAa;MAExC,OAAKA,aAAc,IAAI,OAAO,IAAI,IAAI,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC;AAG/D,SAAO;;CAGT,AAAO,oBAA0B;EAC/B,MAAMC,SAAuB,EAAE;AAE/B,OAAK,MAAM,GAAG,sBAAsB,MAAKD,aACvC,MAAK,MAAM,GAAG,iBAAiB,kBAC7B,KAAI;AACF,iBAAc;WACP,OAAO;AACd,OAAI,iBAAiB,MACnB,QAAO,KAAK,MAAM;OAElB,OAAM;;AAMd,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,QAAQ,OAAO;;;AAK9C,MAAa,kBAAkB,IAAI,iBAAiB;;;;;;;AAapD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAIE,eAA6B;CACjC,IAAIC;AAEJ,QAAO,cAAc;AACnB,eAAa,OAAO,yBAAyB,cAAc,IAAI;AAE/D,MAAI,WACF,QAAO;GACL,OAAO;GACP;GACD;AAGH,iBAAe,OAAO,eAAe,aAAa;;;;;;;;;;ACjHtD,SAAgB,sBACd,cACS;CACT,MAAM,QAAQ,0BAA0B,YAAY,aAAa;AAGjE,KAAI,OAAO,UAAU,YACnB,QAAO;CAGT,MAAM,EAAE,eAAe;AAGvB,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"} |
| const require_createRequestId = require('./createRequestId-DOf8Ktjs.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-DdfaiPVH.cjs'); | ||
| const require_bufferUtils = require('./bufferUtils-Uc0eRItL.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-BS75Oulv.cjs'); | ||
| const require_handleRequest = require('./handleRequest-tUNU616J.cjs'); | ||
| let outvariant = require("outvariant"); | ||
| let is_node_process = require("is-node-process"); | ||
| //#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts | ||
| /** | ||
| * Concatenate two `Uint8Array` buffers. | ||
| */ | ||
| function concatArrayBuffer(left, right) { | ||
| const result = new Uint8Array(left.byteLength + right.byteLength); | ||
| result.set(left, 0); | ||
| result.set(right, left.byteLength); | ||
| return result; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts | ||
| var EventPolyfill = class { | ||
| constructor(type, options) { | ||
| this.NONE = 0; | ||
| this.CAPTURING_PHASE = 1; | ||
| this.AT_TARGET = 2; | ||
| this.BUBBLING_PHASE = 3; | ||
| this.type = ""; | ||
| this.srcElement = null; | ||
| this.currentTarget = null; | ||
| this.eventPhase = 0; | ||
| this.isTrusted = true; | ||
| this.composed = false; | ||
| this.cancelable = true; | ||
| this.defaultPrevented = false; | ||
| this.bubbles = true; | ||
| this.lengthComputable = true; | ||
| this.loaded = 0; | ||
| this.total = 0; | ||
| this.cancelBubble = false; | ||
| this.returnValue = true; | ||
| this.type = type; | ||
| this.target = options?.target || null; | ||
| this.currentTarget = options?.currentTarget || null; | ||
| this.timeStamp = Date.now(); | ||
| } | ||
| composedPath() { | ||
| return []; | ||
| } | ||
| initEvent(type, bubbles, cancelable) { | ||
| this.type = type; | ||
| this.bubbles = !!bubbles; | ||
| this.cancelable = !!cancelable; | ||
| } | ||
| preventDefault() { | ||
| this.defaultPrevented = true; | ||
| } | ||
| stopPropagation() {} | ||
| stopImmediatePropagation() {} | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts | ||
| var ProgressEventPolyfill = class extends EventPolyfill { | ||
| constructor(type, init) { | ||
| super(type); | ||
| this.lengthComputable = init?.lengthComputable || false; | ||
| this.composed = init?.composed || false; | ||
| this.loaded = init?.loaded || 0; | ||
| this.total = init?.total || 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createEvent.ts | ||
| const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined"; | ||
| function createEvent(target, type, init) { | ||
| const progressEvents = [ | ||
| "error", | ||
| "progress", | ||
| "loadstart", | ||
| "loadend", | ||
| "load", | ||
| "timeout", | ||
| "abort" | ||
| ]; | ||
| /** | ||
| * `ProgressEvent` is not supported in React Native. | ||
| * @see https://github.com/mswjs/interceptors/issues/40 | ||
| */ | ||
| const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill; | ||
| return progressEvents.includes(type) ? new ProgressEventClass(type, { | ||
| lengthComputable: true, | ||
| loaded: init?.loaded || 0, | ||
| total: init?.total || 0 | ||
| }) : new EventPolyfill(type, { | ||
| target, | ||
| currentTarget: target | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/findPropertySource.ts | ||
| /** | ||
| * Returns the source object of the given property on the target object | ||
| * (the target itself, any parent in its prototype, or null). | ||
| */ | ||
| function findPropertySource(target, propertyName) { | ||
| if (!(propertyName in target)) return null; | ||
| if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target; | ||
| const prototype = Reflect.getPrototypeOf(target); | ||
| return prototype ? findPropertySource(prototype, propertyName) : null; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/createProxy.ts | ||
| function createProxy(target, options) { | ||
| return new Proxy(target, optionsToProxyHandler(options)); | ||
| } | ||
| function optionsToProxyHandler(options) { | ||
| const { constructorCall, methodCall, getProperty, setProperty } = options; | ||
| const handler = {}; | ||
| if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) { | ||
| const next = Reflect.construct.bind(null, target, args, newTarget); | ||
| return constructorCall.call(newTarget, args, next); | ||
| }; | ||
| handler.set = function(target, propertyName, nextValue) { | ||
| const next = () => { | ||
| const propertySource = findPropertySource(target, propertyName) || target; | ||
| const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName); | ||
| if (typeof ownDescriptors?.set !== "undefined") { | ||
| ownDescriptors.set.apply(target, [nextValue]); | ||
| return true; | ||
| } | ||
| return Reflect.defineProperty(propertySource, propertyName, { | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| value: nextValue | ||
| }); | ||
| }; | ||
| if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next); | ||
| return next(); | ||
| }; | ||
| handler.get = function(target, propertyName, receiver) { | ||
| /** | ||
| * @note Using `Reflect.get()` here causes "TypeError: Illegal invocation". | ||
| */ | ||
| const next = () => target[propertyName]; | ||
| const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next(); | ||
| if (typeof value === "function") return (...args) => { | ||
| const next$1 = value.bind(target, ...args); | ||
| if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1); | ||
| return next$1(); | ||
| }; | ||
| return value; | ||
| }; | ||
| return handler; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts | ||
| function isDomParserSupportedType(type) { | ||
| return [ | ||
| "application/xhtml+xml", | ||
| "application/xml", | ||
| "image/svg+xml", | ||
| "text/html", | ||
| "text/xml" | ||
| ].some((supportedType) => { | ||
| return type.startsWith(supportedType); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/parseJson.ts | ||
| /** | ||
| * Parses a given string into JSON. | ||
| * Gracefully handles invalid JSON by returning `null`. | ||
| */ | ||
| function parseJson(data) { | ||
| try { | ||
| return JSON.parse(data); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createResponse.ts | ||
| /** | ||
| * Creates a Fetch API `Response` instance from the given | ||
| * `XMLHttpRequest` instance and a response body. | ||
| */ | ||
| function createResponse(request, body) { | ||
| return new require_getRawRequest.FetchResponse(require_getRawRequest.FetchResponse.isResponseWithBody(request.status) ? body : null, { | ||
| url: request.responseURL, | ||
| status: request.status, | ||
| statusText: request.statusText, | ||
| headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders()) | ||
| }); | ||
| } | ||
| function createHeadersFromXMLHttpRequestHeaders(headersString) { | ||
| const headers = new Headers(); | ||
| const lines = headersString.split(/[\r\n]+/); | ||
| for (const line of lines) { | ||
| if (line.trim() === "") continue; | ||
| const [name, ...parts] = line.split(": "); | ||
| const value = parts.join(": "); | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts | ||
| /** | ||
| * Return a total byte length of the given request/response body. | ||
| * If the `Content-Length` header is present, it will be used as the byte length. | ||
| */ | ||
| async function getBodyByteLength(input) { | ||
| const explicitContentLength = input.headers.get("content-length"); | ||
| if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength); | ||
| return (await input.arrayBuffer()).byteLength; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts | ||
| const kIsRequestHandled = Symbol("kIsRequestHandled"); | ||
| const IS_NODE = (0, is_node_process.isNodeProcess)(); | ||
| const kFetchRequest = Symbol("kFetchRequest"); | ||
| /** | ||
| * An `XMLHttpRequest` instance controller that allows us | ||
| * to handle any given request instance (e.g. responding to it). | ||
| */ | ||
| var XMLHttpRequestController = class { | ||
| constructor(initialRequest, logger) { | ||
| this.initialRequest = initialRequest; | ||
| this.logger = logger; | ||
| this.method = "GET"; | ||
| this.url = null; | ||
| this[kIsRequestHandled] = false; | ||
| this.events = /* @__PURE__ */ new Map(); | ||
| this.uploadEvents = /* @__PURE__ */ new Map(); | ||
| this.requestId = require_createRequestId.createRequestId(); | ||
| this.requestHeaders = new Headers(); | ||
| this.responseBuffer = new Uint8Array(); | ||
| this.request = createProxy(initialRequest, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "ontimeout": { | ||
| const eventName = propertyName.slice(2); | ||
| /** | ||
| * @note Proxy callbacks to event listeners because JSDOM has trouble | ||
| * translating these properties to callbacks. It seemed to be operating | ||
| * on events exclusively. | ||
| */ | ||
| this.request.addEventListener(eventName, nextValue); | ||
| return invoke(); | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "open": { | ||
| const [method, url] = args; | ||
| if (typeof url === "undefined") { | ||
| this.method = "GET"; | ||
| this.url = toAbsoluteUrl(method); | ||
| } else { | ||
| this.method = method; | ||
| this.url = toAbsoluteUrl(url); | ||
| } | ||
| this.logger = this.logger.extend(`${this.method} ${this.url.href}`); | ||
| this.logger.info("open", this.method, this.url.href); | ||
| return invoke(); | ||
| } | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerEvent(eventName, listener); | ||
| this.logger.info("addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| case "setRequestHeader": { | ||
| const [name, value] = args; | ||
| this.requestHeaders.set(name, value); | ||
| this.logger.info("setRequestHeader", name, value); | ||
| return invoke(); | ||
| } | ||
| case "send": { | ||
| const [body] = args; | ||
| this.request.addEventListener("load", () => { | ||
| if (typeof this.onResponse !== "undefined") { | ||
| const fetchResponse = createResponse( | ||
| this.request, | ||
| /** | ||
| * The `response` property is the right way to read | ||
| * the ambiguous response body, as the request's "responseType" may differ. | ||
| * @see https://xhr.spec.whatwg.org/#the-response-attribute | ||
| */ | ||
| this.request.response | ||
| ); | ||
| this.onResponse.call(this, { | ||
| response: fetchResponse, | ||
| isMockedResponse: this[kIsRequestHandled], | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }); | ||
| } | ||
| }); | ||
| const requestBody = typeof body === "string" ? require_bufferUtils.encodeBuffer(body) : body; | ||
| const fetchRequest = this.toFetchApiRequest(requestBody); | ||
| this[kFetchRequest] = fetchRequest.clone(); | ||
| /** | ||
| * @note Start request handling on the next tick so that the user | ||
| * could add event listeners for "loadend" before the interceptor fires it. | ||
| */ | ||
| queueMicrotask(() => { | ||
| (this.onRequest?.call(this, { | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }) || Promise.resolve()).finally(() => { | ||
| if (!this[kIsRequestHandled]) { | ||
| this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState); | ||
| /** | ||
| * @note Set the intercepted request ID on the original request in Node.js | ||
| * so that if it triggers any other interceptors, they don't attempt | ||
| * to process it once again. | ||
| * | ||
| * For instance, XMLHttpRequest is often implemented via "http.ClientRequest" | ||
| * and we don't want for both XHR and ClientRequest interceptors to | ||
| * handle the same request at the same time (e.g. emit the "response" event twice). | ||
| */ | ||
| if (IS_NODE) this.request.setRequestHeader(require_createRequestId.INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId); | ||
| return invoke(); | ||
| } | ||
| }); | ||
| }); | ||
| break; | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| } | ||
| }); | ||
| /** | ||
| * Proxy the `.upload` property to gather the event listeners/callbacks. | ||
| */ | ||
| define(this.request, "upload", createProxy(this.request.upload, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "onloadstart": | ||
| case "onprogress": | ||
| case "onaboart": | ||
| case "onerror": | ||
| case "onload": | ||
| case "ontimeout": | ||
| case "onloadend": { | ||
| const eventName = propertyName.slice(2); | ||
| this.registerUploadEvent(eventName, nextValue); | ||
| } | ||
| } | ||
| return invoke(); | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerUploadEvent(eventName, listener); | ||
| this.logger.info("upload.addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| } | ||
| } | ||
| })); | ||
| } | ||
| registerEvent(eventName, listener) { | ||
| const nextEvents = (this.events.get(eventName) || []).concat(listener); | ||
| this.events.set(eventName, nextEvents); | ||
| this.logger.info("registered event \"%s\"", eventName, listener); | ||
| } | ||
| registerUploadEvent(eventName, listener) { | ||
| const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener); | ||
| this.uploadEvents.set(eventName, nextEvents); | ||
| this.logger.info("registered upload event \"%s\"", eventName, listener); | ||
| } | ||
| /** | ||
| * Responds to the current request with the given | ||
| * Fetch API `Response` instance. | ||
| */ | ||
| async respondWith(response) { | ||
| /** | ||
| * @note Since `XMLHttpRequestController` delegates the handling of the responses | ||
| * to the "load" event listener that doesn't distinguish between the mocked and original | ||
| * responses, mark the request that had a mocked response with a corresponding symbol. | ||
| * | ||
| * Mark this request as having a mocked response immediately since | ||
| * calculating request/response total body length is asynchronous. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| /** | ||
| * Dispatch request upload events for requests with a body. | ||
| * @see https://github.com/mswjs/interceptors/issues/573 | ||
| */ | ||
| if (this[kFetchRequest]) { | ||
| const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]); | ||
| this.trigger("loadstart", this.request.upload, { | ||
| loaded: 0, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("progress", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("load", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| } | ||
| this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText); | ||
| define(this.request, "status", response.status); | ||
| define(this.request, "statusText", response.statusText); | ||
| define(this.request, "responseURL", this.url.href); | ||
| this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => { | ||
| this.logger.info("getResponseHeader", args[0]); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning null"); | ||
| return null; | ||
| } | ||
| const headerValue = response.headers.get(args[0]); | ||
| this.logger.info("resolved response header \"%s\" to", args[0], headerValue); | ||
| return headerValue; | ||
| } }); | ||
| this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => { | ||
| this.logger.info("getAllResponseHeaders"); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning empty string"); | ||
| return ""; | ||
| } | ||
| const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => { | ||
| return `${headerName}: ${headerValue}`; | ||
| }).join("\r\n"); | ||
| this.logger.info("resolved all response headers to", allHeaders); | ||
| return allHeaders; | ||
| } }); | ||
| Object.defineProperties(this.request, { | ||
| response: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.response | ||
| }, | ||
| responseText: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseText | ||
| }, | ||
| responseXML: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseXML | ||
| } | ||
| }); | ||
| const totalResponseBodyLength = await getBodyByteLength(response.clone()); | ||
| this.logger.info("calculated response body length", totalResponseBodyLength); | ||
| this.trigger("loadstart", this.request, { | ||
| loaded: 0, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.setReadyState(this.request.HEADERS_RECEIVED); | ||
| this.setReadyState(this.request.LOADING); | ||
| const finalizeResponse = () => { | ||
| this.logger.info("finalizing the mocked response..."); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("load", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| }; | ||
| if (response.body) { | ||
| this.logger.info("mocked response has body, streaming..."); | ||
| const reader = response.body.getReader(); | ||
| const readNextResponseBodyChunk = async () => { | ||
| const { value, done } = await reader.read(); | ||
| if (done) { | ||
| this.logger.info("response body stream done!"); | ||
| finalizeResponse(); | ||
| return; | ||
| } | ||
| if (value) { | ||
| this.logger.info("read response body chunk:", value); | ||
| this.responseBuffer = concatArrayBuffer(this.responseBuffer, value); | ||
| this.trigger("progress", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| } | ||
| readNextResponseBodyChunk(); | ||
| }; | ||
| readNextResponseBodyChunk(); | ||
| } else finalizeResponse(); | ||
| } | ||
| responseBufferToText() { | ||
| return require_bufferUtils.decodeBuffer(this.responseBuffer); | ||
| } | ||
| get response() { | ||
| this.logger.info("getResponse (responseType: %s)", this.request.responseType); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| switch (this.request.responseType) { | ||
| case "json": { | ||
| const responseJson = parseJson(this.responseBufferToText()); | ||
| this.logger.info("resolved response JSON", responseJson); | ||
| return responseJson; | ||
| } | ||
| case "arraybuffer": { | ||
| const arrayBuffer = require_bufferUtils.toArrayBuffer(this.responseBuffer); | ||
| this.logger.info("resolved response ArrayBuffer", arrayBuffer); | ||
| return arrayBuffer; | ||
| } | ||
| case "blob": { | ||
| const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain"; | ||
| const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType }); | ||
| this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType); | ||
| return responseBlob; | ||
| } | ||
| default: { | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText); | ||
| return responseText; | ||
| } | ||
| } | ||
| } | ||
| get responseText() { | ||
| /** | ||
| * Throw when trying to read the response body as text when the | ||
| * "responseType" doesn't expect text. This just respects the spec better. | ||
| * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute | ||
| */ | ||
| (0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return ""; | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("getResponseText: \"%s\"", responseText); | ||
| return responseText; | ||
| } | ||
| get responseXML() { | ||
| (0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| const contentType = this.request.getResponseHeader("Content-Type") || ""; | ||
| if (typeof DOMParser === "undefined") { | ||
| console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly."); | ||
| return null; | ||
| } | ||
| if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType); | ||
| return null; | ||
| } | ||
| errorWith(error) { | ||
| /** | ||
| * @note Mark this request as handled even if it received a mock error. | ||
| * This prevents the controller from trying to perform this request as-is. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| this.logger.info("responding with an error"); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("error", this.request); | ||
| this.trigger("loadend", this.request); | ||
| } | ||
| /** | ||
| * Transitions this request's `readyState` to the given one. | ||
| */ | ||
| setReadyState(nextReadyState) { | ||
| this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState); | ||
| if (this.request.readyState === nextReadyState) { | ||
| this.logger.info("ready state identical, skipping transition..."); | ||
| return; | ||
| } | ||
| define(this.request, "readyState", nextReadyState); | ||
| this.logger.info("set readyState to: %d", nextReadyState); | ||
| if (nextReadyState !== this.request.UNSENT) { | ||
| this.logger.info("triggering \"readystatechange\" event..."); | ||
| this.trigger("readystatechange", this.request); | ||
| } | ||
| } | ||
| /** | ||
| * Triggers given event on the `XMLHttpRequest` instance. | ||
| */ | ||
| trigger(eventName, target, options) { | ||
| const callback = target[`on${eventName}`]; | ||
| const event = createEvent(target, eventName, options); | ||
| this.logger.info("trigger \"%s\"", eventName, options || ""); | ||
| if (typeof callback === "function") { | ||
| this.logger.info("found a direct \"%s\" callback, calling...", eventName); | ||
| callback.call(target, event); | ||
| } | ||
| const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events; | ||
| for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) { | ||
| this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName); | ||
| listeners.forEach((listener) => listener.call(target, event)); | ||
| } | ||
| } | ||
| /** | ||
| * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance. | ||
| */ | ||
| toFetchApiRequest(body) { | ||
| this.logger.info("converting request to a Fetch API Request..."); | ||
| const resolvedBody = body instanceof Document ? body.documentElement.innerText : body; | ||
| const fetchRequest = new require_getRawRequest.FetchRequest(this.url.href, { | ||
| method: this.method, | ||
| headers: this.requestHeaders, | ||
| credentials: this.request.withCredentials ? "include" : "same-origin", | ||
| body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody | ||
| }); | ||
| define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "append": | ||
| case "set": { | ||
| const [headerName, headerValue] = args; | ||
| this.request.setRequestHeader(headerName, headerValue); | ||
| break; | ||
| } | ||
| case "delete": { | ||
| const [headerName] = args; | ||
| console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`); | ||
| break; | ||
| } | ||
| } | ||
| return invoke(); | ||
| } })); | ||
| require_getRawRequest.setRawRequest(fetchRequest, this.request); | ||
| this.logger.info("converted request to a Fetch API Request!", fetchRequest); | ||
| return fetchRequest; | ||
| } | ||
| }; | ||
| function toAbsoluteUrl(url) { | ||
| /** | ||
| * @note XMLHttpRequest interceptor may run in environments | ||
| * that implement XMLHttpRequest but don't implement "location" | ||
| * (for example, React Native). If that's the case, return the | ||
| * input URL as-is (nothing to be relative to). | ||
| * @see https://github.com/mswjs/msw/issues/1777 | ||
| */ | ||
| if (typeof location === "undefined") return new URL(url); | ||
| return new URL(url.toString(), location.href); | ||
| } | ||
| function define(target, property, value) { | ||
| Reflect.defineProperty(target, property, { | ||
| writable: true, | ||
| enumerable: true, | ||
| value | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts | ||
| /** | ||
| * Create a proxied `XMLHttpRequest` class. | ||
| * The proxied class establishes spies on certain methods, | ||
| * allowing us to intercept requests and respond to them. | ||
| */ | ||
| function createXMLHttpRequestProxy({ emitter, logger }) { | ||
| return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) { | ||
| logger.info("constructed new XMLHttpRequest"); | ||
| const originalRequest = Reflect.construct(target, args, newTarget); | ||
| /** | ||
| * @note Forward prototype descriptors onto the proxied object. | ||
| * XMLHttpRequest is implemented in JSDOM in a way that assigns | ||
| * a bunch of descriptors, like "set responseType()" on the prototype. | ||
| * With this propagation, we make sure that those descriptors trigger | ||
| * when the user operates with the proxied request instance. | ||
| */ | ||
| const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype); | ||
| for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]); | ||
| const xhrRequestController = new XMLHttpRequestController(originalRequest, logger); | ||
| xhrRequestController.onRequest = async function({ request, requestId }) { | ||
| const controller = new require_getRawRequest.RequestController(request, { | ||
| passthrough: () => { | ||
| this.logger.info("no mocked response received, performing request as-is..."); | ||
| }, | ||
| respondWith: async (response) => { | ||
| if (require_handleRequest.isResponseError(response)) { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| await this.respondWith(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request errored!", { error: reason }); | ||
| if (reason instanceof Error) this.errorWith(reason); | ||
| } | ||
| }); | ||
| this.logger.info("awaiting mocked response..."); | ||
| this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request")); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId, | ||
| controller, | ||
| emitter | ||
| }); | ||
| }; | ||
| xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) { | ||
| this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response")); | ||
| emitter.emit("response", { | ||
| response, | ||
| isMockedResponse, | ||
| request, | ||
| requestId | ||
| }); | ||
| }; | ||
| return xhrRequestController.request; | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/index.ts | ||
| var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends require_createRequestId.Interceptor { | ||
| static { | ||
| this.interceptorSymbol = Symbol("xhr"); | ||
| } | ||
| constructor() { | ||
| super(XMLHttpRequestInterceptor.interceptorSymbol); | ||
| } | ||
| checkEnvironment() { | ||
| return require_hasConfigurableGlobal.hasConfigurableGlobal("XMLHttpRequest"); | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("patching global XMLHttpRequest..."); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.patchesRegistry.applyPatch(globalThis, "XMLHttpRequest", () => { | ||
| return createXMLHttpRequestProxy({ | ||
| emitter: this.emitter, | ||
| logger: this.logger | ||
| }); | ||
| })); | ||
| logger.info("global XMLHttpRequest patched!", globalThis.XMLHttpRequest.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'XMLHttpRequestInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return XMLHttpRequestInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=XMLHttpRequest-CHqOQWxg.cjs.map |
| {"version":3,"file":"XMLHttpRequest-CHqOQWxg.cjs","names":["handler: ProxyHandler<T>","next","FetchResponse","initialRequest: XMLHttpRequest","logger: Logger","createRequestId","encodeBuffer","INTERNAL_REQUEST_ID_HEADER_NAME","decodeBuffer","toArrayBuffer","FetchRequest","RequestController","isResponseError","handleRequest","Interceptor","hasConfigurableGlobal","patchesRegistry"],"sources":["../../src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts","../../src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts","../../src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts","../../src/interceptors/XMLHttpRequest/utils/createEvent.ts","../../src/utils/findPropertySource.ts","../../src/utils/createProxy.ts","../../src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts","../../src/utils/parseJson.ts","../../src/interceptors/XMLHttpRequest/utils/createResponse.ts","../../src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts","../../src/interceptors/XMLHttpRequest/index.ts"],"sourcesContent":["/**\n * Concatenate two `Uint8Array` buffers.\n */\nexport function concatArrayBuffer(\n left: Uint8Array,\n right: Uint8Array\n): Uint8Array {\n const result = new Uint8Array(left.byteLength + right.byteLength)\n result.set(left, 0)\n result.set(right, left.byteLength)\n return result\n}\n","export class EventPolyfill implements Event {\n readonly NONE = 0\n readonly CAPTURING_PHASE = 1\n readonly AT_TARGET = 2\n readonly BUBBLING_PHASE = 3\n\n public type: string = ''\n public srcElement: EventTarget | null = null\n public target: EventTarget | null\n public currentTarget: EventTarget | null = null\n public eventPhase: number = 0\n public timeStamp: number\n public isTrusted: boolean = true\n public composed: boolean = false\n public cancelable: boolean = true\n public defaultPrevented: boolean = false\n public bubbles: boolean = true\n public lengthComputable: boolean = true\n public loaded: number = 0\n public total: number = 0\n\n cancelBubble: boolean = false\n returnValue: boolean = true\n\n constructor(\n type: string,\n options?: { target: EventTarget; currentTarget: EventTarget }\n ) {\n this.type = type\n this.target = options?.target || null\n this.currentTarget = options?.currentTarget || null\n this.timeStamp = Date.now()\n }\n\n public composedPath(): EventTarget[] {\n return []\n }\n\n public initEvent(type: string, bubbles?: boolean, cancelable?: boolean) {\n this.type = type\n this.bubbles = !!bubbles\n this.cancelable = !!cancelable\n }\n\n public preventDefault() {\n this.defaultPrevented = true\n }\n\n public stopPropagation() {}\n public stopImmediatePropagation() {}\n}\n","import { EventPolyfill } from './EventPolyfill'\n\nexport class ProgressEventPolyfill extends EventPolyfill {\n readonly lengthComputable: boolean\n readonly composed: boolean\n readonly loaded: number\n readonly total: number\n\n constructor(type: string, init?: ProgressEventInit) {\n super(type)\n\n this.lengthComputable = init?.lengthComputable || false\n this.composed = init?.composed || false\n this.loaded = init?.loaded || 0\n this.total = init?.total || 0\n }\n}\n","import { EventPolyfill } from '../polyfills/EventPolyfill'\nimport { ProgressEventPolyfill } from '../polyfills/ProgressEventPolyfill'\n\nconst SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined'\n\nexport function createEvent(\n target: XMLHttpRequest | XMLHttpRequestUpload,\n type: string,\n init?: ProgressEventInit\n): EventPolyfill | ProgressEvent {\n const progressEvents = [\n 'error',\n 'progress',\n 'loadstart',\n 'loadend',\n 'load',\n 'timeout',\n 'abort',\n ]\n\n /**\n * `ProgressEvent` is not supported in React Native.\n * @see https://github.com/mswjs/interceptors/issues/40\n */\n const ProgressEventClass = SUPPORTS_PROGRESS_EVENT\n ? ProgressEvent\n : ProgressEventPolyfill\n\n const event = progressEvents.includes(type)\n ? new ProgressEventClass(type, {\n lengthComputable: true,\n loaded: init?.loaded || 0,\n total: init?.total || 0,\n })\n : new EventPolyfill(type, {\n target,\n currentTarget: target,\n })\n\n return event\n}\n","/**\n * Returns the source object of the given property on the target object\n * (the target itself, any parent in its prototype, or null).\n */\nexport function findPropertySource(\n target: object,\n propertyName: string | symbol\n): object | null {\n if (!(propertyName in target)) {\n return null\n }\n\n const hasProperty = Object.prototype.hasOwnProperty.call(target, propertyName)\n if (hasProperty) {\n return target\n }\n\n const prototype = Reflect.getPrototypeOf(target)\n return prototype ? findPropertySource(prototype, propertyName) : null\n}\n","import { findPropertySource } from './findPropertySource'\n\nexport interface ProxyOptions<Target extends Record<string, any>> {\n constructorCall?(args: Array<unknown>, next: NextFunction<Target>): Target\n\n methodCall?<F extends keyof Target>(\n this: Target,\n data: [methodName: F, args: Array<unknown>],\n next: NextFunction<void>\n ): void\n\n setProperty?(\n data: [propertyName: string | symbol, nextValue: unknown],\n next: NextFunction<boolean>\n ): boolean\n\n getProperty?(\n data: [propertyName: string | symbol, receiver: Target],\n next: NextFunction<void>\n ): void\n}\n\nexport type NextFunction<ReturnType> = () => ReturnType\n\nexport function createProxy<Target extends object>(\n target: Target,\n options: ProxyOptions<Target>\n): Target {\n const proxy = new Proxy(target, optionsToProxyHandler(options))\n\n return proxy\n}\n\nfunction optionsToProxyHandler<T extends Record<string, any>>(\n options: ProxyOptions<T>\n): ProxyHandler<T> {\n const { constructorCall, methodCall, getProperty, setProperty } = options\n const handler: ProxyHandler<T> = {}\n\n if (typeof constructorCall !== 'undefined') {\n handler.construct = function (target, args, newTarget) {\n const next = Reflect.construct.bind(null, target as any, args, newTarget)\n return constructorCall.call(newTarget, args, next)\n }\n }\n\n handler.set = function (target, propertyName, nextValue) {\n const next = () => {\n const propertySource = findPropertySource(target, propertyName) || target\n const ownDescriptors = Reflect.getOwnPropertyDescriptor(\n propertySource,\n propertyName\n )\n\n // Respect any custom setters present for this property.\n if (typeof ownDescriptors?.set !== 'undefined') {\n ownDescriptors.set.apply(target, [nextValue])\n return true\n }\n\n // Otherwise, set the property on the source.\n return Reflect.defineProperty(propertySource, propertyName, {\n writable: true,\n enumerable: true,\n configurable: true,\n value: nextValue,\n })\n }\n\n if (typeof setProperty !== 'undefined') {\n return setProperty.call(target, [propertyName, nextValue], next)\n }\n\n return next()\n }\n\n handler.get = function (target, propertyName, receiver) {\n /**\n * @note Using `Reflect.get()` here causes \"TypeError: Illegal invocation\".\n */\n const next = () => target[propertyName as any]\n\n const value =\n typeof getProperty !== 'undefined'\n ? getProperty.call(target, [propertyName, receiver], next)\n : next()\n\n if (typeof value === 'function') {\n return (...args: Array<any>) => {\n const next = value.bind(target, ...args)\n\n if (typeof methodCall !== 'undefined') {\n return methodCall.call(target, [propertyName as any, args], next)\n }\n\n return next()\n }\n }\n\n return value\n }\n\n return handler\n}\n","export function isDomParserSupportedType(\n type: string\n): type is DOMParserSupportedType {\n const supportedTypes: Array<DOMParserSupportedType> = [\n 'application/xhtml+xml',\n 'application/xml',\n 'image/svg+xml',\n 'text/html',\n 'text/xml',\n ]\n return supportedTypes.some((supportedType) => {\n return type.startsWith(supportedType)\n })\n}\n","/**\n * Parses a given string into JSON.\n * Gracefully handles invalid JSON by returning `null`.\n */\nexport function parseJson(data: string): Record<string, unknown> | null {\n try {\n const json = JSON.parse(data)\n return json\n } catch (_) {\n return null\n }\n}\n","import { FetchResponse } from '../../../utils/fetchUtils'\n\n/**\n * Creates a Fetch API `Response` instance from the given\n * `XMLHttpRequest` instance and a response body.\n */\nexport function createResponse(\n request: XMLHttpRequest,\n body: BodyInit | null\n): Response {\n /**\n * Handle XMLHttpRequest responses that must have null as the\n * response body when represented using Fetch API Response.\n * XMLHttpRequest response will always have an empty string\n * as the \"request.response\" in those cases, resulting in an error\n * when constructing a Response instance.\n * @see https://github.com/mswjs/interceptors/issues/379\n */\n const responseBodyOrNull = FetchResponse.isResponseWithBody(request.status)\n ? body\n : null\n\n return new FetchResponse(responseBodyOrNull, {\n url: request.responseURL,\n status: request.status,\n statusText: request.statusText,\n headers: createHeadersFromXMLHttpRequestHeaders(\n request.getAllResponseHeaders()\n ),\n })\n}\n\nfunction createHeadersFromXMLHttpRequestHeaders(headersString: string): Headers {\n const headers = new Headers()\n\n const lines = headersString.split(/[\\r\\n]+/)\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n const [name, ...parts] = line.split(': ')\n const value = parts.join(': ')\n\n headers.append(name, value)\n }\n\n return headers\n}\n","/**\n * Return a total byte length of the given request/response body.\n * If the `Content-Length` header is present, it will be used as the byte length.\n */\nexport async function getBodyByteLength(\n input: Request | Response\n): Promise<number> {\n const explicitContentLength = input.headers.get('content-length')\n\n if (explicitContentLength != null && explicitContentLength !== '') {\n return Number(explicitContentLength)\n }\n\n const buffer = await input.arrayBuffer()\n return buffer.byteLength\n}\n","import { invariant } from 'outvariant'\nimport { isNodeProcess } from 'is-node-process'\nimport type { Logger } from '@open-draft/logger'\nimport { concatArrayBuffer } from './utils/concatArrayBuffer'\nimport { createEvent } from './utils/createEvent'\nimport {\n decodeBuffer,\n encodeBuffer,\n toArrayBuffer,\n} from '../../utils/bufferUtils'\nimport { createProxy } from '../../utils/createProxy'\nimport { isDomParserSupportedType } from './utils/isDomParserSupportedType'\nimport { parseJson } from '../../utils/parseJson'\nimport { createResponse } from './utils/createResponse'\nimport { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'\nimport { createRequestId } from '../../createRequestId'\nimport { getBodyByteLength } from './utils/getBodyByteLength'\nimport { setRawRequest } from '../../getRawRequest'\nimport { FetchRequest } from '../../utils/fetchUtils'\n\nconst kIsRequestHandled = Symbol('kIsRequestHandled')\nconst IS_NODE = isNodeProcess()\nconst kFetchRequest = Symbol('kFetchRequest')\n\n/**\n * An `XMLHttpRequest` instance controller that allows us\n * to handle any given request instance (e.g. responding to it).\n */\nexport class XMLHttpRequestController {\n public request: XMLHttpRequest\n public requestId: string\n public onRequest?: (\n this: XMLHttpRequestController,\n args: {\n request: Request\n requestId: string\n }\n ) => Promise<void>\n public onResponse?: (\n this: XMLHttpRequestController,\n args: {\n response: Response\n isMockedResponse: boolean\n request: Request\n requestId: string\n }\n ) => void;\n\n [kIsRequestHandled]: boolean;\n [kFetchRequest]?: Request\n private method: string = 'GET'\n private url: URL = null as any\n private requestHeaders: Headers\n private responseBuffer: Uint8Array\n private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>\n private uploadEvents: Map<\n keyof XMLHttpRequestEventTargetEventMap,\n Array<Function>\n >\n\n constructor(\n readonly initialRequest: XMLHttpRequest,\n public logger: Logger\n ) {\n this[kIsRequestHandled] = false\n\n this.events = new Map()\n this.uploadEvents = new Map()\n this.requestId = createRequestId()\n this.requestHeaders = new Headers()\n this.responseBuffer = new Uint8Array()\n\n this.request = createProxy(initialRequest, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'ontimeout': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n /**\n * @note Proxy callbacks to event listeners because JSDOM has trouble\n * translating these properties to callbacks. It seemed to be operating\n * on events exclusively.\n */\n this.request.addEventListener(eventName, nextValue as any)\n\n return invoke()\n }\n\n default: {\n return invoke()\n }\n }\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'open': {\n const [method, url] = args as [string, string | undefined]\n\n if (typeof url === 'undefined') {\n this.method = 'GET'\n this.url = toAbsoluteUrl(method)\n } else {\n this.method = method\n this.url = toAbsoluteUrl(url)\n }\n\n this.logger = this.logger.extend(`${this.method} ${this.url.href}`)\n this.logger.info('open', this.method, this.url.href)\n\n return invoke()\n }\n\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n\n this.registerEvent(eventName, listener)\n this.logger.info('addEventListener', eventName, listener)\n\n return invoke()\n }\n\n case 'setRequestHeader': {\n const [name, value] = args as [string, string]\n this.requestHeaders.set(name, value)\n\n this.logger.info('setRequestHeader', name, value)\n\n return invoke()\n }\n\n case 'send': {\n const [body] = args as [\n body?: XMLHttpRequestBodyInit | Document | null,\n ]\n\n this.request.addEventListener('load', () => {\n if (typeof this.onResponse !== 'undefined') {\n // Create a Fetch API Response representation of whichever\n // response this XMLHttpRequest received. Note those may\n // be either a mocked and the original response.\n const fetchResponse = createResponse(\n this.request,\n /**\n * The `response` property is the right way to read\n * the ambiguous response body, as the request's \"responseType\" may differ.\n * @see https://xhr.spec.whatwg.org/#the-response-attribute\n */\n this.request.response\n )\n\n // Notify the consumer about the response.\n this.onResponse.call(this, {\n response: fetchResponse,\n isMockedResponse: this[kIsRequestHandled],\n request: fetchRequest,\n requestId: this.requestId!,\n })\n }\n })\n\n const requestBody =\n typeof body === 'string' ? encodeBuffer(body) : body\n\n // Delegate request handling to the consumer.\n const fetchRequest = this.toFetchApiRequest(requestBody)\n this[kFetchRequest] = fetchRequest.clone()\n\n /**\n * @note Start request handling on the next tick so that the user\n * could add event listeners for \"loadend\" before the interceptor fires it.\n */\n queueMicrotask(() => {\n const onceRequestSettled =\n this.onRequest?.call(this, {\n request: fetchRequest,\n requestId: this.requestId!,\n }) || Promise.resolve()\n\n onceRequestSettled.finally(() => {\n // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.\n if (!this[kIsRequestHandled]) {\n this.logger.info(\n 'request callback settled but request has not been handled (readystate %d), performing as-is...',\n this.request.readyState\n )\n\n /**\n * @note Set the intercepted request ID on the original request in Node.js\n * so that if it triggers any other interceptors, they don't attempt\n * to process it once again.\n *\n * For instance, XMLHttpRequest is often implemented via \"http.ClientRequest\"\n * and we don't want for both XHR and ClientRequest interceptors to\n * handle the same request at the same time (e.g. emit the \"response\" event twice).\n */\n if (IS_NODE) {\n this.request.setRequestHeader(\n INTERNAL_REQUEST_ID_HEADER_NAME,\n this.requestId!\n )\n }\n\n return invoke()\n }\n })\n })\n\n break\n }\n\n default: {\n return invoke()\n }\n }\n },\n })\n\n /**\n * Proxy the `.upload` property to gather the event listeners/callbacks.\n */\n define(\n this.request,\n 'upload',\n createProxy(this.request.upload, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'onloadstart':\n case 'onprogress':\n case 'onaboart':\n case 'onerror':\n case 'onload':\n case 'ontimeout':\n case 'onloadend': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n this.registerUploadEvent(eventName, nextValue as Function)\n }\n }\n\n return invoke()\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n this.registerUploadEvent(eventName, listener)\n this.logger.info('upload.addEventListener', eventName, listener)\n\n return invoke()\n }\n }\n },\n })\n )\n }\n\n private registerEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.events.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.events.set(eventName, nextEvents)\n\n this.logger.info('registered event \"%s\"', eventName, listener)\n }\n\n private registerUploadEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.uploadEvents.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.uploadEvents.set(eventName, nextEvents)\n\n this.logger.info('registered upload event \"%s\"', eventName, listener)\n }\n\n /**\n * Responds to the current request with the given\n * Fetch API `Response` instance.\n */\n public async respondWith(response: Response): Promise<void> {\n /**\n * @note Since `XMLHttpRequestController` delegates the handling of the responses\n * to the \"load\" event listener that doesn't distinguish between the mocked and original\n * responses, mark the request that had a mocked response with a corresponding symbol.\n *\n * Mark this request as having a mocked response immediately since\n * calculating request/response total body length is asynchronous.\n */\n this[kIsRequestHandled] = true\n\n /**\n * Dispatch request upload events for requests with a body.\n * @see https://github.com/mswjs/interceptors/issues/573\n */\n if (this[kFetchRequest]) {\n const totalRequestBodyLength = await getBodyByteLength(\n this[kFetchRequest]\n )\n\n this.trigger('loadstart', this.request.upload, {\n loaded: 0,\n total: totalRequestBodyLength,\n })\n this.trigger('progress', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n this.trigger('load', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n\n this.trigger('loadend', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n }\n\n this.logger.info(\n 'responding with a mocked response: %d %s',\n response.status,\n response.statusText\n )\n\n define(this.request, 'status', response.status)\n define(this.request, 'statusText', response.statusText)\n define(this.request, 'responseURL', this.url.href)\n\n this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {\n apply: (_, __, args: [name: string]) => {\n this.logger.info('getResponseHeader', args[0])\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning null')\n\n // Headers not received yet, nothing to return.\n return null\n }\n\n const headerValue = response.headers.get(args[0])\n this.logger.info(\n 'resolved response header \"%s\" to',\n args[0],\n headerValue\n )\n\n return headerValue\n },\n })\n\n this.request.getAllResponseHeaders = new Proxy(\n this.request.getAllResponseHeaders,\n {\n apply: () => {\n this.logger.info('getAllResponseHeaders')\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning empty string')\n\n // Headers not received yet, nothing to return.\n return ''\n }\n\n const headersList = Array.from(response.headers.entries())\n const allHeaders = headersList\n .map(([headerName, headerValue]) => {\n return `${headerName}: ${headerValue}`\n })\n .join('\\r\\n')\n\n this.logger.info('resolved all response headers to', allHeaders)\n\n return allHeaders\n },\n }\n )\n\n // Update the response getters to resolve against the mocked response.\n Object.defineProperties(this.request, {\n response: {\n enumerable: true,\n configurable: false,\n get: () => this.response,\n },\n responseText: {\n enumerable: true,\n configurable: false,\n get: () => this.responseText,\n },\n responseXML: {\n enumerable: true,\n configurable: false,\n get: () => this.responseXML,\n },\n })\n\n const totalResponseBodyLength = await getBodyByteLength(response.clone())\n\n this.logger.info('calculated response body length', totalResponseBodyLength)\n\n this.trigger('loadstart', this.request, {\n loaded: 0,\n total: totalResponseBodyLength,\n })\n\n this.setReadyState(this.request.HEADERS_RECEIVED)\n this.setReadyState(this.request.LOADING)\n\n const finalizeResponse = () => {\n this.logger.info('finalizing the mocked response...')\n\n this.setReadyState(this.request.DONE)\n\n this.trigger('load', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n\n this.trigger('loadend', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n if (response.body) {\n this.logger.info('mocked response has body, streaming...')\n\n const reader = response.body.getReader()\n\n const readNextResponseBodyChunk = async () => {\n const { value, done } = await reader.read()\n\n if (done) {\n this.logger.info('response body stream done!')\n finalizeResponse()\n return\n }\n\n if (value) {\n this.logger.info('read response body chunk:', value)\n this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)\n\n this.trigger('progress', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n readNextResponseBodyChunk()\n }\n\n readNextResponseBodyChunk()\n } else {\n finalizeResponse()\n }\n }\n\n private responseBufferToText(): string {\n return decodeBuffer(this.responseBuffer)\n }\n\n get response(): unknown {\n this.logger.info(\n 'getResponse (responseType: %s)',\n this.request.responseType\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n switch (this.request.responseType) {\n case 'json': {\n const responseJson = parseJson(this.responseBufferToText())\n this.logger.info('resolved response JSON', responseJson)\n\n return responseJson\n }\n\n case 'arraybuffer': {\n const arrayBuffer = toArrayBuffer(this.responseBuffer)\n this.logger.info('resolved response ArrayBuffer', arrayBuffer)\n\n return arrayBuffer\n }\n\n case 'blob': {\n const mimeType =\n this.request.getResponseHeader('Content-Type') || 'text/plain'\n const responseBlob = new Blob([this.responseBufferToText()], {\n type: mimeType,\n })\n\n this.logger.info(\n 'resolved response Blob (mime type: %s)',\n responseBlob,\n mimeType\n )\n\n return responseBlob\n }\n\n default: {\n const responseText = this.responseBufferToText()\n this.logger.info(\n 'resolving \"%s\" response type as text',\n this.request.responseType,\n responseText\n )\n\n return responseText\n }\n }\n }\n\n get responseText(): string {\n /**\n * Throw when trying to read the response body as text when the\n * \"responseType\" doesn't expect text. This just respects the spec better.\n * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute\n */\n invariant(\n this.request.responseType === '' || this.request.responseType === 'text',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (\n this.request.readyState !== this.request.LOADING &&\n this.request.readyState !== this.request.DONE\n ) {\n return ''\n }\n\n const responseText = this.responseBufferToText()\n this.logger.info('getResponseText: \"%s\"', responseText)\n\n return responseText\n }\n\n get responseXML(): Document | null {\n invariant(\n this.request.responseType === '' ||\n this.request.responseType === 'document',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n const contentType = this.request.getResponseHeader('Content-Type') || ''\n\n if (typeof DOMParser === 'undefined') {\n console.warn(\n 'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'\n )\n return null\n }\n\n if (isDomParserSupportedType(contentType)) {\n return new DOMParser().parseFromString(\n this.responseBufferToText(),\n contentType\n )\n }\n\n return null\n }\n\n public errorWith(error?: Error): void {\n /**\n * @note Mark this request as handled even if it received a mock error.\n * This prevents the controller from trying to perform this request as-is.\n */\n this[kIsRequestHandled] = true\n this.logger.info('responding with an error')\n\n this.setReadyState(this.request.DONE)\n this.trigger('error', this.request)\n this.trigger('loadend', this.request)\n }\n\n /**\n * Transitions this request's `readyState` to the given one.\n */\n private setReadyState(nextReadyState: number): void {\n this.logger.info(\n 'setReadyState: %d -> %d',\n this.request.readyState,\n nextReadyState\n )\n\n if (this.request.readyState === nextReadyState) {\n this.logger.info('ready state identical, skipping transition...')\n return\n }\n\n define(this.request, 'readyState', nextReadyState)\n\n this.logger.info('set readyState to: %d', nextReadyState)\n\n if (nextReadyState !== this.request.UNSENT) {\n this.logger.info('triggering \"readystatechange\" event...')\n\n this.trigger('readystatechange', this.request)\n }\n }\n\n /**\n * Triggers given event on the `XMLHttpRequest` instance.\n */\n private trigger<\n EventName extends keyof (XMLHttpRequestEventTargetEventMap & {\n readystatechange: ProgressEvent<XMLHttpRequestEventTarget>\n }),\n >(\n eventName: EventName,\n target: XMLHttpRequest | XMLHttpRequestUpload,\n options?: ProgressEventInit\n ): void {\n const callback = (target as XMLHttpRequest)[`on${eventName}`]\n const event = createEvent(target, eventName, options)\n\n this.logger.info('trigger \"%s\"', eventName, options || '')\n\n // Invoke direct callbacks.\n if (typeof callback === 'function') {\n this.logger.info('found a direct \"%s\" callback, calling...', eventName)\n callback.call(target as XMLHttpRequest, event)\n }\n\n // Invoke event listeners.\n const events =\n target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events\n\n for (const [registeredEventName, listeners] of events) {\n if (registeredEventName === eventName) {\n this.logger.info(\n 'found %d listener(s) for \"%s\" event, calling...',\n listeners.length,\n eventName\n )\n\n listeners.forEach((listener) => listener.call(target, event))\n }\n }\n }\n\n /**\n * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.\n */\n private toFetchApiRequest(\n body: XMLHttpRequestBodyInit | Document | null | undefined\n ): Request {\n this.logger.info('converting request to a Fetch API Request...')\n\n // If the `Document` is used as the body of this XMLHttpRequest,\n // set its inner text as the Fetch API Request body.\n const resolvedBody =\n body instanceof Document ? body.documentElement.innerText : body\n\n const fetchRequest = new FetchRequest(this.url.href, {\n method: this.method,\n headers: this.requestHeaders,\n /**\n * @see https://xhr.spec.whatwg.org/#cross-origin-credentials\n */\n credentials: this.request.withCredentials ? 'include' : 'same-origin',\n body: ['GET', 'HEAD'].includes(this.method.toUpperCase())\n ? null\n : resolvedBody,\n })\n\n const proxyHeaders = createProxy(fetchRequest.headers, {\n methodCall: ([methodName, args], invoke) => {\n // Forward the latest state of the internal request headers\n // because the interceptor might have modified them\n // without responding to the request.\n switch (methodName) {\n case 'append':\n case 'set': {\n const [headerName, headerValue] = args as [string, string]\n this.request.setRequestHeader(headerName, headerValue)\n break\n }\n\n case 'delete': {\n const [headerName] = args as [string]\n console.warn(\n `XMLHttpRequest: Cannot remove a \"${headerName}\" header from the Fetch API representation of the \"${fetchRequest.method} ${fetchRequest.url}\" request. XMLHttpRequest headers cannot be removed.`\n )\n break\n }\n }\n\n return invoke()\n },\n })\n define(fetchRequest, 'headers', proxyHeaders)\n setRawRequest(fetchRequest, this.request)\n\n this.logger.info('converted request to a Fetch API Request!', fetchRequest)\n\n return fetchRequest\n }\n}\n\nfunction toAbsoluteUrl(url: string | URL): URL {\n /**\n * @note XMLHttpRequest interceptor may run in environments\n * that implement XMLHttpRequest but don't implement \"location\"\n * (for example, React Native). If that's the case, return the\n * input URL as-is (nothing to be relative to).\n * @see https://github.com/mswjs/msw/issues/1777\n */\n if (typeof location === 'undefined') {\n return new URL(url)\n }\n\n return new URL(url.toString(), location.href)\n}\n\nfunction define(\n target: object,\n property: string | symbol,\n value: unknown\n): void {\n Reflect.defineProperty(target, property, {\n // Ensure writable properties to allow redefining readonly properties.\n writable: true,\n enumerable: true,\n value,\n })\n}\n","import type { Logger } from '@open-draft/logger'\nimport { XMLHttpRequestEmitter } from '.'\nimport { RequestController } from '../../RequestController'\nimport { XMLHttpRequestController } from './XMLHttpRequestController'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { isResponseError } from '../../utils/responseUtils'\n\nexport interface XMLHttpRequestProxyOptions {\n emitter: XMLHttpRequestEmitter\n logger: Logger\n}\n\n/**\n * Create a proxied `XMLHttpRequest` class.\n * The proxied class establishes spies on certain methods,\n * allowing us to intercept requests and respond to them.\n */\nexport function createXMLHttpRequestProxy({\n emitter,\n logger,\n}: XMLHttpRequestProxyOptions) {\n const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {\n construct(target, args, newTarget) {\n logger.info('constructed new XMLHttpRequest')\n\n const originalRequest = Reflect.construct(\n target,\n args,\n newTarget\n ) as XMLHttpRequest\n\n /**\n * @note Forward prototype descriptors onto the proxied object.\n * XMLHttpRequest is implemented in JSDOM in a way that assigns\n * a bunch of descriptors, like \"set responseType()\" on the prototype.\n * With this propagation, we make sure that those descriptors trigger\n * when the user operates with the proxied request instance.\n */\n const prototypeDescriptors = Object.getOwnPropertyDescriptors(\n target.prototype\n )\n for (const propertyName in prototypeDescriptors) {\n Reflect.defineProperty(\n originalRequest,\n propertyName,\n prototypeDescriptors[propertyName]\n )\n }\n\n const xhrRequestController = new XMLHttpRequestController(\n originalRequest,\n logger\n )\n\n xhrRequestController.onRequest = async function ({ request, requestId }) {\n const controller = new RequestController(request, {\n passthrough: () => {\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n },\n respondWith: async (response) => {\n if (isResponseError(response)) {\n this.errorWith(new TypeError('Network error'))\n return\n }\n\n await this.respondWith(response)\n },\n errorWith: (reason) => {\n this.logger.info('request errored!', { error: reason })\n\n if (reason instanceof Error) {\n this.errorWith(reason)\n }\n },\n })\n\n this.logger.info('awaiting mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n controller,\n emitter,\n })\n }\n\n xhrRequestController.onResponse = async function ({\n response,\n isMockedResponse,\n request,\n requestId,\n }) {\n this.logger.info(\n 'emitting the \"response\" event for %s listener(s)...',\n emitter.listenerCount('response')\n )\n\n emitter.emit('response', {\n response,\n isMockedResponse,\n request,\n requestId,\n })\n }\n\n // Return the proxied request from the controller\n // so that the controller can react to the consumer's interactions\n // with this request (opening/sending/etc).\n return xhrRequestController.request\n },\n })\n\n return XMLHttpRequestProxy\n}\n","import { Emitter } from 'strict-event-emitter'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>\n\nexport class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {\n static interceptorSymbol = Symbol('xhr')\n\n constructor() {\n super(XMLHttpRequestInterceptor.interceptorSymbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('XMLHttpRequest')\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('patching global XMLHttpRequest...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'XMLHttpRequest', () => {\n return createXMLHttpRequestProxy({\n emitter: this.emitter,\n logger: this.logger,\n })\n })\n )\n\n logger.info(\n 'global XMLHttpRequest patched!',\n globalThis.XMLHttpRequest.name\n )\n }\n}\n"],"mappings":"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"} |
| import { n as INTERNAL_REQUEST_ID_HEADER_NAME, r as Interceptor, t as createRequestId } from "./createRequestId-DYCsFHOi.mjs"; | ||
| import { i as FetchResponse, n as setRawRequest, o as RequestController, r as FetchRequest } from "./getRawRequest-B1BqgWG6.mjs"; | ||
| import { n as encodeBuffer, r as toArrayBuffer, t as decodeBuffer } from "./bufferUtils-BiiO6HZv.mjs"; | ||
| import { n as patchesRegistry, t as hasConfigurableGlobal } from "./hasConfigurableGlobal-FTYwno1G.mjs"; | ||
| import { n as isResponseError, t as handleRequest } from "./handleRequest-BHrC8Flw.mjs"; | ||
| import { invariant } from "outvariant"; | ||
| import { isNodeProcess } from "is-node-process"; | ||
| //#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts | ||
| /** | ||
| * Concatenate two `Uint8Array` buffers. | ||
| */ | ||
| function concatArrayBuffer(left, right) { | ||
| const result = new Uint8Array(left.byteLength + right.byteLength); | ||
| result.set(left, 0); | ||
| result.set(right, left.byteLength); | ||
| return result; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts | ||
| var EventPolyfill = class { | ||
| constructor(type, options) { | ||
| this.NONE = 0; | ||
| this.CAPTURING_PHASE = 1; | ||
| this.AT_TARGET = 2; | ||
| this.BUBBLING_PHASE = 3; | ||
| this.type = ""; | ||
| this.srcElement = null; | ||
| this.currentTarget = null; | ||
| this.eventPhase = 0; | ||
| this.isTrusted = true; | ||
| this.composed = false; | ||
| this.cancelable = true; | ||
| this.defaultPrevented = false; | ||
| this.bubbles = true; | ||
| this.lengthComputable = true; | ||
| this.loaded = 0; | ||
| this.total = 0; | ||
| this.cancelBubble = false; | ||
| this.returnValue = true; | ||
| this.type = type; | ||
| this.target = options?.target || null; | ||
| this.currentTarget = options?.currentTarget || null; | ||
| this.timeStamp = Date.now(); | ||
| } | ||
| composedPath() { | ||
| return []; | ||
| } | ||
| initEvent(type, bubbles, cancelable) { | ||
| this.type = type; | ||
| this.bubbles = !!bubbles; | ||
| this.cancelable = !!cancelable; | ||
| } | ||
| preventDefault() { | ||
| this.defaultPrevented = true; | ||
| } | ||
| stopPropagation() {} | ||
| stopImmediatePropagation() {} | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts | ||
| var ProgressEventPolyfill = class extends EventPolyfill { | ||
| constructor(type, init) { | ||
| super(type); | ||
| this.lengthComputable = init?.lengthComputable || false; | ||
| this.composed = init?.composed || false; | ||
| this.loaded = init?.loaded || 0; | ||
| this.total = init?.total || 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createEvent.ts | ||
| const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined"; | ||
| function createEvent(target, type, init) { | ||
| const progressEvents = [ | ||
| "error", | ||
| "progress", | ||
| "loadstart", | ||
| "loadend", | ||
| "load", | ||
| "timeout", | ||
| "abort" | ||
| ]; | ||
| /** | ||
| * `ProgressEvent` is not supported in React Native. | ||
| * @see https://github.com/mswjs/interceptors/issues/40 | ||
| */ | ||
| const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill; | ||
| return progressEvents.includes(type) ? new ProgressEventClass(type, { | ||
| lengthComputable: true, | ||
| loaded: init?.loaded || 0, | ||
| total: init?.total || 0 | ||
| }) : new EventPolyfill(type, { | ||
| target, | ||
| currentTarget: target | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/findPropertySource.ts | ||
| /** | ||
| * Returns the source object of the given property on the target object | ||
| * (the target itself, any parent in its prototype, or null). | ||
| */ | ||
| function findPropertySource(target, propertyName) { | ||
| if (!(propertyName in target)) return null; | ||
| if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target; | ||
| const prototype = Reflect.getPrototypeOf(target); | ||
| return prototype ? findPropertySource(prototype, propertyName) : null; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/createProxy.ts | ||
| function createProxy(target, options) { | ||
| return new Proxy(target, optionsToProxyHandler(options)); | ||
| } | ||
| function optionsToProxyHandler(options) { | ||
| const { constructorCall, methodCall, getProperty, setProperty } = options; | ||
| const handler = {}; | ||
| if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) { | ||
| const next = Reflect.construct.bind(null, target, args, newTarget); | ||
| return constructorCall.call(newTarget, args, next); | ||
| }; | ||
| handler.set = function(target, propertyName, nextValue) { | ||
| const next = () => { | ||
| const propertySource = findPropertySource(target, propertyName) || target; | ||
| const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName); | ||
| if (typeof ownDescriptors?.set !== "undefined") { | ||
| ownDescriptors.set.apply(target, [nextValue]); | ||
| return true; | ||
| } | ||
| return Reflect.defineProperty(propertySource, propertyName, { | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| value: nextValue | ||
| }); | ||
| }; | ||
| if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next); | ||
| return next(); | ||
| }; | ||
| handler.get = function(target, propertyName, receiver) { | ||
| /** | ||
| * @note Using `Reflect.get()` here causes "TypeError: Illegal invocation". | ||
| */ | ||
| const next = () => target[propertyName]; | ||
| const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next(); | ||
| if (typeof value === "function") return (...args) => { | ||
| const next$1 = value.bind(target, ...args); | ||
| if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1); | ||
| return next$1(); | ||
| }; | ||
| return value; | ||
| }; | ||
| return handler; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts | ||
| function isDomParserSupportedType(type) { | ||
| return [ | ||
| "application/xhtml+xml", | ||
| "application/xml", | ||
| "image/svg+xml", | ||
| "text/html", | ||
| "text/xml" | ||
| ].some((supportedType) => { | ||
| return type.startsWith(supportedType); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/parseJson.ts | ||
| /** | ||
| * Parses a given string into JSON. | ||
| * Gracefully handles invalid JSON by returning `null`. | ||
| */ | ||
| function parseJson(data) { | ||
| try { | ||
| return JSON.parse(data); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createResponse.ts | ||
| /** | ||
| * Creates a Fetch API `Response` instance from the given | ||
| * `XMLHttpRequest` instance and a response body. | ||
| */ | ||
| function createResponse(request, body) { | ||
| return new FetchResponse(FetchResponse.isResponseWithBody(request.status) ? body : null, { | ||
| url: request.responseURL, | ||
| status: request.status, | ||
| statusText: request.statusText, | ||
| headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders()) | ||
| }); | ||
| } | ||
| function createHeadersFromXMLHttpRequestHeaders(headersString) { | ||
| const headers = new Headers(); | ||
| const lines = headersString.split(/[\r\n]+/); | ||
| for (const line of lines) { | ||
| if (line.trim() === "") continue; | ||
| const [name, ...parts] = line.split(": "); | ||
| const value = parts.join(": "); | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts | ||
| /** | ||
| * Return a total byte length of the given request/response body. | ||
| * If the `Content-Length` header is present, it will be used as the byte length. | ||
| */ | ||
| async function getBodyByteLength(input) { | ||
| const explicitContentLength = input.headers.get("content-length"); | ||
| if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength); | ||
| return (await input.arrayBuffer()).byteLength; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts | ||
| const kIsRequestHandled = Symbol("kIsRequestHandled"); | ||
| const IS_NODE = isNodeProcess(); | ||
| const kFetchRequest = Symbol("kFetchRequest"); | ||
| /** | ||
| * An `XMLHttpRequest` instance controller that allows us | ||
| * to handle any given request instance (e.g. responding to it). | ||
| */ | ||
| var XMLHttpRequestController = class { | ||
| constructor(initialRequest, logger) { | ||
| this.initialRequest = initialRequest; | ||
| this.logger = logger; | ||
| this.method = "GET"; | ||
| this.url = null; | ||
| this[kIsRequestHandled] = false; | ||
| this.events = /* @__PURE__ */ new Map(); | ||
| this.uploadEvents = /* @__PURE__ */ new Map(); | ||
| this.requestId = createRequestId(); | ||
| this.requestHeaders = new Headers(); | ||
| this.responseBuffer = new Uint8Array(); | ||
| this.request = createProxy(initialRequest, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "ontimeout": { | ||
| const eventName = propertyName.slice(2); | ||
| /** | ||
| * @note Proxy callbacks to event listeners because JSDOM has trouble | ||
| * translating these properties to callbacks. It seemed to be operating | ||
| * on events exclusively. | ||
| */ | ||
| this.request.addEventListener(eventName, nextValue); | ||
| return invoke(); | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "open": { | ||
| const [method, url] = args; | ||
| if (typeof url === "undefined") { | ||
| this.method = "GET"; | ||
| this.url = toAbsoluteUrl(method); | ||
| } else { | ||
| this.method = method; | ||
| this.url = toAbsoluteUrl(url); | ||
| } | ||
| this.logger = this.logger.extend(`${this.method} ${this.url.href}`); | ||
| this.logger.info("open", this.method, this.url.href); | ||
| return invoke(); | ||
| } | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerEvent(eventName, listener); | ||
| this.logger.info("addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| case "setRequestHeader": { | ||
| const [name, value] = args; | ||
| this.requestHeaders.set(name, value); | ||
| this.logger.info("setRequestHeader", name, value); | ||
| return invoke(); | ||
| } | ||
| case "send": { | ||
| const [body] = args; | ||
| this.request.addEventListener("load", () => { | ||
| if (typeof this.onResponse !== "undefined") { | ||
| const fetchResponse = createResponse( | ||
| this.request, | ||
| /** | ||
| * The `response` property is the right way to read | ||
| * the ambiguous response body, as the request's "responseType" may differ. | ||
| * @see https://xhr.spec.whatwg.org/#the-response-attribute | ||
| */ | ||
| this.request.response | ||
| ); | ||
| this.onResponse.call(this, { | ||
| response: fetchResponse, | ||
| isMockedResponse: this[kIsRequestHandled], | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }); | ||
| } | ||
| }); | ||
| const requestBody = typeof body === "string" ? encodeBuffer(body) : body; | ||
| const fetchRequest = this.toFetchApiRequest(requestBody); | ||
| this[kFetchRequest] = fetchRequest.clone(); | ||
| /** | ||
| * @note Start request handling on the next tick so that the user | ||
| * could add event listeners for "loadend" before the interceptor fires it. | ||
| */ | ||
| queueMicrotask(() => { | ||
| (this.onRequest?.call(this, { | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }) || Promise.resolve()).finally(() => { | ||
| if (!this[kIsRequestHandled]) { | ||
| this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState); | ||
| /** | ||
| * @note Set the intercepted request ID on the original request in Node.js | ||
| * so that if it triggers any other interceptors, they don't attempt | ||
| * to process it once again. | ||
| * | ||
| * For instance, XMLHttpRequest is often implemented via "http.ClientRequest" | ||
| * and we don't want for both XHR and ClientRequest interceptors to | ||
| * handle the same request at the same time (e.g. emit the "response" event twice). | ||
| */ | ||
| if (IS_NODE) this.request.setRequestHeader(INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId); | ||
| return invoke(); | ||
| } | ||
| }); | ||
| }); | ||
| break; | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| } | ||
| }); | ||
| /** | ||
| * Proxy the `.upload` property to gather the event listeners/callbacks. | ||
| */ | ||
| define(this.request, "upload", createProxy(this.request.upload, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "onloadstart": | ||
| case "onprogress": | ||
| case "onaboart": | ||
| case "onerror": | ||
| case "onload": | ||
| case "ontimeout": | ||
| case "onloadend": { | ||
| const eventName = propertyName.slice(2); | ||
| this.registerUploadEvent(eventName, nextValue); | ||
| } | ||
| } | ||
| return invoke(); | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerUploadEvent(eventName, listener); | ||
| this.logger.info("upload.addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| } | ||
| } | ||
| })); | ||
| } | ||
| registerEvent(eventName, listener) { | ||
| const nextEvents = (this.events.get(eventName) || []).concat(listener); | ||
| this.events.set(eventName, nextEvents); | ||
| this.logger.info("registered event \"%s\"", eventName, listener); | ||
| } | ||
| registerUploadEvent(eventName, listener) { | ||
| const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener); | ||
| this.uploadEvents.set(eventName, nextEvents); | ||
| this.logger.info("registered upload event \"%s\"", eventName, listener); | ||
| } | ||
| /** | ||
| * Responds to the current request with the given | ||
| * Fetch API `Response` instance. | ||
| */ | ||
| async respondWith(response) { | ||
| /** | ||
| * @note Since `XMLHttpRequestController` delegates the handling of the responses | ||
| * to the "load" event listener that doesn't distinguish between the mocked and original | ||
| * responses, mark the request that had a mocked response with a corresponding symbol. | ||
| * | ||
| * Mark this request as having a mocked response immediately since | ||
| * calculating request/response total body length is asynchronous. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| /** | ||
| * Dispatch request upload events for requests with a body. | ||
| * @see https://github.com/mswjs/interceptors/issues/573 | ||
| */ | ||
| if (this[kFetchRequest]) { | ||
| const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]); | ||
| this.trigger("loadstart", this.request.upload, { | ||
| loaded: 0, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("progress", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("load", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| } | ||
| this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText); | ||
| define(this.request, "status", response.status); | ||
| define(this.request, "statusText", response.statusText); | ||
| define(this.request, "responseURL", this.url.href); | ||
| this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => { | ||
| this.logger.info("getResponseHeader", args[0]); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning null"); | ||
| return null; | ||
| } | ||
| const headerValue = response.headers.get(args[0]); | ||
| this.logger.info("resolved response header \"%s\" to", args[0], headerValue); | ||
| return headerValue; | ||
| } }); | ||
| this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => { | ||
| this.logger.info("getAllResponseHeaders"); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning empty string"); | ||
| return ""; | ||
| } | ||
| const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => { | ||
| return `${headerName}: ${headerValue}`; | ||
| }).join("\r\n"); | ||
| this.logger.info("resolved all response headers to", allHeaders); | ||
| return allHeaders; | ||
| } }); | ||
| Object.defineProperties(this.request, { | ||
| response: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.response | ||
| }, | ||
| responseText: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseText | ||
| }, | ||
| responseXML: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseXML | ||
| } | ||
| }); | ||
| const totalResponseBodyLength = await getBodyByteLength(response.clone()); | ||
| this.logger.info("calculated response body length", totalResponseBodyLength); | ||
| this.trigger("loadstart", this.request, { | ||
| loaded: 0, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.setReadyState(this.request.HEADERS_RECEIVED); | ||
| this.setReadyState(this.request.LOADING); | ||
| const finalizeResponse = () => { | ||
| this.logger.info("finalizing the mocked response..."); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("load", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| }; | ||
| if (response.body) { | ||
| this.logger.info("mocked response has body, streaming..."); | ||
| const reader = response.body.getReader(); | ||
| const readNextResponseBodyChunk = async () => { | ||
| const { value, done } = await reader.read(); | ||
| if (done) { | ||
| this.logger.info("response body stream done!"); | ||
| finalizeResponse(); | ||
| return; | ||
| } | ||
| if (value) { | ||
| this.logger.info("read response body chunk:", value); | ||
| this.responseBuffer = concatArrayBuffer(this.responseBuffer, value); | ||
| this.trigger("progress", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| } | ||
| readNextResponseBodyChunk(); | ||
| }; | ||
| readNextResponseBodyChunk(); | ||
| } else finalizeResponse(); | ||
| } | ||
| responseBufferToText() { | ||
| return decodeBuffer(this.responseBuffer); | ||
| } | ||
| get response() { | ||
| this.logger.info("getResponse (responseType: %s)", this.request.responseType); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| switch (this.request.responseType) { | ||
| case "json": { | ||
| const responseJson = parseJson(this.responseBufferToText()); | ||
| this.logger.info("resolved response JSON", responseJson); | ||
| return responseJson; | ||
| } | ||
| case "arraybuffer": { | ||
| const arrayBuffer = toArrayBuffer(this.responseBuffer); | ||
| this.logger.info("resolved response ArrayBuffer", arrayBuffer); | ||
| return arrayBuffer; | ||
| } | ||
| case "blob": { | ||
| const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain"; | ||
| const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType }); | ||
| this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType); | ||
| return responseBlob; | ||
| } | ||
| default: { | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText); | ||
| return responseText; | ||
| } | ||
| } | ||
| } | ||
| get responseText() { | ||
| /** | ||
| * Throw when trying to read the response body as text when the | ||
| * "responseType" doesn't expect text. This just respects the spec better. | ||
| * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute | ||
| */ | ||
| invariant(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return ""; | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("getResponseText: \"%s\"", responseText); | ||
| return responseText; | ||
| } | ||
| get responseXML() { | ||
| invariant(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| const contentType = this.request.getResponseHeader("Content-Type") || ""; | ||
| if (typeof DOMParser === "undefined") { | ||
| console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly."); | ||
| return null; | ||
| } | ||
| if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType); | ||
| return null; | ||
| } | ||
| errorWith(error) { | ||
| /** | ||
| * @note Mark this request as handled even if it received a mock error. | ||
| * This prevents the controller from trying to perform this request as-is. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| this.logger.info("responding with an error"); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("error", this.request); | ||
| this.trigger("loadend", this.request); | ||
| } | ||
| /** | ||
| * Transitions this request's `readyState` to the given one. | ||
| */ | ||
| setReadyState(nextReadyState) { | ||
| this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState); | ||
| if (this.request.readyState === nextReadyState) { | ||
| this.logger.info("ready state identical, skipping transition..."); | ||
| return; | ||
| } | ||
| define(this.request, "readyState", nextReadyState); | ||
| this.logger.info("set readyState to: %d", nextReadyState); | ||
| if (nextReadyState !== this.request.UNSENT) { | ||
| this.logger.info("triggering \"readystatechange\" event..."); | ||
| this.trigger("readystatechange", this.request); | ||
| } | ||
| } | ||
| /** | ||
| * Triggers given event on the `XMLHttpRequest` instance. | ||
| */ | ||
| trigger(eventName, target, options) { | ||
| const callback = target[`on${eventName}`]; | ||
| const event = createEvent(target, eventName, options); | ||
| this.logger.info("trigger \"%s\"", eventName, options || ""); | ||
| if (typeof callback === "function") { | ||
| this.logger.info("found a direct \"%s\" callback, calling...", eventName); | ||
| callback.call(target, event); | ||
| } | ||
| const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events; | ||
| for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) { | ||
| this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName); | ||
| listeners.forEach((listener) => listener.call(target, event)); | ||
| } | ||
| } | ||
| /** | ||
| * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance. | ||
| */ | ||
| toFetchApiRequest(body) { | ||
| this.logger.info("converting request to a Fetch API Request..."); | ||
| const resolvedBody = body instanceof Document ? body.documentElement.innerText : body; | ||
| const fetchRequest = new FetchRequest(this.url.href, { | ||
| method: this.method, | ||
| headers: this.requestHeaders, | ||
| credentials: this.request.withCredentials ? "include" : "same-origin", | ||
| body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody | ||
| }); | ||
| define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "append": | ||
| case "set": { | ||
| const [headerName, headerValue] = args; | ||
| this.request.setRequestHeader(headerName, headerValue); | ||
| break; | ||
| } | ||
| case "delete": { | ||
| const [headerName] = args; | ||
| console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`); | ||
| break; | ||
| } | ||
| } | ||
| return invoke(); | ||
| } })); | ||
| setRawRequest(fetchRequest, this.request); | ||
| this.logger.info("converted request to a Fetch API Request!", fetchRequest); | ||
| return fetchRequest; | ||
| } | ||
| }; | ||
| function toAbsoluteUrl(url) { | ||
| /** | ||
| * @note XMLHttpRequest interceptor may run in environments | ||
| * that implement XMLHttpRequest but don't implement "location" | ||
| * (for example, React Native). If that's the case, return the | ||
| * input URL as-is (nothing to be relative to). | ||
| * @see https://github.com/mswjs/msw/issues/1777 | ||
| */ | ||
| if (typeof location === "undefined") return new URL(url); | ||
| return new URL(url.toString(), location.href); | ||
| } | ||
| function define(target, property, value) { | ||
| Reflect.defineProperty(target, property, { | ||
| writable: true, | ||
| enumerable: true, | ||
| value | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts | ||
| /** | ||
| * Create a proxied `XMLHttpRequest` class. | ||
| * The proxied class establishes spies on certain methods, | ||
| * allowing us to intercept requests and respond to them. | ||
| */ | ||
| function createXMLHttpRequestProxy({ emitter, logger }) { | ||
| return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) { | ||
| logger.info("constructed new XMLHttpRequest"); | ||
| const originalRequest = Reflect.construct(target, args, newTarget); | ||
| /** | ||
| * @note Forward prototype descriptors onto the proxied object. | ||
| * XMLHttpRequest is implemented in JSDOM in a way that assigns | ||
| * a bunch of descriptors, like "set responseType()" on the prototype. | ||
| * With this propagation, we make sure that those descriptors trigger | ||
| * when the user operates with the proxied request instance. | ||
| */ | ||
| const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype); | ||
| for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]); | ||
| const xhrRequestController = new XMLHttpRequestController(originalRequest, logger); | ||
| xhrRequestController.onRequest = async function({ request, requestId }) { | ||
| const controller = new RequestController(request, { | ||
| passthrough: () => { | ||
| this.logger.info("no mocked response received, performing request as-is..."); | ||
| }, | ||
| respondWith: async (response) => { | ||
| if (isResponseError(response)) { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| await this.respondWith(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request errored!", { error: reason }); | ||
| if (reason instanceof Error) this.errorWith(reason); | ||
| } | ||
| }); | ||
| this.logger.info("awaiting mocked response..."); | ||
| this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request")); | ||
| await handleRequest({ | ||
| request, | ||
| requestId, | ||
| controller, | ||
| emitter | ||
| }); | ||
| }; | ||
| xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) { | ||
| this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response")); | ||
| emitter.emit("response", { | ||
| response, | ||
| isMockedResponse, | ||
| request, | ||
| requestId | ||
| }); | ||
| }; | ||
| return xhrRequestController.request; | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/index.ts | ||
| var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends Interceptor { | ||
| static { | ||
| this.interceptorSymbol = Symbol("xhr"); | ||
| } | ||
| constructor() { | ||
| super(XMLHttpRequestInterceptor.interceptorSymbol); | ||
| } | ||
| checkEnvironment() { | ||
| return hasConfigurableGlobal("XMLHttpRequest"); | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("patching global XMLHttpRequest..."); | ||
| this.subscriptions.push(patchesRegistry.applyPatch(globalThis, "XMLHttpRequest", () => { | ||
| return createXMLHttpRequestProxy({ | ||
| emitter: this.emitter, | ||
| logger: this.logger | ||
| }); | ||
| })); | ||
| logger.info("global XMLHttpRequest patched!", globalThis.XMLHttpRequest.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { XMLHttpRequestInterceptor as t }; | ||
| //# sourceMappingURL=XMLHttpRequest-DP9ps_69.mjs.map |
| {"version":3,"file":"XMLHttpRequest-DP9ps_69.mjs","names":["handler: ProxyHandler<T>","next","initialRequest: XMLHttpRequest","logger: Logger"],"sources":["../../src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts","../../src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts","../../src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts","../../src/interceptors/XMLHttpRequest/utils/createEvent.ts","../../src/utils/findPropertySource.ts","../../src/utils/createProxy.ts","../../src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts","../../src/utils/parseJson.ts","../../src/interceptors/XMLHttpRequest/utils/createResponse.ts","../../src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts","../../src/interceptors/XMLHttpRequest/index.ts"],"sourcesContent":["/**\n * Concatenate two `Uint8Array` buffers.\n */\nexport function concatArrayBuffer(\n left: Uint8Array,\n right: Uint8Array\n): Uint8Array {\n const result = new Uint8Array(left.byteLength + right.byteLength)\n result.set(left, 0)\n result.set(right, left.byteLength)\n return result\n}\n","export class EventPolyfill implements Event {\n readonly NONE = 0\n readonly CAPTURING_PHASE = 1\n readonly AT_TARGET = 2\n readonly BUBBLING_PHASE = 3\n\n public type: string = ''\n public srcElement: EventTarget | null = null\n public target: EventTarget | null\n public currentTarget: EventTarget | null = null\n public eventPhase: number = 0\n public timeStamp: number\n public isTrusted: boolean = true\n public composed: boolean = false\n public cancelable: boolean = true\n public defaultPrevented: boolean = false\n public bubbles: boolean = true\n public lengthComputable: boolean = true\n public loaded: number = 0\n public total: number = 0\n\n cancelBubble: boolean = false\n returnValue: boolean = true\n\n constructor(\n type: string,\n options?: { target: EventTarget; currentTarget: EventTarget }\n ) {\n this.type = type\n this.target = options?.target || null\n this.currentTarget = options?.currentTarget || null\n this.timeStamp = Date.now()\n }\n\n public composedPath(): EventTarget[] {\n return []\n }\n\n public initEvent(type: string, bubbles?: boolean, cancelable?: boolean) {\n this.type = type\n this.bubbles = !!bubbles\n this.cancelable = !!cancelable\n }\n\n public preventDefault() {\n this.defaultPrevented = true\n }\n\n public stopPropagation() {}\n public stopImmediatePropagation() {}\n}\n","import { EventPolyfill } from './EventPolyfill'\n\nexport class ProgressEventPolyfill extends EventPolyfill {\n readonly lengthComputable: boolean\n readonly composed: boolean\n readonly loaded: number\n readonly total: number\n\n constructor(type: string, init?: ProgressEventInit) {\n super(type)\n\n this.lengthComputable = init?.lengthComputable || false\n this.composed = init?.composed || false\n this.loaded = init?.loaded || 0\n this.total = init?.total || 0\n }\n}\n","import { EventPolyfill } from '../polyfills/EventPolyfill'\nimport { ProgressEventPolyfill } from '../polyfills/ProgressEventPolyfill'\n\nconst SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined'\n\nexport function createEvent(\n target: XMLHttpRequest | XMLHttpRequestUpload,\n type: string,\n init?: ProgressEventInit\n): EventPolyfill | ProgressEvent {\n const progressEvents = [\n 'error',\n 'progress',\n 'loadstart',\n 'loadend',\n 'load',\n 'timeout',\n 'abort',\n ]\n\n /**\n * `ProgressEvent` is not supported in React Native.\n * @see https://github.com/mswjs/interceptors/issues/40\n */\n const ProgressEventClass = SUPPORTS_PROGRESS_EVENT\n ? ProgressEvent\n : ProgressEventPolyfill\n\n const event = progressEvents.includes(type)\n ? new ProgressEventClass(type, {\n lengthComputable: true,\n loaded: init?.loaded || 0,\n total: init?.total || 0,\n })\n : new EventPolyfill(type, {\n target,\n currentTarget: target,\n })\n\n return event\n}\n","/**\n * Returns the source object of the given property on the target object\n * (the target itself, any parent in its prototype, or null).\n */\nexport function findPropertySource(\n target: object,\n propertyName: string | symbol\n): object | null {\n if (!(propertyName in target)) {\n return null\n }\n\n const hasProperty = Object.prototype.hasOwnProperty.call(target, propertyName)\n if (hasProperty) {\n return target\n }\n\n const prototype = Reflect.getPrototypeOf(target)\n return prototype ? findPropertySource(prototype, propertyName) : null\n}\n","import { findPropertySource } from './findPropertySource'\n\nexport interface ProxyOptions<Target extends Record<string, any>> {\n constructorCall?(args: Array<unknown>, next: NextFunction<Target>): Target\n\n methodCall?<F extends keyof Target>(\n this: Target,\n data: [methodName: F, args: Array<unknown>],\n next: NextFunction<void>\n ): void\n\n setProperty?(\n data: [propertyName: string | symbol, nextValue: unknown],\n next: NextFunction<boolean>\n ): boolean\n\n getProperty?(\n data: [propertyName: string | symbol, receiver: Target],\n next: NextFunction<void>\n ): void\n}\n\nexport type NextFunction<ReturnType> = () => ReturnType\n\nexport function createProxy<Target extends object>(\n target: Target,\n options: ProxyOptions<Target>\n): Target {\n const proxy = new Proxy(target, optionsToProxyHandler(options))\n\n return proxy\n}\n\nfunction optionsToProxyHandler<T extends Record<string, any>>(\n options: ProxyOptions<T>\n): ProxyHandler<T> {\n const { constructorCall, methodCall, getProperty, setProperty } = options\n const handler: ProxyHandler<T> = {}\n\n if (typeof constructorCall !== 'undefined') {\n handler.construct = function (target, args, newTarget) {\n const next = Reflect.construct.bind(null, target as any, args, newTarget)\n return constructorCall.call(newTarget, args, next)\n }\n }\n\n handler.set = function (target, propertyName, nextValue) {\n const next = () => {\n const propertySource = findPropertySource(target, propertyName) || target\n const ownDescriptors = Reflect.getOwnPropertyDescriptor(\n propertySource,\n propertyName\n )\n\n // Respect any custom setters present for this property.\n if (typeof ownDescriptors?.set !== 'undefined') {\n ownDescriptors.set.apply(target, [nextValue])\n return true\n }\n\n // Otherwise, set the property on the source.\n return Reflect.defineProperty(propertySource, propertyName, {\n writable: true,\n enumerable: true,\n configurable: true,\n value: nextValue,\n })\n }\n\n if (typeof setProperty !== 'undefined') {\n return setProperty.call(target, [propertyName, nextValue], next)\n }\n\n return next()\n }\n\n handler.get = function (target, propertyName, receiver) {\n /**\n * @note Using `Reflect.get()` here causes \"TypeError: Illegal invocation\".\n */\n const next = () => target[propertyName as any]\n\n const value =\n typeof getProperty !== 'undefined'\n ? getProperty.call(target, [propertyName, receiver], next)\n : next()\n\n if (typeof value === 'function') {\n return (...args: Array<any>) => {\n const next = value.bind(target, ...args)\n\n if (typeof methodCall !== 'undefined') {\n return methodCall.call(target, [propertyName as any, args], next)\n }\n\n return next()\n }\n }\n\n return value\n }\n\n return handler\n}\n","export function isDomParserSupportedType(\n type: string\n): type is DOMParserSupportedType {\n const supportedTypes: Array<DOMParserSupportedType> = [\n 'application/xhtml+xml',\n 'application/xml',\n 'image/svg+xml',\n 'text/html',\n 'text/xml',\n ]\n return supportedTypes.some((supportedType) => {\n return type.startsWith(supportedType)\n })\n}\n","/**\n * Parses a given string into JSON.\n * Gracefully handles invalid JSON by returning `null`.\n */\nexport function parseJson(data: string): Record<string, unknown> | null {\n try {\n const json = JSON.parse(data)\n return json\n } catch (_) {\n return null\n }\n}\n","import { FetchResponse } from '../../../utils/fetchUtils'\n\n/**\n * Creates a Fetch API `Response` instance from the given\n * `XMLHttpRequest` instance and a response body.\n */\nexport function createResponse(\n request: XMLHttpRequest,\n body: BodyInit | null\n): Response {\n /**\n * Handle XMLHttpRequest responses that must have null as the\n * response body when represented using Fetch API Response.\n * XMLHttpRequest response will always have an empty string\n * as the \"request.response\" in those cases, resulting in an error\n * when constructing a Response instance.\n * @see https://github.com/mswjs/interceptors/issues/379\n */\n const responseBodyOrNull = FetchResponse.isResponseWithBody(request.status)\n ? body\n : null\n\n return new FetchResponse(responseBodyOrNull, {\n url: request.responseURL,\n status: request.status,\n statusText: request.statusText,\n headers: createHeadersFromXMLHttpRequestHeaders(\n request.getAllResponseHeaders()\n ),\n })\n}\n\nfunction createHeadersFromXMLHttpRequestHeaders(headersString: string): Headers {\n const headers = new Headers()\n\n const lines = headersString.split(/[\\r\\n]+/)\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n const [name, ...parts] = line.split(': ')\n const value = parts.join(': ')\n\n headers.append(name, value)\n }\n\n return headers\n}\n","/**\n * Return a total byte length of the given request/response body.\n * If the `Content-Length` header is present, it will be used as the byte length.\n */\nexport async function getBodyByteLength(\n input: Request | Response\n): Promise<number> {\n const explicitContentLength = input.headers.get('content-length')\n\n if (explicitContentLength != null && explicitContentLength !== '') {\n return Number(explicitContentLength)\n }\n\n const buffer = await input.arrayBuffer()\n return buffer.byteLength\n}\n","import { invariant } from 'outvariant'\nimport { isNodeProcess } from 'is-node-process'\nimport type { Logger } from '@open-draft/logger'\nimport { concatArrayBuffer } from './utils/concatArrayBuffer'\nimport { createEvent } from './utils/createEvent'\nimport {\n decodeBuffer,\n encodeBuffer,\n toArrayBuffer,\n} from '../../utils/bufferUtils'\nimport { createProxy } from '../../utils/createProxy'\nimport { isDomParserSupportedType } from './utils/isDomParserSupportedType'\nimport { parseJson } from '../../utils/parseJson'\nimport { createResponse } from './utils/createResponse'\nimport { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'\nimport { createRequestId } from '../../createRequestId'\nimport { getBodyByteLength } from './utils/getBodyByteLength'\nimport { setRawRequest } from '../../getRawRequest'\nimport { FetchRequest } from '../../utils/fetchUtils'\n\nconst kIsRequestHandled = Symbol('kIsRequestHandled')\nconst IS_NODE = isNodeProcess()\nconst kFetchRequest = Symbol('kFetchRequest')\n\n/**\n * An `XMLHttpRequest` instance controller that allows us\n * to handle any given request instance (e.g. responding to it).\n */\nexport class XMLHttpRequestController {\n public request: XMLHttpRequest\n public requestId: string\n public onRequest?: (\n this: XMLHttpRequestController,\n args: {\n request: Request\n requestId: string\n }\n ) => Promise<void>\n public onResponse?: (\n this: XMLHttpRequestController,\n args: {\n response: Response\n isMockedResponse: boolean\n request: Request\n requestId: string\n }\n ) => void;\n\n [kIsRequestHandled]: boolean;\n [kFetchRequest]?: Request\n private method: string = 'GET'\n private url: URL = null as any\n private requestHeaders: Headers\n private responseBuffer: Uint8Array\n private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>\n private uploadEvents: Map<\n keyof XMLHttpRequestEventTargetEventMap,\n Array<Function>\n >\n\n constructor(\n readonly initialRequest: XMLHttpRequest,\n public logger: Logger\n ) {\n this[kIsRequestHandled] = false\n\n this.events = new Map()\n this.uploadEvents = new Map()\n this.requestId = createRequestId()\n this.requestHeaders = new Headers()\n this.responseBuffer = new Uint8Array()\n\n this.request = createProxy(initialRequest, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'ontimeout': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n /**\n * @note Proxy callbacks to event listeners because JSDOM has trouble\n * translating these properties to callbacks. It seemed to be operating\n * on events exclusively.\n */\n this.request.addEventListener(eventName, nextValue as any)\n\n return invoke()\n }\n\n default: {\n return invoke()\n }\n }\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'open': {\n const [method, url] = args as [string, string | undefined]\n\n if (typeof url === 'undefined') {\n this.method = 'GET'\n this.url = toAbsoluteUrl(method)\n } else {\n this.method = method\n this.url = toAbsoluteUrl(url)\n }\n\n this.logger = this.logger.extend(`${this.method} ${this.url.href}`)\n this.logger.info('open', this.method, this.url.href)\n\n return invoke()\n }\n\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n\n this.registerEvent(eventName, listener)\n this.logger.info('addEventListener', eventName, listener)\n\n return invoke()\n }\n\n case 'setRequestHeader': {\n const [name, value] = args as [string, string]\n this.requestHeaders.set(name, value)\n\n this.logger.info('setRequestHeader', name, value)\n\n return invoke()\n }\n\n case 'send': {\n const [body] = args as [\n body?: XMLHttpRequestBodyInit | Document | null,\n ]\n\n this.request.addEventListener('load', () => {\n if (typeof this.onResponse !== 'undefined') {\n // Create a Fetch API Response representation of whichever\n // response this XMLHttpRequest received. Note those may\n // be either a mocked and the original response.\n const fetchResponse = createResponse(\n this.request,\n /**\n * The `response` property is the right way to read\n * the ambiguous response body, as the request's \"responseType\" may differ.\n * @see https://xhr.spec.whatwg.org/#the-response-attribute\n */\n this.request.response\n )\n\n // Notify the consumer about the response.\n this.onResponse.call(this, {\n response: fetchResponse,\n isMockedResponse: this[kIsRequestHandled],\n request: fetchRequest,\n requestId: this.requestId!,\n })\n }\n })\n\n const requestBody =\n typeof body === 'string' ? encodeBuffer(body) : body\n\n // Delegate request handling to the consumer.\n const fetchRequest = this.toFetchApiRequest(requestBody)\n this[kFetchRequest] = fetchRequest.clone()\n\n /**\n * @note Start request handling on the next tick so that the user\n * could add event listeners for \"loadend\" before the interceptor fires it.\n */\n queueMicrotask(() => {\n const onceRequestSettled =\n this.onRequest?.call(this, {\n request: fetchRequest,\n requestId: this.requestId!,\n }) || Promise.resolve()\n\n onceRequestSettled.finally(() => {\n // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.\n if (!this[kIsRequestHandled]) {\n this.logger.info(\n 'request callback settled but request has not been handled (readystate %d), performing as-is...',\n this.request.readyState\n )\n\n /**\n * @note Set the intercepted request ID on the original request in Node.js\n * so that if it triggers any other interceptors, they don't attempt\n * to process it once again.\n *\n * For instance, XMLHttpRequest is often implemented via \"http.ClientRequest\"\n * and we don't want for both XHR and ClientRequest interceptors to\n * handle the same request at the same time (e.g. emit the \"response\" event twice).\n */\n if (IS_NODE) {\n this.request.setRequestHeader(\n INTERNAL_REQUEST_ID_HEADER_NAME,\n this.requestId!\n )\n }\n\n return invoke()\n }\n })\n })\n\n break\n }\n\n default: {\n return invoke()\n }\n }\n },\n })\n\n /**\n * Proxy the `.upload` property to gather the event listeners/callbacks.\n */\n define(\n this.request,\n 'upload',\n createProxy(this.request.upload, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'onloadstart':\n case 'onprogress':\n case 'onaboart':\n case 'onerror':\n case 'onload':\n case 'ontimeout':\n case 'onloadend': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n this.registerUploadEvent(eventName, nextValue as Function)\n }\n }\n\n return invoke()\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n this.registerUploadEvent(eventName, listener)\n this.logger.info('upload.addEventListener', eventName, listener)\n\n return invoke()\n }\n }\n },\n })\n )\n }\n\n private registerEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.events.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.events.set(eventName, nextEvents)\n\n this.logger.info('registered event \"%s\"', eventName, listener)\n }\n\n private registerUploadEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.uploadEvents.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.uploadEvents.set(eventName, nextEvents)\n\n this.logger.info('registered upload event \"%s\"', eventName, listener)\n }\n\n /**\n * Responds to the current request with the given\n * Fetch API `Response` instance.\n */\n public async respondWith(response: Response): Promise<void> {\n /**\n * @note Since `XMLHttpRequestController` delegates the handling of the responses\n * to the \"load\" event listener that doesn't distinguish between the mocked and original\n * responses, mark the request that had a mocked response with a corresponding symbol.\n *\n * Mark this request as having a mocked response immediately since\n * calculating request/response total body length is asynchronous.\n */\n this[kIsRequestHandled] = true\n\n /**\n * Dispatch request upload events for requests with a body.\n * @see https://github.com/mswjs/interceptors/issues/573\n */\n if (this[kFetchRequest]) {\n const totalRequestBodyLength = await getBodyByteLength(\n this[kFetchRequest]\n )\n\n this.trigger('loadstart', this.request.upload, {\n loaded: 0,\n total: totalRequestBodyLength,\n })\n this.trigger('progress', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n this.trigger('load', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n\n this.trigger('loadend', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n }\n\n this.logger.info(\n 'responding with a mocked response: %d %s',\n response.status,\n response.statusText\n )\n\n define(this.request, 'status', response.status)\n define(this.request, 'statusText', response.statusText)\n define(this.request, 'responseURL', this.url.href)\n\n this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {\n apply: (_, __, args: [name: string]) => {\n this.logger.info('getResponseHeader', args[0])\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning null')\n\n // Headers not received yet, nothing to return.\n return null\n }\n\n const headerValue = response.headers.get(args[0])\n this.logger.info(\n 'resolved response header \"%s\" to',\n args[0],\n headerValue\n )\n\n return headerValue\n },\n })\n\n this.request.getAllResponseHeaders = new Proxy(\n this.request.getAllResponseHeaders,\n {\n apply: () => {\n this.logger.info('getAllResponseHeaders')\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning empty string')\n\n // Headers not received yet, nothing to return.\n return ''\n }\n\n const headersList = Array.from(response.headers.entries())\n const allHeaders = headersList\n .map(([headerName, headerValue]) => {\n return `${headerName}: ${headerValue}`\n })\n .join('\\r\\n')\n\n this.logger.info('resolved all response headers to', allHeaders)\n\n return allHeaders\n },\n }\n )\n\n // Update the response getters to resolve against the mocked response.\n Object.defineProperties(this.request, {\n response: {\n enumerable: true,\n configurable: false,\n get: () => this.response,\n },\n responseText: {\n enumerable: true,\n configurable: false,\n get: () => this.responseText,\n },\n responseXML: {\n enumerable: true,\n configurable: false,\n get: () => this.responseXML,\n },\n })\n\n const totalResponseBodyLength = await getBodyByteLength(response.clone())\n\n this.logger.info('calculated response body length', totalResponseBodyLength)\n\n this.trigger('loadstart', this.request, {\n loaded: 0,\n total: totalResponseBodyLength,\n })\n\n this.setReadyState(this.request.HEADERS_RECEIVED)\n this.setReadyState(this.request.LOADING)\n\n const finalizeResponse = () => {\n this.logger.info('finalizing the mocked response...')\n\n this.setReadyState(this.request.DONE)\n\n this.trigger('load', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n\n this.trigger('loadend', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n if (response.body) {\n this.logger.info('mocked response has body, streaming...')\n\n const reader = response.body.getReader()\n\n const readNextResponseBodyChunk = async () => {\n const { value, done } = await reader.read()\n\n if (done) {\n this.logger.info('response body stream done!')\n finalizeResponse()\n return\n }\n\n if (value) {\n this.logger.info('read response body chunk:', value)\n this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)\n\n this.trigger('progress', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n readNextResponseBodyChunk()\n }\n\n readNextResponseBodyChunk()\n } else {\n finalizeResponse()\n }\n }\n\n private responseBufferToText(): string {\n return decodeBuffer(this.responseBuffer)\n }\n\n get response(): unknown {\n this.logger.info(\n 'getResponse (responseType: %s)',\n this.request.responseType\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n switch (this.request.responseType) {\n case 'json': {\n const responseJson = parseJson(this.responseBufferToText())\n this.logger.info('resolved response JSON', responseJson)\n\n return responseJson\n }\n\n case 'arraybuffer': {\n const arrayBuffer = toArrayBuffer(this.responseBuffer)\n this.logger.info('resolved response ArrayBuffer', arrayBuffer)\n\n return arrayBuffer\n }\n\n case 'blob': {\n const mimeType =\n this.request.getResponseHeader('Content-Type') || 'text/plain'\n const responseBlob = new Blob([this.responseBufferToText()], {\n type: mimeType,\n })\n\n this.logger.info(\n 'resolved response Blob (mime type: %s)',\n responseBlob,\n mimeType\n )\n\n return responseBlob\n }\n\n default: {\n const responseText = this.responseBufferToText()\n this.logger.info(\n 'resolving \"%s\" response type as text',\n this.request.responseType,\n responseText\n )\n\n return responseText\n }\n }\n }\n\n get responseText(): string {\n /**\n * Throw when trying to read the response body as text when the\n * \"responseType\" doesn't expect text. This just respects the spec better.\n * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute\n */\n invariant(\n this.request.responseType === '' || this.request.responseType === 'text',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (\n this.request.readyState !== this.request.LOADING &&\n this.request.readyState !== this.request.DONE\n ) {\n return ''\n }\n\n const responseText = this.responseBufferToText()\n this.logger.info('getResponseText: \"%s\"', responseText)\n\n return responseText\n }\n\n get responseXML(): Document | null {\n invariant(\n this.request.responseType === '' ||\n this.request.responseType === 'document',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n const contentType = this.request.getResponseHeader('Content-Type') || ''\n\n if (typeof DOMParser === 'undefined') {\n console.warn(\n 'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'\n )\n return null\n }\n\n if (isDomParserSupportedType(contentType)) {\n return new DOMParser().parseFromString(\n this.responseBufferToText(),\n contentType\n )\n }\n\n return null\n }\n\n public errorWith(error?: Error): void {\n /**\n * @note Mark this request as handled even if it received a mock error.\n * This prevents the controller from trying to perform this request as-is.\n */\n this[kIsRequestHandled] = true\n this.logger.info('responding with an error')\n\n this.setReadyState(this.request.DONE)\n this.trigger('error', this.request)\n this.trigger('loadend', this.request)\n }\n\n /**\n * Transitions this request's `readyState` to the given one.\n */\n private setReadyState(nextReadyState: number): void {\n this.logger.info(\n 'setReadyState: %d -> %d',\n this.request.readyState,\n nextReadyState\n )\n\n if (this.request.readyState === nextReadyState) {\n this.logger.info('ready state identical, skipping transition...')\n return\n }\n\n define(this.request, 'readyState', nextReadyState)\n\n this.logger.info('set readyState to: %d', nextReadyState)\n\n if (nextReadyState !== this.request.UNSENT) {\n this.logger.info('triggering \"readystatechange\" event...')\n\n this.trigger('readystatechange', this.request)\n }\n }\n\n /**\n * Triggers given event on the `XMLHttpRequest` instance.\n */\n private trigger<\n EventName extends keyof (XMLHttpRequestEventTargetEventMap & {\n readystatechange: ProgressEvent<XMLHttpRequestEventTarget>\n }),\n >(\n eventName: EventName,\n target: XMLHttpRequest | XMLHttpRequestUpload,\n options?: ProgressEventInit\n ): void {\n const callback = (target as XMLHttpRequest)[`on${eventName}`]\n const event = createEvent(target, eventName, options)\n\n this.logger.info('trigger \"%s\"', eventName, options || '')\n\n // Invoke direct callbacks.\n if (typeof callback === 'function') {\n this.logger.info('found a direct \"%s\" callback, calling...', eventName)\n callback.call(target as XMLHttpRequest, event)\n }\n\n // Invoke event listeners.\n const events =\n target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events\n\n for (const [registeredEventName, listeners] of events) {\n if (registeredEventName === eventName) {\n this.logger.info(\n 'found %d listener(s) for \"%s\" event, calling...',\n listeners.length,\n eventName\n )\n\n listeners.forEach((listener) => listener.call(target, event))\n }\n }\n }\n\n /**\n * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.\n */\n private toFetchApiRequest(\n body: XMLHttpRequestBodyInit | Document | null | undefined\n ): Request {\n this.logger.info('converting request to a Fetch API Request...')\n\n // If the `Document` is used as the body of this XMLHttpRequest,\n // set its inner text as the Fetch API Request body.\n const resolvedBody =\n body instanceof Document ? body.documentElement.innerText : body\n\n const fetchRequest = new FetchRequest(this.url.href, {\n method: this.method,\n headers: this.requestHeaders,\n /**\n * @see https://xhr.spec.whatwg.org/#cross-origin-credentials\n */\n credentials: this.request.withCredentials ? 'include' : 'same-origin',\n body: ['GET', 'HEAD'].includes(this.method.toUpperCase())\n ? null\n : resolvedBody,\n })\n\n const proxyHeaders = createProxy(fetchRequest.headers, {\n methodCall: ([methodName, args], invoke) => {\n // Forward the latest state of the internal request headers\n // because the interceptor might have modified them\n // without responding to the request.\n switch (methodName) {\n case 'append':\n case 'set': {\n const [headerName, headerValue] = args as [string, string]\n this.request.setRequestHeader(headerName, headerValue)\n break\n }\n\n case 'delete': {\n const [headerName] = args as [string]\n console.warn(\n `XMLHttpRequest: Cannot remove a \"${headerName}\" header from the Fetch API representation of the \"${fetchRequest.method} ${fetchRequest.url}\" request. XMLHttpRequest headers cannot be removed.`\n )\n break\n }\n }\n\n return invoke()\n },\n })\n define(fetchRequest, 'headers', proxyHeaders)\n setRawRequest(fetchRequest, this.request)\n\n this.logger.info('converted request to a Fetch API Request!', fetchRequest)\n\n return fetchRequest\n }\n}\n\nfunction toAbsoluteUrl(url: string | URL): URL {\n /**\n * @note XMLHttpRequest interceptor may run in environments\n * that implement XMLHttpRequest but don't implement \"location\"\n * (for example, React Native). If that's the case, return the\n * input URL as-is (nothing to be relative to).\n * @see https://github.com/mswjs/msw/issues/1777\n */\n if (typeof location === 'undefined') {\n return new URL(url)\n }\n\n return new URL(url.toString(), location.href)\n}\n\nfunction define(\n target: object,\n property: string | symbol,\n value: unknown\n): void {\n Reflect.defineProperty(target, property, {\n // Ensure writable properties to allow redefining readonly properties.\n writable: true,\n enumerable: true,\n value,\n })\n}\n","import type { Logger } from '@open-draft/logger'\nimport { XMLHttpRequestEmitter } from '.'\nimport { RequestController } from '../../RequestController'\nimport { XMLHttpRequestController } from './XMLHttpRequestController'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { isResponseError } from '../../utils/responseUtils'\n\nexport interface XMLHttpRequestProxyOptions {\n emitter: XMLHttpRequestEmitter\n logger: Logger\n}\n\n/**\n * Create a proxied `XMLHttpRequest` class.\n * The proxied class establishes spies on certain methods,\n * allowing us to intercept requests and respond to them.\n */\nexport function createXMLHttpRequestProxy({\n emitter,\n logger,\n}: XMLHttpRequestProxyOptions) {\n const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {\n construct(target, args, newTarget) {\n logger.info('constructed new XMLHttpRequest')\n\n const originalRequest = Reflect.construct(\n target,\n args,\n newTarget\n ) as XMLHttpRequest\n\n /**\n * @note Forward prototype descriptors onto the proxied object.\n * XMLHttpRequest is implemented in JSDOM in a way that assigns\n * a bunch of descriptors, like \"set responseType()\" on the prototype.\n * With this propagation, we make sure that those descriptors trigger\n * when the user operates with the proxied request instance.\n */\n const prototypeDescriptors = Object.getOwnPropertyDescriptors(\n target.prototype\n )\n for (const propertyName in prototypeDescriptors) {\n Reflect.defineProperty(\n originalRequest,\n propertyName,\n prototypeDescriptors[propertyName]\n )\n }\n\n const xhrRequestController = new XMLHttpRequestController(\n originalRequest,\n logger\n )\n\n xhrRequestController.onRequest = async function ({ request, requestId }) {\n const controller = new RequestController(request, {\n passthrough: () => {\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n },\n respondWith: async (response) => {\n if (isResponseError(response)) {\n this.errorWith(new TypeError('Network error'))\n return\n }\n\n await this.respondWith(response)\n },\n errorWith: (reason) => {\n this.logger.info('request errored!', { error: reason })\n\n if (reason instanceof Error) {\n this.errorWith(reason)\n }\n },\n })\n\n this.logger.info('awaiting mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n controller,\n emitter,\n })\n }\n\n xhrRequestController.onResponse = async function ({\n response,\n isMockedResponse,\n request,\n requestId,\n }) {\n this.logger.info(\n 'emitting the \"response\" event for %s listener(s)...',\n emitter.listenerCount('response')\n )\n\n emitter.emit('response', {\n response,\n isMockedResponse,\n request,\n requestId,\n })\n }\n\n // Return the proxied request from the controller\n // so that the controller can react to the consumer's interactions\n // with this request (opening/sending/etc).\n return xhrRequestController.request\n },\n })\n\n return XMLHttpRequestProxy\n}\n","import { Emitter } from 'strict-event-emitter'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>\n\nexport class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {\n static interceptorSymbol = Symbol('xhr')\n\n constructor() {\n super(XMLHttpRequestInterceptor.interceptorSymbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('XMLHttpRequest')\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('patching global XMLHttpRequest...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'XMLHttpRequest', () => {\n return createXMLHttpRequestProxy({\n emitter: this.emitter,\n logger: this.logger,\n })\n })\n )\n\n logger.info(\n 'global XMLHttpRequest patched!',\n globalThis.XMLHttpRequest.name\n )\n }\n}\n"],"mappings":"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"} |
| import { c as Interceptor } from "./fetchUtils-BKJ1XmiO.mjs"; | ||
| //#region src/BatchInterceptor.ts | ||
| /** | ||
| * A batch interceptor that exposes a single interface | ||
| * to apply and operate with multiple interceptors at once. | ||
| */ | ||
| var BatchInterceptor = class BatchInterceptor extends Interceptor { | ||
| constructor(options) { | ||
| BatchInterceptor.symbol = Symbol(options.name); | ||
| super(BatchInterceptor.symbol); | ||
| this.interceptors = options.interceptors; | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("applying all %d interceptors...", this.interceptors.length); | ||
| for (const interceptor of this.interceptors) { | ||
| logger.info("applying \"%s\" interceptor...", interceptor.constructor.name); | ||
| interceptor.apply(); | ||
| logger.info("adding interceptor dispose subscription"); | ||
| this.subscriptions.push(() => interceptor.dispose()); | ||
| } | ||
| } | ||
| on(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.on(event, listener); | ||
| return this; | ||
| } | ||
| once(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.once(event, listener); | ||
| return this; | ||
| } | ||
| off(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.off(event, listener); | ||
| return this; | ||
| } | ||
| removeAllListeners(event) { | ||
| for (const interceptors of this.interceptors) interceptors.removeAllListeners(event); | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { BatchInterceptor as t }; | ||
| //# sourceMappingURL=BatchInterceptor-DdMNfSUY.mjs.map |
| {"version":3,"file":"BatchInterceptor-DdMNfSUY.mjs","names":[],"sources":["../../src/BatchInterceptor.ts"],"sourcesContent":["import { EventMap, Listener } from 'strict-event-emitter'\nimport { Interceptor, ExtractEventNames } from './Interceptor'\n\nexport interface BatchInterceptorOptions<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> {\n name: string\n interceptors: InterceptorList\n}\n\nexport type ExtractEventMapType<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> = InterceptorList extends ReadonlyArray<infer InterceptorType>\n ? InterceptorType extends Interceptor<infer EventMap>\n ? EventMap\n : never\n : never\n\n/**\n * A batch interceptor that exposes a single interface\n * to apply and operate with multiple interceptors at once.\n */\nexport class BatchInterceptor<\n InterceptorList extends ReadonlyArray<Interceptor<any>>,\n Events extends EventMap = ExtractEventMapType<InterceptorList>\n> extends Interceptor<Events> {\n static symbol: symbol\n\n private interceptors: InterceptorList\n\n constructor(options: BatchInterceptorOptions<InterceptorList>) {\n BatchInterceptor.symbol = Symbol(options.name)\n super(BatchInterceptor.symbol)\n this.interceptors = options.interceptors\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('applying all %d interceptors...', this.interceptors.length)\n\n for (const interceptor of this.interceptors) {\n logger.info('applying \"%s\" interceptor...', interceptor.constructor.name)\n interceptor.apply()\n\n logger.info('adding interceptor dispose subscription')\n this.subscriptions.push(() => interceptor.dispose())\n }\n }\n\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n // Instead of adding a listener to the batch interceptor,\n // propagate the listener to each of the individual interceptors.\n for (const interceptor of this.interceptors) {\n interceptor.on(event, listener)\n }\n\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.once(event, listener)\n }\n\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.off(event, listener)\n }\n\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName | undefined\n ): this {\n for (const interceptors of this.interceptors) {\n interceptors.removeAllListeners(event)\n }\n\n return this\n }\n}\n"],"mappings":";;;;;;;AAsBA,IAAa,mBAAb,MAAa,yBAGH,YAAoB;CAK5B,YAAY,SAAmD;AAC7D,mBAAiB,SAAS,OAAO,QAAQ,KAAK;AAC9C,QAAM,iBAAiB,OAAO;AAC9B,OAAK,eAAe,QAAQ;;CAG9B,AAAU,QAAQ;EAChB,MAAM,SAAS,KAAK,OAAO,OAAO,QAAQ;AAE1C,SAAO,KAAK,mCAAmC,KAAK,aAAa,OAAO;AAExE,OAAK,MAAM,eAAe,KAAK,cAAc;AAC3C,UAAO,KAAK,kCAAgC,YAAY,YAAY,KAAK;AACzE,eAAY,OAAO;AAEnB,UAAO,KAAK,0CAA0C;AACtD,QAAK,cAAc,WAAW,YAAY,SAAS,CAAC;;;CAIxD,AAAO,GACL,OACA,UACM;AAGN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,GAAG,OAAO,SAAS;AAGjC,SAAO;;CAGT,AAAO,KACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,KAAK,OAAO,SAAS;AAGnC,SAAO;;CAGT,AAAO,IACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,IAAI,OAAO,SAAS;AAGlC,SAAO;;CAGT,AAAO,mBACL,OACM;AACN,OAAK,MAAM,gBAAgB,KAAK,aAC9B,cAAa,mBAAmB,MAAM;AAGxC,SAAO"} |
| const require_fetchUtils = require('./fetchUtils-umV5xXBy.cjs'); | ||
| //#region src/BatchInterceptor.ts | ||
| /** | ||
| * A batch interceptor that exposes a single interface | ||
| * to apply and operate with multiple interceptors at once. | ||
| */ | ||
| var BatchInterceptor = class BatchInterceptor extends require_fetchUtils.Interceptor { | ||
| constructor(options) { | ||
| BatchInterceptor.symbol = Symbol(options.name); | ||
| super(BatchInterceptor.symbol); | ||
| this.interceptors = options.interceptors; | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("applying all %d interceptors...", this.interceptors.length); | ||
| for (const interceptor of this.interceptors) { | ||
| logger.info("applying \"%s\" interceptor...", interceptor.constructor.name); | ||
| interceptor.apply(); | ||
| logger.info("adding interceptor dispose subscription"); | ||
| this.subscriptions.push(() => interceptor.dispose()); | ||
| } | ||
| } | ||
| on(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.on(event, listener); | ||
| return this; | ||
| } | ||
| once(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.once(event, listener); | ||
| return this; | ||
| } | ||
| off(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.off(event, listener); | ||
| return this; | ||
| } | ||
| removeAllListeners(event) { | ||
| for (const interceptors of this.interceptors) interceptors.removeAllListeners(event); | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'BatchInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return BatchInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=BatchInterceptor-Z1lwK23r.cjs.map |
| {"version":3,"file":"BatchInterceptor-Z1lwK23r.cjs","names":["Interceptor"],"sources":["../../src/BatchInterceptor.ts"],"sourcesContent":["import { EventMap, Listener } from 'strict-event-emitter'\nimport { Interceptor, ExtractEventNames } from './Interceptor'\n\nexport interface BatchInterceptorOptions<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> {\n name: string\n interceptors: InterceptorList\n}\n\nexport type ExtractEventMapType<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> = InterceptorList extends ReadonlyArray<infer InterceptorType>\n ? InterceptorType extends Interceptor<infer EventMap>\n ? EventMap\n : never\n : never\n\n/**\n * A batch interceptor that exposes a single interface\n * to apply and operate with multiple interceptors at once.\n */\nexport class BatchInterceptor<\n InterceptorList extends ReadonlyArray<Interceptor<any>>,\n Events extends EventMap = ExtractEventMapType<InterceptorList>\n> extends Interceptor<Events> {\n static symbol: symbol\n\n private interceptors: InterceptorList\n\n constructor(options: BatchInterceptorOptions<InterceptorList>) {\n BatchInterceptor.symbol = Symbol(options.name)\n super(BatchInterceptor.symbol)\n this.interceptors = options.interceptors\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('applying all %d interceptors...', this.interceptors.length)\n\n for (const interceptor of this.interceptors) {\n logger.info('applying \"%s\" interceptor...', interceptor.constructor.name)\n interceptor.apply()\n\n logger.info('adding interceptor dispose subscription')\n this.subscriptions.push(() => interceptor.dispose())\n }\n }\n\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n // Instead of adding a listener to the batch interceptor,\n // propagate the listener to each of the individual interceptors.\n for (const interceptor of this.interceptors) {\n interceptor.on(event, listener)\n }\n\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.once(event, listener)\n }\n\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.off(event, listener)\n }\n\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName | undefined\n ): this {\n for (const interceptors of this.interceptors) {\n interceptors.removeAllListeners(event)\n }\n\n return this\n }\n}\n"],"mappings":";;;;;;;AAsBA,IAAa,mBAAb,MAAa,yBAGHA,+BAAoB;CAK5B,YAAY,SAAmD;AAC7D,mBAAiB,SAAS,OAAO,QAAQ,KAAK;AAC9C,QAAM,iBAAiB,OAAO;AAC9B,OAAK,eAAe,QAAQ;;CAG9B,AAAU,QAAQ;EAChB,MAAM,SAAS,KAAK,OAAO,OAAO,QAAQ;AAE1C,SAAO,KAAK,mCAAmC,KAAK,aAAa,OAAO;AAExE,OAAK,MAAM,eAAe,KAAK,cAAc;AAC3C,UAAO,KAAK,kCAAgC,YAAY,YAAY,KAAK;AACzE,eAAY,OAAO;AAEnB,UAAO,KAAK,0CAA0C;AACtD,QAAK,cAAc,WAAW,YAAY,SAAS,CAAC;;;CAIxD,AAAO,GACL,OACA,UACM;AAGN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,GAAG,OAAO,SAAS;AAGjC,SAAO;;CAGT,AAAO,KACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,KAAK,OAAO,SAAS;AAGnC,SAAO;;CAGT,AAAO,IACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,IAAI,OAAO,SAAS;AAGlC,SAAO;;CAGT,AAAO,mBACL,OACM;AACN,OAAK,MAAM,gBAAgB,KAAK,aAC9B,cAAa,mBAAmB,MAAM;AAGxC,SAAO"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-umV5xXBy.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-B6znU6il.cjs'); | ||
| const require_handleRequest = require('./handleRequest-DVOthWJo.cjs'); | ||
| const require_node = require('./node-DIKcnzhK.cjs'); | ||
| let _open_draft_logger = require("@open-draft/logger"); | ||
| let outvariant = require("outvariant"); | ||
| let node_http = require("node:http"); | ||
| node_http = require_chunk.__toESM(node_http); | ||
| let node_https = require("node:https"); | ||
| node_https = require_chunk.__toESM(node_https); | ||
| let node_net = require("node:net"); | ||
| node_net = require_chunk.__toESM(node_net); | ||
| let _http_common = require("_http_common"); | ||
| let node_stream = require("node:stream"); | ||
| let node_url = require("node:url"); | ||
| let http = require("http"); | ||
| //#region src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts | ||
| /** | ||
| * Normalizes the arguments provided to the `Writable.prototype.write()` | ||
| * and `Writable.prototype.end()`. | ||
| */ | ||
| function normalizeSocketWriteArgs(args) { | ||
| const normalized = [ | ||
| args[0], | ||
| void 0, | ||
| void 0 | ||
| ]; | ||
| if (typeof args[1] === "string") normalized[1] = args[1]; | ||
| else if (typeof args[1] === "function") normalized[2] = args[1]; | ||
| if (typeof args[2] === "function") normalized[2] = args[2]; | ||
| return normalized; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/Socket/MockSocket.ts | ||
| var MockSocket = class extends node_net.default.Socket { | ||
| constructor(options) { | ||
| super(); | ||
| this.options = options; | ||
| this.connecting = false; | ||
| this.connect(); | ||
| this._final = (callback) => { | ||
| callback(null); | ||
| }; | ||
| } | ||
| connect() { | ||
| this.connecting = true; | ||
| return this; | ||
| } | ||
| write(...args) { | ||
| const [chunk, encoding, callback] = normalizeSocketWriteArgs(args); | ||
| this.options.write(chunk, encoding, callback); | ||
| return true; | ||
| } | ||
| end(...args) { | ||
| const [chunk, encoding, callback] = normalizeSocketWriteArgs(args); | ||
| this.options.write(chunk, encoding, callback); | ||
| return super.end.apply(this, args); | ||
| } | ||
| push(chunk, encoding) { | ||
| this.options.read(chunk, encoding); | ||
| return super.push(chunk, encoding); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts | ||
| function baseUrlFromConnectionOptions(options) { | ||
| if ("href" in options) return new URL(options.href); | ||
| const protocol = options.port === 443 ? "https:" : "http:"; | ||
| const host = options.host; | ||
| const url = new URL(`${protocol}//${host}`); | ||
| if (options.port) url.port = options.port.toString(); | ||
| if (options.path) url.pathname = options.path; | ||
| if (options.auth) { | ||
| const [username, password] = options.auth.split(":"); | ||
| url.username = username; | ||
| url.password = password; | ||
| } | ||
| return url; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/recordRawHeaders.ts | ||
| const kRawHeaders = Symbol("kRawHeaders"); | ||
| const kRestorePatches = Symbol("kRestorePatches"); | ||
| function recordRawHeader(headers, args, behavior) { | ||
| ensureRawHeadersSymbol(headers, []); | ||
| const rawHeaders = Reflect.get(headers, kRawHeaders); | ||
| if (behavior === "set") { | ||
| for (let index = rawHeaders.length - 1; index >= 0; index--) if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) rawHeaders.splice(index, 1); | ||
| } | ||
| rawHeaders.push(args); | ||
| } | ||
| /** | ||
| * Define the raw headers symbol on the given `Headers` instance. | ||
| * If the symbol already exists, this function does nothing. | ||
| */ | ||
| function ensureRawHeadersSymbol(headers, rawHeaders) { | ||
| if (Reflect.has(headers, kRawHeaders)) return; | ||
| defineRawHeadersSymbol(headers, rawHeaders); | ||
| } | ||
| /** | ||
| * Define the raw headers symbol on the given `Headers` instance. | ||
| * If the symbol already exists, it gets overridden. | ||
| */ | ||
| function defineRawHeadersSymbol(headers, rawHeaders) { | ||
| Object.defineProperty(headers, kRawHeaders, { | ||
| value: rawHeaders, | ||
| enumerable: false, | ||
| configurable: true | ||
| }); | ||
| } | ||
| /** | ||
| * Patch the global `Headers` class to store raw headers. | ||
| * This is for compatibility with `IncomingMessage.prototype.rawHeaders`. | ||
| * | ||
| * @note Node.js has their own raw headers symbol but it | ||
| * only records the first header name in case of multi-value headers. | ||
| * Any other headers are normalized before comparing. This makes it | ||
| * incompatible with the `rawHeaders` format. | ||
| * | ||
| * let h = new Headers() | ||
| * h.append('X-Custom', 'one') | ||
| * h.append('x-custom', 'two') | ||
| * h[Symbol('headers map')] // Map { 'X-Custom' => 'one, two' } | ||
| */ | ||
| function recordRawFetchHeaders() { | ||
| if (Reflect.get(Headers, kRestorePatches)) return Reflect.get(Headers, kRestorePatches); | ||
| const { Headers: OriginalHeaders, Request: OriginalRequest, Response: OriginalResponse } = globalThis; | ||
| const { set, append, delete: headersDeleteMethod } = Headers.prototype; | ||
| Object.defineProperty(Headers, kRestorePatches, { | ||
| value: () => { | ||
| Headers.prototype.set = set; | ||
| Headers.prototype.append = append; | ||
| Headers.prototype.delete = headersDeleteMethod; | ||
| globalThis.Headers = OriginalHeaders; | ||
| globalThis.Request = OriginalRequest; | ||
| globalThis.Response = OriginalResponse; | ||
| Object.setPrototypeOf(require_fetchUtils.FetchRequest, OriginalRequest); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchRequest.prototype, OriginalRequest.prototype); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchResponse, OriginalResponse); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchResponse.prototype, OriginalResponse.prototype); | ||
| Reflect.deleteProperty(Headers, kRestorePatches); | ||
| }, | ||
| enumerable: false, | ||
| configurable: true | ||
| }); | ||
| Object.defineProperty(globalThis, "Headers", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Headers, { construct(target, args, newTarget) { | ||
| const headersInit = args[0] || []; | ||
| if (headersInit instanceof Headers && Reflect.has(headersInit, kRawHeaders)) { | ||
| const sanitizedHeaders = Reflect.get(headersInit, kRawHeaders).map((tuple) => [tuple[0], tuple[1]]); | ||
| const headers$1 = Reflect.construct(target, [sanitizedHeaders], newTarget); | ||
| ensureRawHeadersSymbol(headers$1, [...sanitizedHeaders]); | ||
| return headers$1; | ||
| } | ||
| const headers = Reflect.construct(target, args, newTarget); | ||
| if (!Reflect.has(headers, kRawHeaders)) ensureRawHeadersSymbol(headers, Array.isArray(headersInit) ? headersInit : Object.entries(headersInit)); | ||
| return headers; | ||
| } }) | ||
| }); | ||
| Headers.prototype.set = new Proxy(Headers.prototype.set, { apply(target, thisArg, args) { | ||
| recordRawHeader(thisArg, [args[0], args[1]], "set"); | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Headers.prototype.append = new Proxy(Headers.prototype.append, { apply(target, thisArg, args) { | ||
| recordRawHeader(thisArg, [args[0], args[1]], "append"); | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Headers.prototype.delete = new Proxy(Headers.prototype.delete, { apply(target, thisArg, args) { | ||
| const rawHeaders = Reflect.get(thisArg, kRawHeaders); | ||
| if (rawHeaders) { | ||
| for (let index = rawHeaders.length - 1; index >= 0; index--) if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) rawHeaders.splice(index, 1); | ||
| } | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Object.defineProperty(globalThis, "Request", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Request, { construct(target, args, newTarget) { | ||
| const request = Reflect.construct(target, args, newTarget); | ||
| const inferredRawHeaders = []; | ||
| if (typeof args[0] === "object" && args[0].headers != null) inferredRawHeaders.push(...inferRawHeaders(args[0].headers)); | ||
| if (typeof args[1] === "object" && args[1].headers != null) inferredRawHeaders.push(...inferRawHeaders(args[1].headers)); | ||
| if (inferredRawHeaders.length > 0) ensureRawHeadersSymbol(request.headers, inferredRawHeaders); | ||
| return request; | ||
| } }) | ||
| }); | ||
| Object.defineProperty(globalThis, "Response", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Response, { construct(target, args, newTarget) { | ||
| const response = Reflect.construct(target, args, newTarget); | ||
| if (typeof args[1] === "object" && args[1].headers != null) ensureRawHeadersSymbol(response.headers, inferRawHeaders(args[1].headers)); | ||
| return response; | ||
| } }) | ||
| }); | ||
| /** | ||
| * Re-parent FetchRequest/FetchResponse so their `super()` calls go | ||
| * through the proxied globalThis.Request/Response above. Without this, | ||
| * FetchRequest extends the statically-captured (original) Request, | ||
| * bypassing the construct proxy that records raw headers. | ||
| */ | ||
| Object.setPrototypeOf(require_fetchUtils.FetchRequest, globalThis.Request); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchRequest.prototype, globalThis.Request.prototype); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchResponse, globalThis.Response); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchResponse.prototype, globalThis.Response.prototype); | ||
| } | ||
| function restoreHeadersPrototype() { | ||
| if (!Reflect.get(Headers, kRestorePatches)) return; | ||
| Reflect.get(Headers, kRestorePatches)(); | ||
| } | ||
| function getRawFetchHeaders(headers) { | ||
| if (!Reflect.has(headers, kRawHeaders)) return Array.from(headers.entries()); | ||
| const rawHeaders = Reflect.get(headers, kRawHeaders); | ||
| return rawHeaders.length > 0 ? rawHeaders : Array.from(headers.entries()); | ||
| } | ||
| /** | ||
| * Infers the raw headers from the given `HeadersInit` provided | ||
| * to the Request/Response constructor. | ||
| * | ||
| * If the `init.headers` is a Headers instance, use it directly. | ||
| * That means the headers were created standalone and already have | ||
| * the raw headers stored. | ||
| * If the `init.headers` is a HeadersInit, create a new Headers | ||
| * instance out of it. | ||
| */ | ||
| function inferRawHeaders(headers) { | ||
| if (headers instanceof Headers) return Reflect.get(headers, kRawHeaders) || []; | ||
| return Reflect.get(new Headers(headers), kRawHeaders); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/parserUtils.ts | ||
| /** | ||
| * @see https://github.com/nodejs/node/blob/f3adc11e37b8bfaaa026ea85c1cf22e3a0e29ae9/lib/_http_common.js#L180 | ||
| */ | ||
| function freeParser(parser, socket) { | ||
| if (parser._consumed) parser.unconsume(); | ||
| parser._headers = []; | ||
| parser._url = ""; | ||
| parser.socket = null; | ||
| parser.incoming = null; | ||
| parser.outgoing = null; | ||
| parser.maxHeaderPairs = 2e3; | ||
| parser._consumed = false; | ||
| parser.onIncoming = null; | ||
| parser[_http_common.HTTPParser.kOnHeaders] = null; | ||
| parser[_http_common.HTTPParser.kOnHeadersComplete] = null; | ||
| parser[_http_common.HTTPParser.kOnMessageBegin] = null; | ||
| parser[_http_common.HTTPParser.kOnMessageComplete] = null; | ||
| parser[_http_common.HTTPParser.kOnBody] = null; | ||
| parser[_http_common.HTTPParser.kOnExecute] = null; | ||
| parser[_http_common.HTTPParser.kOnTimeout] = null; | ||
| parser.remove(); | ||
| parser.free(); | ||
| if (socket) | ||
| /** | ||
| * @note Unassigning the socket's parser will fail this assertion | ||
| * if there's still some data being processed on the socket: | ||
| * @see https://github.com/nodejs/node/blob/4e1f39b678b37017ac9baa0971e3aeecd3b67b51/lib/_http_client.js#L613 | ||
| */ | ||
| if (socket.destroyed) socket.parser = null; | ||
| else socket.once("end", () => { | ||
| socket.parser = null; | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/MockHttpSocket.ts | ||
| const kRequestId = Symbol("kRequestId"); | ||
| var MockHttpSocket = class extends MockSocket { | ||
| constructor(options) { | ||
| super({ | ||
| write: (chunk, encoding, callback) => { | ||
| if (this.socketState !== "passthrough") this.writeBuffer.push([ | ||
| chunk, | ||
| encoding, | ||
| callback | ||
| ]); | ||
| if (chunk) { | ||
| /** | ||
| * Forward any writes to the mock socket to the underlying original socket. | ||
| * This ensures functional duplex connections, like WebSocket. | ||
| * @see https://github.com/mswjs/interceptors/issues/682 | ||
| */ | ||
| if (this.socketState === "passthrough") this.originalSocket?.write(chunk, encoding, callback); | ||
| this.requestParser.execute(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding)); | ||
| } | ||
| }, | ||
| read: (chunk) => { | ||
| if (chunk !== null) | ||
| /** | ||
| * @todo We need to free the parser if the connection has been | ||
| * upgraded to a non-HTTP protocol. It won't be able to parse data | ||
| * from that point onward anyway. No need to keep it in memory. | ||
| */ | ||
| this.responseParser.execute(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); | ||
| } | ||
| }); | ||
| this.requestRawHeadersBuffer = []; | ||
| this.responseRawHeadersBuffer = []; | ||
| this.writeBuffer = []; | ||
| this.socketState = "unknown"; | ||
| this.onRequestHeaders = (rawHeaders) => { | ||
| this.requestRawHeadersBuffer.push(...rawHeaders); | ||
| }; | ||
| this.onRequestStart = (versionMajor, versionMinor, rawHeaders, _, path, __, ___, ____, shouldKeepAlive) => { | ||
| this.shouldKeepAlive = shouldKeepAlive; | ||
| const url = new URL(path || "", this.baseUrl); | ||
| const method = this.connectionOptions.method?.toUpperCase() || "GET"; | ||
| const headers = require_fetchUtils.FetchResponse.parseRawHeaders([...this.requestRawHeadersBuffer, ...rawHeaders || []]); | ||
| this.requestRawHeadersBuffer.length = 0; | ||
| const canHaveBody = method !== "GET" && method !== "HEAD"; | ||
| if (url.username || url.password) { | ||
| if (!headers.has("authorization")) headers.set("authorization", `Basic ${url.username}:${url.password}`); | ||
| url.username = ""; | ||
| url.password = ""; | ||
| } | ||
| this.requestStream = new node_stream.Readable({ read: () => { | ||
| this.flushWriteBuffer(); | ||
| } }); | ||
| const requestId = require_fetchUtils.createRequestId(); | ||
| this.request = new require_fetchUtils.FetchRequest(url, { | ||
| method, | ||
| headers, | ||
| credentials: "same-origin", | ||
| duplex: canHaveBody ? "half" : void 0, | ||
| body: canHaveBody ? node_stream.Readable.toWeb(this.requestStream) : null | ||
| }); | ||
| Reflect.set(this.request, kRequestId, requestId); | ||
| require_getRawRequest.setRawRequest(this.request, Reflect.get(this, "_httpMessage")); | ||
| require_node.setRawRequestBodyStream(this.request, this.requestStream); | ||
| /** | ||
| * @fixme Stop relying on the "X-Request-Id" request header | ||
| * to figure out if one interceptor has been invoked within another. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| if (this.request.headers.has(require_fetchUtils.INTERNAL_REQUEST_ID_HEADER_NAME)) { | ||
| this.passthrough(); | ||
| return; | ||
| } | ||
| this.onRequest({ | ||
| requestId, | ||
| request: this.request, | ||
| socket: this | ||
| }); | ||
| }; | ||
| this.onResponseHeaders = (rawHeaders) => { | ||
| this.responseRawHeadersBuffer.push(...rawHeaders); | ||
| }; | ||
| this.onResponseStart = (versionMajor, versionMinor, rawHeaders, method, url, status, statusText) => { | ||
| const headers = require_fetchUtils.FetchResponse.parseRawHeaders([...this.responseRawHeadersBuffer, ...rawHeaders || []]); | ||
| this.responseRawHeadersBuffer.length = 0; | ||
| const response = new require_fetchUtils.FetchResponse( | ||
| /** | ||
| * @note The Fetch API response instance exposed to the consumer | ||
| * is created over the response stream of the HTTP parser. It is NOT | ||
| * related to the Socket instance. This way, you can read response body | ||
| * in response listener while the Socket instance delays the emission | ||
| * of "end" and other events until those response listeners are finished. | ||
| */ | ||
| require_fetchUtils.FetchResponse.isResponseWithBody(status) ? node_stream.Readable.toWeb(this.responseStream = new node_stream.Readable({ read() {} })) : null, | ||
| { | ||
| url, | ||
| status, | ||
| statusText, | ||
| headers | ||
| } | ||
| ); | ||
| (0, outvariant.invariant)(this.request, "Failed to handle a response: request does not exist"); | ||
| require_fetchUtils.FetchResponse.setUrl(this.request.url, response); | ||
| /** | ||
| * @fixme Stop relying on the "X-Request-Id" request header | ||
| * to figure out if one interceptor has been invoked within another. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| if (this.request.headers.has(require_fetchUtils.INTERNAL_REQUEST_ID_HEADER_NAME)) return; | ||
| this.responseListenersPromise = this.onResponse({ | ||
| response, | ||
| isMockedResponse: this.socketState === "mock", | ||
| requestId: Reflect.get(this.request, kRequestId), | ||
| request: this.request, | ||
| socket: this | ||
| }); | ||
| }; | ||
| this.connectionOptions = options.connectionOptions; | ||
| this.createConnection = options.createConnection; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions); | ||
| this.requestParser = new _http_common.HTTPParser(); | ||
| this.requestParser.initialize(_http_common.HTTPParser.REQUEST, {}); | ||
| this.requestParser[_http_common.HTTPParser.kOnHeaders] = this.onRequestHeaders.bind(this); | ||
| this.requestParser[_http_common.HTTPParser.kOnHeadersComplete] = this.onRequestStart.bind(this); | ||
| this.requestParser[_http_common.HTTPParser.kOnBody] = this.onRequestBody.bind(this); | ||
| this.requestParser[_http_common.HTTPParser.kOnMessageComplete] = this.onRequestEnd.bind(this); | ||
| this.responseParser = new _http_common.HTTPParser(); | ||
| this.responseParser.initialize(_http_common.HTTPParser.RESPONSE, {}); | ||
| this.responseParser[_http_common.HTTPParser.kOnHeaders] = this.onResponseHeaders.bind(this); | ||
| this.responseParser[_http_common.HTTPParser.kOnHeadersComplete] = this.onResponseStart.bind(this); | ||
| this.responseParser[_http_common.HTTPParser.kOnBody] = this.onResponseBody.bind(this); | ||
| this.responseParser[_http_common.HTTPParser.kOnMessageComplete] = this.onResponseEnd.bind(this); | ||
| this.once("finish", () => freeParser(this.requestParser, this)); | ||
| if (this.baseUrl.protocol === "https:") { | ||
| Reflect.set(this, "encrypted", true); | ||
| Reflect.set(this, "authorized", false); | ||
| Reflect.set(this, "getProtocol", () => "TLSv1.3"); | ||
| Reflect.set(this, "getSession", () => void 0); | ||
| Reflect.set(this, "isSessionReused", () => false); | ||
| Reflect.set(this, "getCipher", () => ({ | ||
| name: "AES256-SHA", | ||
| standardName: "TLS_RSA_WITH_AES_256_CBC_SHA", | ||
| version: "TLSv1.3" | ||
| })); | ||
| } | ||
| } | ||
| emit(event, ...args) { | ||
| const emitEvent = super.emit.bind(this, event, ...args); | ||
| if (this.responseListenersPromise) { | ||
| this.responseListenersPromise.finally(emitEvent); | ||
| return this.listenerCount(event) > 0; | ||
| } | ||
| return emitEvent(); | ||
| } | ||
| destroy(error) { | ||
| freeParser(this.responseParser, this); | ||
| if (error) this.emit("error", error); | ||
| return super.destroy(error); | ||
| } | ||
| /** | ||
| * Establish this Socket connection as-is and pipe | ||
| * its data/events through this Socket. | ||
| */ | ||
| passthrough() { | ||
| this.socketState = "passthrough"; | ||
| if (this.destroyed) return; | ||
| const socket = this.createConnection(); | ||
| this.originalSocket = socket; | ||
| /** | ||
| * @note Inherit the original socket's connection handle. | ||
| * Without this, each push to the mock socket results in a | ||
| * new "connection" listener being added (i.e. buffering pushes). | ||
| * @see https://github.com/nodejs/node/blob/b18153598b25485ce4f54d0c5cb830a9457691ee/lib/net.js#L734 | ||
| */ | ||
| if ("_handle" in socket) Object.defineProperty(this, "_handle", { | ||
| value: socket._handle, | ||
| enumerable: true, | ||
| writable: true | ||
| }); | ||
| this.once("close", () => { | ||
| socket.removeAllListeners(); | ||
| if (!socket.destroyed) socket.destroy(); | ||
| this.originalSocket = void 0; | ||
| }); | ||
| this.address = socket.address.bind(socket); | ||
| let writeArgs; | ||
| let headersWritten = false; | ||
| while (writeArgs = this.writeBuffer.shift()) if (writeArgs !== void 0) { | ||
| if (!headersWritten) { | ||
| const [chunk, encoding, callback] = writeArgs; | ||
| const chunkString = chunk.toString(); | ||
| const chunkBeforeRequestHeaders = chunkString.slice(0, chunkString.indexOf("\r\n") + 2); | ||
| const chunkAfterRequestHeaders = chunkString.slice(chunk.indexOf("\r\n\r\n")); | ||
| const headersChunk = `${chunkBeforeRequestHeaders}${getRawFetchHeaders(this.request.headers).filter(([name]) => { | ||
| return name.toLowerCase() !== require_fetchUtils.INTERNAL_REQUEST_ID_HEADER_NAME; | ||
| }).map(([name, value]) => `${name}: ${value}`).join("\r\n")}${chunkAfterRequestHeaders}`; | ||
| socket.write(headersChunk, encoding, callback); | ||
| headersWritten = true; | ||
| continue; | ||
| } | ||
| socket.write(...writeArgs); | ||
| } | ||
| if (Reflect.get(socket, "encrypted")) [ | ||
| "encrypted", | ||
| "authorized", | ||
| "getProtocol", | ||
| "getSession", | ||
| "isSessionReused", | ||
| "getCipher" | ||
| ].forEach((propertyName) => { | ||
| Object.defineProperty(this, propertyName, { | ||
| enumerable: true, | ||
| get: () => { | ||
| const value = Reflect.get(socket, propertyName); | ||
| return typeof value === "function" ? value.bind(socket) : value; | ||
| } | ||
| }); | ||
| }); | ||
| socket.on("lookup", (...args) => this.emit("lookup", ...args)).on("connect", () => { | ||
| this.connecting = socket.connecting; | ||
| this.emit("connect"); | ||
| }).on("secureConnect", () => this.emit("secureConnect")).on("secure", () => this.emit("secure")).on("session", (session) => this.emit("session", session)).on("ready", () => this.emit("ready")).on("drain", () => this.emit("drain")).on("data", (chunk) => { | ||
| this.push(chunk); | ||
| }).on("error", (error) => { | ||
| Reflect.set(this, "_hadError", Reflect.get(socket, "_hadError")); | ||
| this.emit("error", error); | ||
| }).on("resume", () => this.emit("resume")).on("timeout", () => this.emit("timeout")).on("prefinish", () => this.emit("prefinish")).on("finish", () => this.emit("finish")).on("close", (hadError) => this.emit("close", hadError)).on("end", () => this.emit("end")); | ||
| } | ||
| /** | ||
| * Convert the given Fetch API `Response` instance to an | ||
| * HTTP message and push it to the socket. | ||
| */ | ||
| async respondWith(response) { | ||
| if (this.destroyed) return; | ||
| (0, outvariant.invariant)(this.socketState !== "mock", "[MockHttpSocket] Failed to respond to the \"%s %s\" request with \"%s %s\": the request has already been handled", this.request?.method, this.request?.url, response.status, response.statusText); | ||
| if (require_handleRequest.isPropertyAccessible(response, "type") && response.type === "error") { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| this.mockConnect(); | ||
| this.socketState = "mock"; | ||
| this.flushWriteBuffer(); | ||
| const serverResponse = new node_http.ServerResponse(new node_http.IncomingMessage(this)); | ||
| /** | ||
| * Assign a mock socket instance to the server response to | ||
| * spy on the response chunk writes. Push the transformed response chunks | ||
| * to this `MockHttpSocket` instance to trigger the "data" event. | ||
| * @note Providing the same `MockSocket` instance when creating `ServerResponse` | ||
| * does not have the same effect. | ||
| * @see https://github.com/nodejs/node/blob/10099bb3f7fd97bb9dd9667188426866b3098e07/test/parallel/test-http-server-response-standalone.js#L32 | ||
| */ | ||
| serverResponse.assignSocket(new MockSocket({ | ||
| write: (chunk, encoding, callback) => { | ||
| this.push(chunk, encoding); | ||
| callback?.(); | ||
| }, | ||
| read() {} | ||
| })); | ||
| /** | ||
| * @note Remove the `Connection` and `Date` response headers | ||
| * injected by `ServerResponse` by default. Those are required | ||
| * from the server but the interceptor is NOT technically a server. | ||
| * It's confusing to add response headers that the developer didn't | ||
| * specify themselves. They can always add these if they wish. | ||
| * @see https://www.rfc-editor.org/rfc/rfc9110#field.date | ||
| * @see https://www.rfc-editor.org/rfc/rfc9110#field.connection | ||
| */ | ||
| serverResponse.removeHeader("connection"); | ||
| serverResponse.removeHeader("date"); | ||
| const rawResponseHeaders = getRawFetchHeaders(response.headers); | ||
| /** | ||
| * @note Call `.writeHead` in order to set the raw response headers | ||
| * in the same case as they were provided by the developer. Using | ||
| * `.setHeader()`/`.appendHeader()` normalizes header names. | ||
| */ | ||
| serverResponse.writeHead(response.status, response.statusText || node_http.STATUS_CODES[response.status], rawResponseHeaders); | ||
| this.once("error", () => { | ||
| serverResponse.destroy(); | ||
| }); | ||
| if (response.body) try { | ||
| const reader = response.body.getReader(); | ||
| while (true) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) { | ||
| serverResponse.end(); | ||
| break; | ||
| } | ||
| serverResponse.write(value); | ||
| } | ||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| serverResponse.destroy(); | ||
| /** | ||
| * @note Destroy the request socket gracefully. | ||
| * Response stream errors do NOT produce request errors. | ||
| */ | ||
| this.destroy(); | ||
| return; | ||
| } | ||
| serverResponse.destroy(); | ||
| throw error; | ||
| } | ||
| else serverResponse.end(); | ||
| if (!this.shouldKeepAlive) { | ||
| this.emit("readable"); | ||
| /** | ||
| * @todo @fixme This is likely a hack. | ||
| * Since we push null to the socket, it never propagates to the | ||
| * parser, and the parser never calls "onResponseEnd" to close | ||
| * the response stream. We are closing the stream here manually | ||
| * but that shouldn't be the case. | ||
| */ | ||
| this.responseStream?.push(null); | ||
| this.push(null); | ||
| } | ||
| } | ||
| /** | ||
| * Close this socket connection with the given error. | ||
| */ | ||
| errorWith(error) { | ||
| this.destroy(error); | ||
| } | ||
| mockConnect() { | ||
| this.connecting = false; | ||
| const isIPv6 = node_net.default.isIPv6(this.connectionOptions.hostname) || this.connectionOptions.family === 6; | ||
| const addressInfo = { | ||
| address: isIPv6 ? "::1" : "127.0.0.1", | ||
| family: isIPv6 ? "IPv6" : "IPv4", | ||
| port: this.connectionOptions.port | ||
| }; | ||
| this.address = () => addressInfo; | ||
| this.emit("lookup", null, addressInfo.address, addressInfo.family === "IPv6" ? 6 : 4, this.connectionOptions.host); | ||
| this.emit("connect"); | ||
| this.emit("ready"); | ||
| if (this.baseUrl.protocol === "https:") { | ||
| this.emit("secure"); | ||
| this.emit("secureConnect"); | ||
| this.emit("session", this.connectionOptions.session || Buffer.from("mock-session-renegotiate")); | ||
| this.emit("session", Buffer.from("mock-session-resume")); | ||
| } | ||
| } | ||
| flushWriteBuffer() { | ||
| for (const writeCall of this.writeBuffer) if (typeof writeCall[2] === "function") { | ||
| writeCall[2](); | ||
| /** | ||
| * @note Remove the callback from the write call | ||
| * so it doesn't get called twice on passthrough | ||
| * if `request.end()` was called within `request.write()`. | ||
| * @see https://github.com/mswjs/interceptors/issues/684 | ||
| */ | ||
| writeCall[2] = void 0; | ||
| } | ||
| } | ||
| onRequestBody(chunk) { | ||
| (0, outvariant.invariant)(this.requestStream, "Failed to write to a request stream: stream does not exist"); | ||
| this.requestStream.push(chunk); | ||
| } | ||
| onRequestEnd() { | ||
| if (this.requestStream) this.requestStream.push(null); | ||
| } | ||
| onResponseBody(chunk) { | ||
| (0, outvariant.invariant)(this.responseStream, "Failed to write to a response stream: stream does not exist"); | ||
| this.responseStream.push(chunk); | ||
| } | ||
| onResponseEnd() { | ||
| if (this.responseStream) this.responseStream.push(null); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/agents.ts | ||
| var MockAgent = class extends node_http.default.Agent { | ||
| constructor(options) { | ||
| super(); | ||
| this.customAgent = options.customAgent; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| } | ||
| createConnection(options, callback) { | ||
| const createConnection = this.customAgent instanceof node_http.default.Agent ? this.customAgent.createConnection : super.createConnection; | ||
| const createConnectionOptions = this.customAgent instanceof node_http.default.Agent ? { | ||
| ...options, | ||
| ...this.customAgent.options | ||
| } : options; | ||
| return new MockHttpSocket({ | ||
| connectionOptions: options, | ||
| createConnection: createConnection.bind(this.customAgent || this, createConnectionOptions, callback), | ||
| onRequest: this.onRequest.bind(this), | ||
| onResponse: this.onResponse.bind(this) | ||
| }); | ||
| } | ||
| }; | ||
| var MockHttpsAgent = class extends node_https.default.Agent { | ||
| constructor(options) { | ||
| super(); | ||
| this.customAgent = options.customAgent; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| } | ||
| createConnection(options, callback) { | ||
| const createConnection = this.customAgent instanceof node_http.default.Agent ? this.customAgent.createConnection : super.createConnection; | ||
| const createConnectionOptions = this.customAgent instanceof node_http.default.Agent ? { | ||
| ...options, | ||
| ...this.customAgent.options | ||
| } : options; | ||
| return new MockHttpSocket({ | ||
| connectionOptions: options, | ||
| createConnection: createConnection.bind(this.customAgent || this, createConnectionOptions, callback), | ||
| onRequest: this.onRequest.bind(this), | ||
| onResponse: this.onResponse.bind(this) | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/utils/getUrlByRequestOptions.ts | ||
| const logger$2 = new _open_draft_logger.Logger("utils getUrlByRequestOptions"); | ||
| const DEFAULT_PATH = "/"; | ||
| const DEFAULT_PROTOCOL = "http:"; | ||
| const DEFAULT_HOSTNAME = "localhost"; | ||
| const SSL_PORT = 443; | ||
| function getAgent(options) { | ||
| return options.agent instanceof http.Agent ? options.agent : void 0; | ||
| } | ||
| function getProtocolByRequestOptions(options) { | ||
| if (options.protocol) return options.protocol; | ||
| const agentProtocol = getAgent(options)?.protocol; | ||
| if (agentProtocol) return agentProtocol; | ||
| const port = getPortByRequestOptions(options); | ||
| return options.cert || port === SSL_PORT ? "https:" : options.uri?.protocol || DEFAULT_PROTOCOL; | ||
| } | ||
| function getPortByRequestOptions(options) { | ||
| if (options.port) return Number(options.port); | ||
| const agent = getAgent(options); | ||
| if (agent?.options.port) return Number(agent.options.port); | ||
| if (agent?.defaultPort) return Number(agent.defaultPort); | ||
| } | ||
| function getAuthByRequestOptions(options) { | ||
| if (options.auth) { | ||
| const [username, password] = options.auth.split(":"); | ||
| return { | ||
| username, | ||
| password | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Returns true if host looks like an IPv6 address without surrounding brackets | ||
| * It assumes any host containing `:` is definitely not IPv4 and probably IPv6, | ||
| * but note that this could include invalid IPv6 addresses as well. | ||
| */ | ||
| function isRawIPv6Address(host) { | ||
| return host.includes(":") && !host.startsWith("[") && !host.endsWith("]"); | ||
| } | ||
| function getHostname(options) { | ||
| let host = options.hostname || options.host; | ||
| if (host) { | ||
| if (isRawIPv6Address(host)) host = `[${host}]`; | ||
| return new URL(`http://${host}`).hostname; | ||
| } | ||
| return DEFAULT_HOSTNAME; | ||
| } | ||
| /** | ||
| * Creates a `URL` instance from a given `RequestOptions` object. | ||
| */ | ||
| function getUrlByRequestOptions(options) { | ||
| logger$2.info("request options", options); | ||
| if (options.uri) { | ||
| logger$2.info("constructing url from explicitly provided \"options.uri\": %s", options.uri); | ||
| return new URL(options.uri.href); | ||
| } | ||
| logger$2.info("figuring out url from request options..."); | ||
| const protocol = getProtocolByRequestOptions(options); | ||
| logger$2.info("protocol", protocol); | ||
| const port = getPortByRequestOptions(options); | ||
| logger$2.info("port", port); | ||
| const hostname = getHostname(options); | ||
| logger$2.info("hostname", hostname); | ||
| const path = options.path || DEFAULT_PATH; | ||
| logger$2.info("path", path); | ||
| const credentials = getAuthByRequestOptions(options); | ||
| logger$2.info("credentials", credentials); | ||
| const authString = credentials ? `${credentials.username}:${credentials.password}@` : ""; | ||
| logger$2.info("auth string:", authString); | ||
| const portString = typeof port !== "undefined" ? `:${port}` : ""; | ||
| const url = new URL(`${protocol}//${hostname}${portString}${path}`); | ||
| url.username = credentials?.username || ""; | ||
| url.password = credentials?.password || ""; | ||
| logger$2.info("created url:", url); | ||
| return url; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/cloneObject.ts | ||
| const logger$1 = new _open_draft_logger.Logger("cloneObject"); | ||
| function isPlainObject(obj) { | ||
| logger$1.info("is plain object?", obj); | ||
| if (obj == null || !obj.constructor?.name) { | ||
| logger$1.info("given object is undefined, not a plain object..."); | ||
| return false; | ||
| } | ||
| logger$1.info("checking the object constructor:", obj.constructor.name); | ||
| return obj.constructor.name === "Object"; | ||
| } | ||
| function cloneObject(obj) { | ||
| logger$1.info("cloning object:", obj); | ||
| const enumerableProperties = Object.entries(obj).reduce((acc, [key, value]) => { | ||
| logger$1.info("analyzing key-value pair:", key, value); | ||
| acc[key] = isPlainObject(value) ? cloneObject(value) : value; | ||
| return acc; | ||
| }, {}); | ||
| return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts | ||
| const logger = new _open_draft_logger.Logger("http normalizeClientRequestArgs"); | ||
| function resolveRequestOptions(args, url) { | ||
| if (typeof args[1] === "undefined" || typeof args[1] === "function") { | ||
| logger.info("request options not provided, deriving from the url", url); | ||
| return (0, node_url.urlToHttpOptions)(url); | ||
| } | ||
| if (args[1]) { | ||
| logger.info("has custom RequestOptions!", args[1]); | ||
| const requestOptionsFromUrl = (0, node_url.urlToHttpOptions)(url); | ||
| logger.info("derived RequestOptions from the URL:", requestOptionsFromUrl); | ||
| /** | ||
| * Clone the request options to lock their state | ||
| * at the moment they are provided to `ClientRequest`. | ||
| * @see https://github.com/mswjs/interceptors/issues/86 | ||
| */ | ||
| logger.info("cloning RequestOptions..."); | ||
| const clonedRequestOptions = cloneObject(args[1]); | ||
| logger.info("successfully cloned RequestOptions!", clonedRequestOptions); | ||
| return { | ||
| ...requestOptionsFromUrl, | ||
| ...clonedRequestOptions | ||
| }; | ||
| } | ||
| logger.info("using an empty object as request options"); | ||
| return {}; | ||
| } | ||
| /** | ||
| * Overrides the given `URL` instance with the explicit properties provided | ||
| * on the `RequestOptions` object. The options object takes precedence, | ||
| * and will replace URL properties like "host", "path", and "port", if specified. | ||
| */ | ||
| function overrideUrlByRequestOptions(url, options) { | ||
| url.host = options.host || url.host; | ||
| url.hostname = options.hostname || url.hostname; | ||
| url.port = options.port ? options.port.toString() : url.port; | ||
| if (options.path) { | ||
| const parsedOptionsPath = (0, node_url.parse)(options.path, false); | ||
| url.pathname = parsedOptionsPath.pathname || ""; | ||
| url.search = parsedOptionsPath.search || ""; | ||
| } | ||
| return url; | ||
| } | ||
| function resolveCallback(args) { | ||
| return typeof args[1] === "function" ? args[1] : args[2]; | ||
| } | ||
| /** | ||
| * Normalizes parameters given to a `http.request` call | ||
| * so it always has a `URL` and `RequestOptions`. | ||
| */ | ||
| function normalizeClientRequestArgs(defaultProtocol, args) { | ||
| let url; | ||
| let options; | ||
| let callback; | ||
| logger.info("arguments", args); | ||
| logger.info("using default protocol:", defaultProtocol); | ||
| if (args.length === 0) { | ||
| const url$1 = new node_url.URL("http://localhost"); | ||
| return [url$1, resolveRequestOptions(args, url$1)]; | ||
| } | ||
| if (typeof args[0] === "string") { | ||
| logger.info("first argument is a location string:", args[0]); | ||
| url = new node_url.URL(args[0]); | ||
| logger.info("created a url:", url); | ||
| const requestOptionsFromUrl = (0, node_url.urlToHttpOptions)(url); | ||
| logger.info("request options from url:", requestOptionsFromUrl); | ||
| options = resolveRequestOptions(args, url); | ||
| logger.info("resolved request options:", options); | ||
| callback = resolveCallback(args); | ||
| } else if (args[0] instanceof node_url.URL) { | ||
| url = args[0]; | ||
| logger.info("first argument is a URL:", url); | ||
| if (typeof args[1] !== "undefined" && require_handleRequest.isObject(args[1])) url = overrideUrlByRequestOptions(url, args[1]); | ||
| options = resolveRequestOptions(args, url); | ||
| logger.info("derived request options:", options); | ||
| callback = resolveCallback(args); | ||
| } else if ("hash" in args[0] && !("method" in args[0])) { | ||
| const [legacyUrl] = args; | ||
| logger.info("first argument is a legacy URL:", legacyUrl); | ||
| if (legacyUrl.hostname === null) { | ||
| /** | ||
| * We are dealing with a relative url, so use the path as an "option" and | ||
| * merge in any existing options, giving priority to existing options -- i.e. a path in any | ||
| * existing options will take precedence over the one contained in the url. This is consistent | ||
| * with the behaviour in ClientRequest. | ||
| * @see https://github.com/nodejs/node/blob/d84f1312915fe45fe0febe888db692c74894c382/lib/_http_client.js#L122 | ||
| */ | ||
| logger.info("given legacy URL is relative (no hostname)"); | ||
| return require_handleRequest.isObject(args[1]) ? normalizeClientRequestArgs(defaultProtocol, [{ | ||
| path: legacyUrl.path, | ||
| ...args[1] | ||
| }, args[2]]) : normalizeClientRequestArgs(defaultProtocol, [{ path: legacyUrl.path }, args[1]]); | ||
| } | ||
| logger.info("given legacy url is absolute"); | ||
| const resolvedUrl = new node_url.URL(legacyUrl.href); | ||
| return args[1] === void 0 ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl]) : typeof args[1] === "function" ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl, args[1]]) : normalizeClientRequestArgs(defaultProtocol, [ | ||
| resolvedUrl, | ||
| args[1], | ||
| args[2] | ||
| ]); | ||
| } else if (require_handleRequest.isObject(args[0])) { | ||
| options = { ...args[0] }; | ||
| logger.info("first argument is RequestOptions:", options); | ||
| options.protocol = options.protocol || defaultProtocol; | ||
| logger.info("normalized request options:", options); | ||
| url = getUrlByRequestOptions(options); | ||
| logger.info("created a URL from RequestOptions:", url.href); | ||
| callback = resolveCallback(args); | ||
| } else throw new Error(`Failed to construct ClientRequest with these parameters: ${args}`); | ||
| options.protocol = options.protocol || url.protocol; | ||
| options.method = options.method || "GET"; | ||
| /** | ||
| * Ensure that the default Agent is always set. | ||
| * This prevents the protocol mismatch for requests with { agent: false }, | ||
| * where the global Agent is inferred. | ||
| * @see https://github.com/mswjs/msw/issues/1150 | ||
| * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L130 | ||
| * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L157-L159 | ||
| */ | ||
| if (!options._defaultAgent) { | ||
| logger.info("has no default agent, setting the default agent for \"%s\"", options.protocol); | ||
| options._defaultAgent = options.protocol === "https:" ? node_https.globalAgent : node_http.globalAgent; | ||
| } | ||
| logger.info("successfully resolved url:", url.href); | ||
| logger.info("successfully resolved options:", options); | ||
| logger.info("successfully resolved callback:", callback); | ||
| /** | ||
| * @note If the user-provided URL is not a valid URL in Node.js, | ||
| * (e.g. the one provided by the JSDOM polyfills), case it to | ||
| * string. Otherwise, this throws on Node.js incompatibility | ||
| * (`ERR_INVALID_ARG_TYPE` on the connection listener) | ||
| * @see https://github.com/node-fetch/node-fetch/issues/1376#issuecomment-966435555 | ||
| */ | ||
| if (!(url instanceof node_url.URL)) url = url.toString(); | ||
| return [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/index.ts | ||
| var ClientRequestInterceptor = class ClientRequestInterceptor extends require_fetchUtils.Interceptor { | ||
| static { | ||
| this.symbol = Symbol("client-request-interceptor"); | ||
| } | ||
| constructor() { | ||
| super(ClientRequestInterceptor.symbol); | ||
| this.onRequest = async ({ request, socket }) => { | ||
| const controller = new require_fetchUtils.RequestController(request, { | ||
| passthrough() { | ||
| socket.passthrough(); | ||
| }, | ||
| async respondWith(response) { | ||
| await socket.respondWith(response); | ||
| }, | ||
| errorWith(reason) { | ||
| if (reason instanceof Error) socket.errorWith(reason); | ||
| } | ||
| }); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId: Reflect.get(request, kRequestId), | ||
| controller, | ||
| emitter: this.emitter | ||
| }); | ||
| }; | ||
| this.onResponse = async ({ requestId, request, response, isMockedResponse }) => { | ||
| return require_handleRequest.emitAsync(this.emitter, "response", { | ||
| requestId, | ||
| request, | ||
| response, | ||
| isMockedResponse | ||
| }); | ||
| }; | ||
| } | ||
| setup() { | ||
| const { ClientRequest: OriginalClientRequest, get: originalGet, request: originalRequest } = node_http.default; | ||
| const { get: originalHttpsGet, request: originalHttpsRequest } = node_https.default; | ||
| const onRequest = this.onRequest.bind(this); | ||
| const onResponse = this.onResponse.bind(this); | ||
| node_http.default.ClientRequest = new Proxy(node_http.default.ClientRequest, { construct: (target, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new (options.protocol === "https:" ? MockHttpsAgent : MockAgent)({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.construct(target, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| node_http.default.request = new Proxy(node_http.default.request, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new MockAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| node_http.default.get = new Proxy(node_http.default.get, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new MockAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| node_https.default.request = new Proxy(node_https.default.request, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("https:", args); | ||
| options.agent = new MockHttpsAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| node_https.default.get = new Proxy(node_https.default.get, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("https:", args); | ||
| options.agent = new MockHttpsAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| recordRawFetchHeaders(); | ||
| this.subscriptions.push(() => { | ||
| node_http.default.ClientRequest = OriginalClientRequest; | ||
| node_http.default.get = originalGet; | ||
| node_http.default.request = originalRequest; | ||
| node_https.default.get = originalHttpsGet; | ||
| node_https.default.request = originalHttpsRequest; | ||
| restoreHeadersPrototype(); | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'ClientRequestInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return ClientRequestInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=ClientRequest-DFs7FPNi.cjs.map |
Sorry, the diff of this file is too big to display
| import { a as RequestController, c as Interceptor, i as createRequestId, n as FetchResponse, s as INTERNAL_REQUEST_ID_HEADER_NAME, t as FetchRequest } from "./fetchUtils-BKJ1XmiO.mjs"; | ||
| import { n as setRawRequest } from "./getRawRequest-C2-1urzA.mjs"; | ||
| import { a as isPropertyAccessible, i as emitAsync, r as isObject, t as handleRequest } from "./handleRequest-DCLzePtS.mjs"; | ||
| import { n as setRawRequestBodyStream } from "./node-lsdNwZEW.mjs"; | ||
| import { Logger } from "@open-draft/logger"; | ||
| import { invariant } from "outvariant"; | ||
| import http, { IncomingMessage, STATUS_CODES, ServerResponse, globalAgent } from "node:http"; | ||
| import https, { globalAgent as globalAgent$1 } from "node:https"; | ||
| import net from "node:net"; | ||
| import { HTTPParser } from "_http_common"; | ||
| import { Readable } from "node:stream"; | ||
| import { URL as URL$1, parse, urlToHttpOptions } from "node:url"; | ||
| import { Agent } from "http"; | ||
| //#region src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts | ||
| /** | ||
| * Normalizes the arguments provided to the `Writable.prototype.write()` | ||
| * and `Writable.prototype.end()`. | ||
| */ | ||
| function normalizeSocketWriteArgs(args) { | ||
| const normalized = [ | ||
| args[0], | ||
| void 0, | ||
| void 0 | ||
| ]; | ||
| if (typeof args[1] === "string") normalized[1] = args[1]; | ||
| else if (typeof args[1] === "function") normalized[2] = args[1]; | ||
| if (typeof args[2] === "function") normalized[2] = args[2]; | ||
| return normalized; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/Socket/MockSocket.ts | ||
| var MockSocket = class extends net.Socket { | ||
| constructor(options) { | ||
| super(); | ||
| this.options = options; | ||
| this.connecting = false; | ||
| this.connect(); | ||
| this._final = (callback) => { | ||
| callback(null); | ||
| }; | ||
| } | ||
| connect() { | ||
| this.connecting = true; | ||
| return this; | ||
| } | ||
| write(...args) { | ||
| const [chunk, encoding, callback] = normalizeSocketWriteArgs(args); | ||
| this.options.write(chunk, encoding, callback); | ||
| return true; | ||
| } | ||
| end(...args) { | ||
| const [chunk, encoding, callback] = normalizeSocketWriteArgs(args); | ||
| this.options.write(chunk, encoding, callback); | ||
| return super.end.apply(this, args); | ||
| } | ||
| push(chunk, encoding) { | ||
| this.options.read(chunk, encoding); | ||
| return super.push(chunk, encoding); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts | ||
| function baseUrlFromConnectionOptions(options) { | ||
| if ("href" in options) return new URL(options.href); | ||
| const protocol = options.port === 443 ? "https:" : "http:"; | ||
| const host = options.host; | ||
| const url = new URL(`${protocol}//${host}`); | ||
| if (options.port) url.port = options.port.toString(); | ||
| if (options.path) url.pathname = options.path; | ||
| if (options.auth) { | ||
| const [username, password] = options.auth.split(":"); | ||
| url.username = username; | ||
| url.password = password; | ||
| } | ||
| return url; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/recordRawHeaders.ts | ||
| const kRawHeaders = Symbol("kRawHeaders"); | ||
| const kRestorePatches = Symbol("kRestorePatches"); | ||
| function recordRawHeader(headers, args, behavior) { | ||
| ensureRawHeadersSymbol(headers, []); | ||
| const rawHeaders = Reflect.get(headers, kRawHeaders); | ||
| if (behavior === "set") { | ||
| for (let index = rawHeaders.length - 1; index >= 0; index--) if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) rawHeaders.splice(index, 1); | ||
| } | ||
| rawHeaders.push(args); | ||
| } | ||
| /** | ||
| * Define the raw headers symbol on the given `Headers` instance. | ||
| * If the symbol already exists, this function does nothing. | ||
| */ | ||
| function ensureRawHeadersSymbol(headers, rawHeaders) { | ||
| if (Reflect.has(headers, kRawHeaders)) return; | ||
| defineRawHeadersSymbol(headers, rawHeaders); | ||
| } | ||
| /** | ||
| * Define the raw headers symbol on the given `Headers` instance. | ||
| * If the symbol already exists, it gets overridden. | ||
| */ | ||
| function defineRawHeadersSymbol(headers, rawHeaders) { | ||
| Object.defineProperty(headers, kRawHeaders, { | ||
| value: rawHeaders, | ||
| enumerable: false, | ||
| configurable: true | ||
| }); | ||
| } | ||
| /** | ||
| * Patch the global `Headers` class to store raw headers. | ||
| * This is for compatibility with `IncomingMessage.prototype.rawHeaders`. | ||
| * | ||
| * @note Node.js has their own raw headers symbol but it | ||
| * only records the first header name in case of multi-value headers. | ||
| * Any other headers are normalized before comparing. This makes it | ||
| * incompatible with the `rawHeaders` format. | ||
| * | ||
| * let h = new Headers() | ||
| * h.append('X-Custom', 'one') | ||
| * h.append('x-custom', 'two') | ||
| * h[Symbol('headers map')] // Map { 'X-Custom' => 'one, two' } | ||
| */ | ||
| function recordRawFetchHeaders() { | ||
| if (Reflect.get(Headers, kRestorePatches)) return Reflect.get(Headers, kRestorePatches); | ||
| const { Headers: OriginalHeaders, Request: OriginalRequest, Response: OriginalResponse } = globalThis; | ||
| const { set, append, delete: headersDeleteMethod } = Headers.prototype; | ||
| Object.defineProperty(Headers, kRestorePatches, { | ||
| value: () => { | ||
| Headers.prototype.set = set; | ||
| Headers.prototype.append = append; | ||
| Headers.prototype.delete = headersDeleteMethod; | ||
| globalThis.Headers = OriginalHeaders; | ||
| globalThis.Request = OriginalRequest; | ||
| globalThis.Response = OriginalResponse; | ||
| Object.setPrototypeOf(FetchRequest, OriginalRequest); | ||
| Object.setPrototypeOf(FetchRequest.prototype, OriginalRequest.prototype); | ||
| Object.setPrototypeOf(FetchResponse, OriginalResponse); | ||
| Object.setPrototypeOf(FetchResponse.prototype, OriginalResponse.prototype); | ||
| Reflect.deleteProperty(Headers, kRestorePatches); | ||
| }, | ||
| enumerable: false, | ||
| configurable: true | ||
| }); | ||
| Object.defineProperty(globalThis, "Headers", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Headers, { construct(target, args, newTarget) { | ||
| const headersInit = args[0] || []; | ||
| if (headersInit instanceof Headers && Reflect.has(headersInit, kRawHeaders)) { | ||
| const sanitizedHeaders = Reflect.get(headersInit, kRawHeaders).map((tuple) => [tuple[0], tuple[1]]); | ||
| const headers$1 = Reflect.construct(target, [sanitizedHeaders], newTarget); | ||
| ensureRawHeadersSymbol(headers$1, [...sanitizedHeaders]); | ||
| return headers$1; | ||
| } | ||
| const headers = Reflect.construct(target, args, newTarget); | ||
| if (!Reflect.has(headers, kRawHeaders)) ensureRawHeadersSymbol(headers, Array.isArray(headersInit) ? headersInit : Object.entries(headersInit)); | ||
| return headers; | ||
| } }) | ||
| }); | ||
| Headers.prototype.set = new Proxy(Headers.prototype.set, { apply(target, thisArg, args) { | ||
| recordRawHeader(thisArg, [args[0], args[1]], "set"); | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Headers.prototype.append = new Proxy(Headers.prototype.append, { apply(target, thisArg, args) { | ||
| recordRawHeader(thisArg, [args[0], args[1]], "append"); | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Headers.prototype.delete = new Proxy(Headers.prototype.delete, { apply(target, thisArg, args) { | ||
| const rawHeaders = Reflect.get(thisArg, kRawHeaders); | ||
| if (rawHeaders) { | ||
| for (let index = rawHeaders.length - 1; index >= 0; index--) if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) rawHeaders.splice(index, 1); | ||
| } | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Object.defineProperty(globalThis, "Request", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Request, { construct(target, args, newTarget) { | ||
| const request = Reflect.construct(target, args, newTarget); | ||
| const inferredRawHeaders = []; | ||
| if (typeof args[0] === "object" && args[0].headers != null) inferredRawHeaders.push(...inferRawHeaders(args[0].headers)); | ||
| if (typeof args[1] === "object" && args[1].headers != null) inferredRawHeaders.push(...inferRawHeaders(args[1].headers)); | ||
| if (inferredRawHeaders.length > 0) ensureRawHeadersSymbol(request.headers, inferredRawHeaders); | ||
| return request; | ||
| } }) | ||
| }); | ||
| Object.defineProperty(globalThis, "Response", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Response, { construct(target, args, newTarget) { | ||
| const response = Reflect.construct(target, args, newTarget); | ||
| if (typeof args[1] === "object" && args[1].headers != null) ensureRawHeadersSymbol(response.headers, inferRawHeaders(args[1].headers)); | ||
| return response; | ||
| } }) | ||
| }); | ||
| /** | ||
| * Re-parent FetchRequest/FetchResponse so their `super()` calls go | ||
| * through the proxied globalThis.Request/Response above. Without this, | ||
| * FetchRequest extends the statically-captured (original) Request, | ||
| * bypassing the construct proxy that records raw headers. | ||
| */ | ||
| Object.setPrototypeOf(FetchRequest, globalThis.Request); | ||
| Object.setPrototypeOf(FetchRequest.prototype, globalThis.Request.prototype); | ||
| Object.setPrototypeOf(FetchResponse, globalThis.Response); | ||
| Object.setPrototypeOf(FetchResponse.prototype, globalThis.Response.prototype); | ||
| } | ||
| function restoreHeadersPrototype() { | ||
| if (!Reflect.get(Headers, kRestorePatches)) return; | ||
| Reflect.get(Headers, kRestorePatches)(); | ||
| } | ||
| function getRawFetchHeaders(headers) { | ||
| if (!Reflect.has(headers, kRawHeaders)) return Array.from(headers.entries()); | ||
| const rawHeaders = Reflect.get(headers, kRawHeaders); | ||
| return rawHeaders.length > 0 ? rawHeaders : Array.from(headers.entries()); | ||
| } | ||
| /** | ||
| * Infers the raw headers from the given `HeadersInit` provided | ||
| * to the Request/Response constructor. | ||
| * | ||
| * If the `init.headers` is a Headers instance, use it directly. | ||
| * That means the headers were created standalone and already have | ||
| * the raw headers stored. | ||
| * If the `init.headers` is a HeadersInit, create a new Headers | ||
| * instance out of it. | ||
| */ | ||
| function inferRawHeaders(headers) { | ||
| if (headers instanceof Headers) return Reflect.get(headers, kRawHeaders) || []; | ||
| return Reflect.get(new Headers(headers), kRawHeaders); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/parserUtils.ts | ||
| /** | ||
| * @see https://github.com/nodejs/node/blob/f3adc11e37b8bfaaa026ea85c1cf22e3a0e29ae9/lib/_http_common.js#L180 | ||
| */ | ||
| function freeParser(parser, socket) { | ||
| if (parser._consumed) parser.unconsume(); | ||
| parser._headers = []; | ||
| parser._url = ""; | ||
| parser.socket = null; | ||
| parser.incoming = null; | ||
| parser.outgoing = null; | ||
| parser.maxHeaderPairs = 2e3; | ||
| parser._consumed = false; | ||
| parser.onIncoming = null; | ||
| parser[HTTPParser.kOnHeaders] = null; | ||
| parser[HTTPParser.kOnHeadersComplete] = null; | ||
| parser[HTTPParser.kOnMessageBegin] = null; | ||
| parser[HTTPParser.kOnMessageComplete] = null; | ||
| parser[HTTPParser.kOnBody] = null; | ||
| parser[HTTPParser.kOnExecute] = null; | ||
| parser[HTTPParser.kOnTimeout] = null; | ||
| parser.remove(); | ||
| parser.free(); | ||
| if (socket) | ||
| /** | ||
| * @note Unassigning the socket's parser will fail this assertion | ||
| * if there's still some data being processed on the socket: | ||
| * @see https://github.com/nodejs/node/blob/4e1f39b678b37017ac9baa0971e3aeecd3b67b51/lib/_http_client.js#L613 | ||
| */ | ||
| if (socket.destroyed) socket.parser = null; | ||
| else socket.once("end", () => { | ||
| socket.parser = null; | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/MockHttpSocket.ts | ||
| const kRequestId = Symbol("kRequestId"); | ||
| var MockHttpSocket = class extends MockSocket { | ||
| constructor(options) { | ||
| super({ | ||
| write: (chunk, encoding, callback) => { | ||
| if (this.socketState !== "passthrough") this.writeBuffer.push([ | ||
| chunk, | ||
| encoding, | ||
| callback | ||
| ]); | ||
| if (chunk) { | ||
| /** | ||
| * Forward any writes to the mock socket to the underlying original socket. | ||
| * This ensures functional duplex connections, like WebSocket. | ||
| * @see https://github.com/mswjs/interceptors/issues/682 | ||
| */ | ||
| if (this.socketState === "passthrough") this.originalSocket?.write(chunk, encoding, callback); | ||
| this.requestParser.execute(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding)); | ||
| } | ||
| }, | ||
| read: (chunk) => { | ||
| if (chunk !== null) | ||
| /** | ||
| * @todo We need to free the parser if the connection has been | ||
| * upgraded to a non-HTTP protocol. It won't be able to parse data | ||
| * from that point onward anyway. No need to keep it in memory. | ||
| */ | ||
| this.responseParser.execute(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); | ||
| } | ||
| }); | ||
| this.requestRawHeadersBuffer = []; | ||
| this.responseRawHeadersBuffer = []; | ||
| this.writeBuffer = []; | ||
| this.socketState = "unknown"; | ||
| this.onRequestHeaders = (rawHeaders) => { | ||
| this.requestRawHeadersBuffer.push(...rawHeaders); | ||
| }; | ||
| this.onRequestStart = (versionMajor, versionMinor, rawHeaders, _, path, __, ___, ____, shouldKeepAlive) => { | ||
| this.shouldKeepAlive = shouldKeepAlive; | ||
| const url = new URL(path || "", this.baseUrl); | ||
| const method = this.connectionOptions.method?.toUpperCase() || "GET"; | ||
| const headers = FetchResponse.parseRawHeaders([...this.requestRawHeadersBuffer, ...rawHeaders || []]); | ||
| this.requestRawHeadersBuffer.length = 0; | ||
| const canHaveBody = method !== "GET" && method !== "HEAD"; | ||
| if (url.username || url.password) { | ||
| if (!headers.has("authorization")) headers.set("authorization", `Basic ${url.username}:${url.password}`); | ||
| url.username = ""; | ||
| url.password = ""; | ||
| } | ||
| this.requestStream = new Readable({ read: () => { | ||
| this.flushWriteBuffer(); | ||
| } }); | ||
| const requestId = createRequestId(); | ||
| this.request = new FetchRequest(url, { | ||
| method, | ||
| headers, | ||
| credentials: "same-origin", | ||
| duplex: canHaveBody ? "half" : void 0, | ||
| body: canHaveBody ? Readable.toWeb(this.requestStream) : null | ||
| }); | ||
| Reflect.set(this.request, kRequestId, requestId); | ||
| setRawRequest(this.request, Reflect.get(this, "_httpMessage")); | ||
| setRawRequestBodyStream(this.request, this.requestStream); | ||
| /** | ||
| * @fixme Stop relying on the "X-Request-Id" request header | ||
| * to figure out if one interceptor has been invoked within another. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) { | ||
| this.passthrough(); | ||
| return; | ||
| } | ||
| this.onRequest({ | ||
| requestId, | ||
| request: this.request, | ||
| socket: this | ||
| }); | ||
| }; | ||
| this.onResponseHeaders = (rawHeaders) => { | ||
| this.responseRawHeadersBuffer.push(...rawHeaders); | ||
| }; | ||
| this.onResponseStart = (versionMajor, versionMinor, rawHeaders, method, url, status, statusText) => { | ||
| const headers = FetchResponse.parseRawHeaders([...this.responseRawHeadersBuffer, ...rawHeaders || []]); | ||
| this.responseRawHeadersBuffer.length = 0; | ||
| const response = new FetchResponse( | ||
| /** | ||
| * @note The Fetch API response instance exposed to the consumer | ||
| * is created over the response stream of the HTTP parser. It is NOT | ||
| * related to the Socket instance. This way, you can read response body | ||
| * in response listener while the Socket instance delays the emission | ||
| * of "end" and other events until those response listeners are finished. | ||
| */ | ||
| FetchResponse.isResponseWithBody(status) ? Readable.toWeb(this.responseStream = new Readable({ read() {} })) : null, | ||
| { | ||
| url, | ||
| status, | ||
| statusText, | ||
| headers | ||
| } | ||
| ); | ||
| invariant(this.request, "Failed to handle a response: request does not exist"); | ||
| FetchResponse.setUrl(this.request.url, response); | ||
| /** | ||
| * @fixme Stop relying on the "X-Request-Id" request header | ||
| * to figure out if one interceptor has been invoked within another. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) return; | ||
| this.responseListenersPromise = this.onResponse({ | ||
| response, | ||
| isMockedResponse: this.socketState === "mock", | ||
| requestId: Reflect.get(this.request, kRequestId), | ||
| request: this.request, | ||
| socket: this | ||
| }); | ||
| }; | ||
| this.connectionOptions = options.connectionOptions; | ||
| this.createConnection = options.createConnection; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions); | ||
| this.requestParser = new HTTPParser(); | ||
| this.requestParser.initialize(HTTPParser.REQUEST, {}); | ||
| this.requestParser[HTTPParser.kOnHeaders] = this.onRequestHeaders.bind(this); | ||
| this.requestParser[HTTPParser.kOnHeadersComplete] = this.onRequestStart.bind(this); | ||
| this.requestParser[HTTPParser.kOnBody] = this.onRequestBody.bind(this); | ||
| this.requestParser[HTTPParser.kOnMessageComplete] = this.onRequestEnd.bind(this); | ||
| this.responseParser = new HTTPParser(); | ||
| this.responseParser.initialize(HTTPParser.RESPONSE, {}); | ||
| this.responseParser[HTTPParser.kOnHeaders] = this.onResponseHeaders.bind(this); | ||
| this.responseParser[HTTPParser.kOnHeadersComplete] = this.onResponseStart.bind(this); | ||
| this.responseParser[HTTPParser.kOnBody] = this.onResponseBody.bind(this); | ||
| this.responseParser[HTTPParser.kOnMessageComplete] = this.onResponseEnd.bind(this); | ||
| this.once("finish", () => freeParser(this.requestParser, this)); | ||
| if (this.baseUrl.protocol === "https:") { | ||
| Reflect.set(this, "encrypted", true); | ||
| Reflect.set(this, "authorized", false); | ||
| Reflect.set(this, "getProtocol", () => "TLSv1.3"); | ||
| Reflect.set(this, "getSession", () => void 0); | ||
| Reflect.set(this, "isSessionReused", () => false); | ||
| Reflect.set(this, "getCipher", () => ({ | ||
| name: "AES256-SHA", | ||
| standardName: "TLS_RSA_WITH_AES_256_CBC_SHA", | ||
| version: "TLSv1.3" | ||
| })); | ||
| } | ||
| } | ||
| emit(event, ...args) { | ||
| const emitEvent = super.emit.bind(this, event, ...args); | ||
| if (this.responseListenersPromise) { | ||
| this.responseListenersPromise.finally(emitEvent); | ||
| return this.listenerCount(event) > 0; | ||
| } | ||
| return emitEvent(); | ||
| } | ||
| destroy(error) { | ||
| freeParser(this.responseParser, this); | ||
| if (error) this.emit("error", error); | ||
| return super.destroy(error); | ||
| } | ||
| /** | ||
| * Establish this Socket connection as-is and pipe | ||
| * its data/events through this Socket. | ||
| */ | ||
| passthrough() { | ||
| this.socketState = "passthrough"; | ||
| if (this.destroyed) return; | ||
| const socket = this.createConnection(); | ||
| this.originalSocket = socket; | ||
| /** | ||
| * @note Inherit the original socket's connection handle. | ||
| * Without this, each push to the mock socket results in a | ||
| * new "connection" listener being added (i.e. buffering pushes). | ||
| * @see https://github.com/nodejs/node/blob/b18153598b25485ce4f54d0c5cb830a9457691ee/lib/net.js#L734 | ||
| */ | ||
| if ("_handle" in socket) Object.defineProperty(this, "_handle", { | ||
| value: socket._handle, | ||
| enumerable: true, | ||
| writable: true | ||
| }); | ||
| this.once("close", () => { | ||
| socket.removeAllListeners(); | ||
| if (!socket.destroyed) socket.destroy(); | ||
| this.originalSocket = void 0; | ||
| }); | ||
| this.address = socket.address.bind(socket); | ||
| let writeArgs; | ||
| let headersWritten = false; | ||
| while (writeArgs = this.writeBuffer.shift()) if (writeArgs !== void 0) { | ||
| if (!headersWritten) { | ||
| const [chunk, encoding, callback] = writeArgs; | ||
| const chunkString = chunk.toString(); | ||
| const chunkBeforeRequestHeaders = chunkString.slice(0, chunkString.indexOf("\r\n") + 2); | ||
| const chunkAfterRequestHeaders = chunkString.slice(chunk.indexOf("\r\n\r\n")); | ||
| const headersChunk = `${chunkBeforeRequestHeaders}${getRawFetchHeaders(this.request.headers).filter(([name]) => { | ||
| return name.toLowerCase() !== INTERNAL_REQUEST_ID_HEADER_NAME; | ||
| }).map(([name, value]) => `${name}: ${value}`).join("\r\n")}${chunkAfterRequestHeaders}`; | ||
| socket.write(headersChunk, encoding, callback); | ||
| headersWritten = true; | ||
| continue; | ||
| } | ||
| socket.write(...writeArgs); | ||
| } | ||
| if (Reflect.get(socket, "encrypted")) [ | ||
| "encrypted", | ||
| "authorized", | ||
| "getProtocol", | ||
| "getSession", | ||
| "isSessionReused", | ||
| "getCipher" | ||
| ].forEach((propertyName) => { | ||
| Object.defineProperty(this, propertyName, { | ||
| enumerable: true, | ||
| get: () => { | ||
| const value = Reflect.get(socket, propertyName); | ||
| return typeof value === "function" ? value.bind(socket) : value; | ||
| } | ||
| }); | ||
| }); | ||
| socket.on("lookup", (...args) => this.emit("lookup", ...args)).on("connect", () => { | ||
| this.connecting = socket.connecting; | ||
| this.emit("connect"); | ||
| }).on("secureConnect", () => this.emit("secureConnect")).on("secure", () => this.emit("secure")).on("session", (session) => this.emit("session", session)).on("ready", () => this.emit("ready")).on("drain", () => this.emit("drain")).on("data", (chunk) => { | ||
| this.push(chunk); | ||
| }).on("error", (error) => { | ||
| Reflect.set(this, "_hadError", Reflect.get(socket, "_hadError")); | ||
| this.emit("error", error); | ||
| }).on("resume", () => this.emit("resume")).on("timeout", () => this.emit("timeout")).on("prefinish", () => this.emit("prefinish")).on("finish", () => this.emit("finish")).on("close", (hadError) => this.emit("close", hadError)).on("end", () => this.emit("end")); | ||
| } | ||
| /** | ||
| * Convert the given Fetch API `Response` instance to an | ||
| * HTTP message and push it to the socket. | ||
| */ | ||
| async respondWith(response) { | ||
| if (this.destroyed) return; | ||
| invariant(this.socketState !== "mock", "[MockHttpSocket] Failed to respond to the \"%s %s\" request with \"%s %s\": the request has already been handled", this.request?.method, this.request?.url, response.status, response.statusText); | ||
| if (isPropertyAccessible(response, "type") && response.type === "error") { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| this.mockConnect(); | ||
| this.socketState = "mock"; | ||
| this.flushWriteBuffer(); | ||
| const serverResponse = new ServerResponse(new IncomingMessage(this)); | ||
| /** | ||
| * Assign a mock socket instance to the server response to | ||
| * spy on the response chunk writes. Push the transformed response chunks | ||
| * to this `MockHttpSocket` instance to trigger the "data" event. | ||
| * @note Providing the same `MockSocket` instance when creating `ServerResponse` | ||
| * does not have the same effect. | ||
| * @see https://github.com/nodejs/node/blob/10099bb3f7fd97bb9dd9667188426866b3098e07/test/parallel/test-http-server-response-standalone.js#L32 | ||
| */ | ||
| serverResponse.assignSocket(new MockSocket({ | ||
| write: (chunk, encoding, callback) => { | ||
| this.push(chunk, encoding); | ||
| callback?.(); | ||
| }, | ||
| read() {} | ||
| })); | ||
| /** | ||
| * @note Remove the `Connection` and `Date` response headers | ||
| * injected by `ServerResponse` by default. Those are required | ||
| * from the server but the interceptor is NOT technically a server. | ||
| * It's confusing to add response headers that the developer didn't | ||
| * specify themselves. They can always add these if they wish. | ||
| * @see https://www.rfc-editor.org/rfc/rfc9110#field.date | ||
| * @see https://www.rfc-editor.org/rfc/rfc9110#field.connection | ||
| */ | ||
| serverResponse.removeHeader("connection"); | ||
| serverResponse.removeHeader("date"); | ||
| const rawResponseHeaders = getRawFetchHeaders(response.headers); | ||
| /** | ||
| * @note Call `.writeHead` in order to set the raw response headers | ||
| * in the same case as they were provided by the developer. Using | ||
| * `.setHeader()`/`.appendHeader()` normalizes header names. | ||
| */ | ||
| serverResponse.writeHead(response.status, response.statusText || STATUS_CODES[response.status], rawResponseHeaders); | ||
| this.once("error", () => { | ||
| serverResponse.destroy(); | ||
| }); | ||
| if (response.body) try { | ||
| const reader = response.body.getReader(); | ||
| while (true) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) { | ||
| serverResponse.end(); | ||
| break; | ||
| } | ||
| serverResponse.write(value); | ||
| } | ||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| serverResponse.destroy(); | ||
| /** | ||
| * @note Destroy the request socket gracefully. | ||
| * Response stream errors do NOT produce request errors. | ||
| */ | ||
| this.destroy(); | ||
| return; | ||
| } | ||
| serverResponse.destroy(); | ||
| throw error; | ||
| } | ||
| else serverResponse.end(); | ||
| if (!this.shouldKeepAlive) { | ||
| this.emit("readable"); | ||
| /** | ||
| * @todo @fixme This is likely a hack. | ||
| * Since we push null to the socket, it never propagates to the | ||
| * parser, and the parser never calls "onResponseEnd" to close | ||
| * the response stream. We are closing the stream here manually | ||
| * but that shouldn't be the case. | ||
| */ | ||
| this.responseStream?.push(null); | ||
| this.push(null); | ||
| } | ||
| } | ||
| /** | ||
| * Close this socket connection with the given error. | ||
| */ | ||
| errorWith(error) { | ||
| this.destroy(error); | ||
| } | ||
| mockConnect() { | ||
| this.connecting = false; | ||
| const isIPv6 = net.isIPv6(this.connectionOptions.hostname) || this.connectionOptions.family === 6; | ||
| const addressInfo = { | ||
| address: isIPv6 ? "::1" : "127.0.0.1", | ||
| family: isIPv6 ? "IPv6" : "IPv4", | ||
| port: this.connectionOptions.port | ||
| }; | ||
| this.address = () => addressInfo; | ||
| this.emit("lookup", null, addressInfo.address, addressInfo.family === "IPv6" ? 6 : 4, this.connectionOptions.host); | ||
| this.emit("connect"); | ||
| this.emit("ready"); | ||
| if (this.baseUrl.protocol === "https:") { | ||
| this.emit("secure"); | ||
| this.emit("secureConnect"); | ||
| this.emit("session", this.connectionOptions.session || Buffer.from("mock-session-renegotiate")); | ||
| this.emit("session", Buffer.from("mock-session-resume")); | ||
| } | ||
| } | ||
| flushWriteBuffer() { | ||
| for (const writeCall of this.writeBuffer) if (typeof writeCall[2] === "function") { | ||
| writeCall[2](); | ||
| /** | ||
| * @note Remove the callback from the write call | ||
| * so it doesn't get called twice on passthrough | ||
| * if `request.end()` was called within `request.write()`. | ||
| * @see https://github.com/mswjs/interceptors/issues/684 | ||
| */ | ||
| writeCall[2] = void 0; | ||
| } | ||
| } | ||
| onRequestBody(chunk) { | ||
| invariant(this.requestStream, "Failed to write to a request stream: stream does not exist"); | ||
| this.requestStream.push(chunk); | ||
| } | ||
| onRequestEnd() { | ||
| if (this.requestStream) this.requestStream.push(null); | ||
| } | ||
| onResponseBody(chunk) { | ||
| invariant(this.responseStream, "Failed to write to a response stream: stream does not exist"); | ||
| this.responseStream.push(chunk); | ||
| } | ||
| onResponseEnd() { | ||
| if (this.responseStream) this.responseStream.push(null); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/agents.ts | ||
| var MockAgent = class extends http.Agent { | ||
| constructor(options) { | ||
| super(); | ||
| this.customAgent = options.customAgent; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| } | ||
| createConnection(options, callback) { | ||
| const createConnection = this.customAgent instanceof http.Agent ? this.customAgent.createConnection : super.createConnection; | ||
| const createConnectionOptions = this.customAgent instanceof http.Agent ? { | ||
| ...options, | ||
| ...this.customAgent.options | ||
| } : options; | ||
| return new MockHttpSocket({ | ||
| connectionOptions: options, | ||
| createConnection: createConnection.bind(this.customAgent || this, createConnectionOptions, callback), | ||
| onRequest: this.onRequest.bind(this), | ||
| onResponse: this.onResponse.bind(this) | ||
| }); | ||
| } | ||
| }; | ||
| var MockHttpsAgent = class extends https.Agent { | ||
| constructor(options) { | ||
| super(); | ||
| this.customAgent = options.customAgent; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| } | ||
| createConnection(options, callback) { | ||
| const createConnection = this.customAgent instanceof http.Agent ? this.customAgent.createConnection : super.createConnection; | ||
| const createConnectionOptions = this.customAgent instanceof http.Agent ? { | ||
| ...options, | ||
| ...this.customAgent.options | ||
| } : options; | ||
| return new MockHttpSocket({ | ||
| connectionOptions: options, | ||
| createConnection: createConnection.bind(this.customAgent || this, createConnectionOptions, callback), | ||
| onRequest: this.onRequest.bind(this), | ||
| onResponse: this.onResponse.bind(this) | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/utils/getUrlByRequestOptions.ts | ||
| const logger$2 = new Logger("utils getUrlByRequestOptions"); | ||
| const DEFAULT_PATH = "/"; | ||
| const DEFAULT_PROTOCOL = "http:"; | ||
| const DEFAULT_HOSTNAME = "localhost"; | ||
| const SSL_PORT = 443; | ||
| function getAgent(options) { | ||
| return options.agent instanceof Agent ? options.agent : void 0; | ||
| } | ||
| function getProtocolByRequestOptions(options) { | ||
| if (options.protocol) return options.protocol; | ||
| const agentProtocol = getAgent(options)?.protocol; | ||
| if (agentProtocol) return agentProtocol; | ||
| const port = getPortByRequestOptions(options); | ||
| return options.cert || port === SSL_PORT ? "https:" : options.uri?.protocol || DEFAULT_PROTOCOL; | ||
| } | ||
| function getPortByRequestOptions(options) { | ||
| if (options.port) return Number(options.port); | ||
| const agent = getAgent(options); | ||
| if (agent?.options.port) return Number(agent.options.port); | ||
| if (agent?.defaultPort) return Number(agent.defaultPort); | ||
| } | ||
| function getAuthByRequestOptions(options) { | ||
| if (options.auth) { | ||
| const [username, password] = options.auth.split(":"); | ||
| return { | ||
| username, | ||
| password | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Returns true if host looks like an IPv6 address without surrounding brackets | ||
| * It assumes any host containing `:` is definitely not IPv4 and probably IPv6, | ||
| * but note that this could include invalid IPv6 addresses as well. | ||
| */ | ||
| function isRawIPv6Address(host) { | ||
| return host.includes(":") && !host.startsWith("[") && !host.endsWith("]"); | ||
| } | ||
| function getHostname(options) { | ||
| let host = options.hostname || options.host; | ||
| if (host) { | ||
| if (isRawIPv6Address(host)) host = `[${host}]`; | ||
| return new URL(`http://${host}`).hostname; | ||
| } | ||
| return DEFAULT_HOSTNAME; | ||
| } | ||
| /** | ||
| * Creates a `URL` instance from a given `RequestOptions` object. | ||
| */ | ||
| function getUrlByRequestOptions(options) { | ||
| logger$2.info("request options", options); | ||
| if (options.uri) { | ||
| logger$2.info("constructing url from explicitly provided \"options.uri\": %s", options.uri); | ||
| return new URL(options.uri.href); | ||
| } | ||
| logger$2.info("figuring out url from request options..."); | ||
| const protocol = getProtocolByRequestOptions(options); | ||
| logger$2.info("protocol", protocol); | ||
| const port = getPortByRequestOptions(options); | ||
| logger$2.info("port", port); | ||
| const hostname = getHostname(options); | ||
| logger$2.info("hostname", hostname); | ||
| const path = options.path || DEFAULT_PATH; | ||
| logger$2.info("path", path); | ||
| const credentials = getAuthByRequestOptions(options); | ||
| logger$2.info("credentials", credentials); | ||
| const authString = credentials ? `${credentials.username}:${credentials.password}@` : ""; | ||
| logger$2.info("auth string:", authString); | ||
| const portString = typeof port !== "undefined" ? `:${port}` : ""; | ||
| const url = new URL(`${protocol}//${hostname}${portString}${path}`); | ||
| url.username = credentials?.username || ""; | ||
| url.password = credentials?.password || ""; | ||
| logger$2.info("created url:", url); | ||
| return url; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/cloneObject.ts | ||
| const logger$1 = new Logger("cloneObject"); | ||
| function isPlainObject(obj) { | ||
| logger$1.info("is plain object?", obj); | ||
| if (obj == null || !obj.constructor?.name) { | ||
| logger$1.info("given object is undefined, not a plain object..."); | ||
| return false; | ||
| } | ||
| logger$1.info("checking the object constructor:", obj.constructor.name); | ||
| return obj.constructor.name === "Object"; | ||
| } | ||
| function cloneObject(obj) { | ||
| logger$1.info("cloning object:", obj); | ||
| const enumerableProperties = Object.entries(obj).reduce((acc, [key, value]) => { | ||
| logger$1.info("analyzing key-value pair:", key, value); | ||
| acc[key] = isPlainObject(value) ? cloneObject(value) : value; | ||
| return acc; | ||
| }, {}); | ||
| return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts | ||
| const logger = new Logger("http normalizeClientRequestArgs"); | ||
| function resolveRequestOptions(args, url) { | ||
| if (typeof args[1] === "undefined" || typeof args[1] === "function") { | ||
| logger.info("request options not provided, deriving from the url", url); | ||
| return urlToHttpOptions(url); | ||
| } | ||
| if (args[1]) { | ||
| logger.info("has custom RequestOptions!", args[1]); | ||
| const requestOptionsFromUrl = urlToHttpOptions(url); | ||
| logger.info("derived RequestOptions from the URL:", requestOptionsFromUrl); | ||
| /** | ||
| * Clone the request options to lock their state | ||
| * at the moment they are provided to `ClientRequest`. | ||
| * @see https://github.com/mswjs/interceptors/issues/86 | ||
| */ | ||
| logger.info("cloning RequestOptions..."); | ||
| const clonedRequestOptions = cloneObject(args[1]); | ||
| logger.info("successfully cloned RequestOptions!", clonedRequestOptions); | ||
| return { | ||
| ...requestOptionsFromUrl, | ||
| ...clonedRequestOptions | ||
| }; | ||
| } | ||
| logger.info("using an empty object as request options"); | ||
| return {}; | ||
| } | ||
| /** | ||
| * Overrides the given `URL` instance with the explicit properties provided | ||
| * on the `RequestOptions` object. The options object takes precedence, | ||
| * and will replace URL properties like "host", "path", and "port", if specified. | ||
| */ | ||
| function overrideUrlByRequestOptions(url, options) { | ||
| url.host = options.host || url.host; | ||
| url.hostname = options.hostname || url.hostname; | ||
| url.port = options.port ? options.port.toString() : url.port; | ||
| if (options.path) { | ||
| const parsedOptionsPath = parse(options.path, false); | ||
| url.pathname = parsedOptionsPath.pathname || ""; | ||
| url.search = parsedOptionsPath.search || ""; | ||
| } | ||
| return url; | ||
| } | ||
| function resolveCallback(args) { | ||
| return typeof args[1] === "function" ? args[1] : args[2]; | ||
| } | ||
| /** | ||
| * Normalizes parameters given to a `http.request` call | ||
| * so it always has a `URL` and `RequestOptions`. | ||
| */ | ||
| function normalizeClientRequestArgs(defaultProtocol, args) { | ||
| let url; | ||
| let options; | ||
| let callback; | ||
| logger.info("arguments", args); | ||
| logger.info("using default protocol:", defaultProtocol); | ||
| if (args.length === 0) { | ||
| const url$1 = new URL$1("http://localhost"); | ||
| return [url$1, resolveRequestOptions(args, url$1)]; | ||
| } | ||
| if (typeof args[0] === "string") { | ||
| logger.info("first argument is a location string:", args[0]); | ||
| url = new URL$1(args[0]); | ||
| logger.info("created a url:", url); | ||
| const requestOptionsFromUrl = urlToHttpOptions(url); | ||
| logger.info("request options from url:", requestOptionsFromUrl); | ||
| options = resolveRequestOptions(args, url); | ||
| logger.info("resolved request options:", options); | ||
| callback = resolveCallback(args); | ||
| } else if (args[0] instanceof URL$1) { | ||
| url = args[0]; | ||
| logger.info("first argument is a URL:", url); | ||
| if (typeof args[1] !== "undefined" && isObject(args[1])) url = overrideUrlByRequestOptions(url, args[1]); | ||
| options = resolveRequestOptions(args, url); | ||
| logger.info("derived request options:", options); | ||
| callback = resolveCallback(args); | ||
| } else if ("hash" in args[0] && !("method" in args[0])) { | ||
| const [legacyUrl] = args; | ||
| logger.info("first argument is a legacy URL:", legacyUrl); | ||
| if (legacyUrl.hostname === null) { | ||
| /** | ||
| * We are dealing with a relative url, so use the path as an "option" and | ||
| * merge in any existing options, giving priority to existing options -- i.e. a path in any | ||
| * existing options will take precedence over the one contained in the url. This is consistent | ||
| * with the behaviour in ClientRequest. | ||
| * @see https://github.com/nodejs/node/blob/d84f1312915fe45fe0febe888db692c74894c382/lib/_http_client.js#L122 | ||
| */ | ||
| logger.info("given legacy URL is relative (no hostname)"); | ||
| return isObject(args[1]) ? normalizeClientRequestArgs(defaultProtocol, [{ | ||
| path: legacyUrl.path, | ||
| ...args[1] | ||
| }, args[2]]) : normalizeClientRequestArgs(defaultProtocol, [{ path: legacyUrl.path }, args[1]]); | ||
| } | ||
| logger.info("given legacy url is absolute"); | ||
| const resolvedUrl = new URL$1(legacyUrl.href); | ||
| return args[1] === void 0 ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl]) : typeof args[1] === "function" ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl, args[1]]) : normalizeClientRequestArgs(defaultProtocol, [ | ||
| resolvedUrl, | ||
| args[1], | ||
| args[2] | ||
| ]); | ||
| } else if (isObject(args[0])) { | ||
| options = { ...args[0] }; | ||
| logger.info("first argument is RequestOptions:", options); | ||
| options.protocol = options.protocol || defaultProtocol; | ||
| logger.info("normalized request options:", options); | ||
| url = getUrlByRequestOptions(options); | ||
| logger.info("created a URL from RequestOptions:", url.href); | ||
| callback = resolveCallback(args); | ||
| } else throw new Error(`Failed to construct ClientRequest with these parameters: ${args}`); | ||
| options.protocol = options.protocol || url.protocol; | ||
| options.method = options.method || "GET"; | ||
| /** | ||
| * Ensure that the default Agent is always set. | ||
| * This prevents the protocol mismatch for requests with { agent: false }, | ||
| * where the global Agent is inferred. | ||
| * @see https://github.com/mswjs/msw/issues/1150 | ||
| * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L130 | ||
| * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L157-L159 | ||
| */ | ||
| if (!options._defaultAgent) { | ||
| logger.info("has no default agent, setting the default agent for \"%s\"", options.protocol); | ||
| options._defaultAgent = options.protocol === "https:" ? globalAgent$1 : globalAgent; | ||
| } | ||
| logger.info("successfully resolved url:", url.href); | ||
| logger.info("successfully resolved options:", options); | ||
| logger.info("successfully resolved callback:", callback); | ||
| /** | ||
| * @note If the user-provided URL is not a valid URL in Node.js, | ||
| * (e.g. the one provided by the JSDOM polyfills), case it to | ||
| * string. Otherwise, this throws on Node.js incompatibility | ||
| * (`ERR_INVALID_ARG_TYPE` on the connection listener) | ||
| * @see https://github.com/node-fetch/node-fetch/issues/1376#issuecomment-966435555 | ||
| */ | ||
| if (!(url instanceof URL$1)) url = url.toString(); | ||
| return [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/index.ts | ||
| var ClientRequestInterceptor = class ClientRequestInterceptor extends Interceptor { | ||
| static { | ||
| this.symbol = Symbol("client-request-interceptor"); | ||
| } | ||
| constructor() { | ||
| super(ClientRequestInterceptor.symbol); | ||
| this.onRequest = async ({ request, socket }) => { | ||
| const controller = new RequestController(request, { | ||
| passthrough() { | ||
| socket.passthrough(); | ||
| }, | ||
| async respondWith(response) { | ||
| await socket.respondWith(response); | ||
| }, | ||
| errorWith(reason) { | ||
| if (reason instanceof Error) socket.errorWith(reason); | ||
| } | ||
| }); | ||
| await handleRequest({ | ||
| request, | ||
| requestId: Reflect.get(request, kRequestId), | ||
| controller, | ||
| emitter: this.emitter | ||
| }); | ||
| }; | ||
| this.onResponse = async ({ requestId, request, response, isMockedResponse }) => { | ||
| return emitAsync(this.emitter, "response", { | ||
| requestId, | ||
| request, | ||
| response, | ||
| isMockedResponse | ||
| }); | ||
| }; | ||
| } | ||
| setup() { | ||
| const { ClientRequest: OriginalClientRequest, get: originalGet, request: originalRequest } = http; | ||
| const { get: originalHttpsGet, request: originalHttpsRequest } = https; | ||
| const onRequest = this.onRequest.bind(this); | ||
| const onResponse = this.onResponse.bind(this); | ||
| http.ClientRequest = new Proxy(http.ClientRequest, { construct: (target, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new (options.protocol === "https:" ? MockHttpsAgent : MockAgent)({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.construct(target, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| http.request = new Proxy(http.request, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new MockAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| http.get = new Proxy(http.get, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new MockAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| https.request = new Proxy(https.request, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("https:", args); | ||
| options.agent = new MockHttpsAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| https.get = new Proxy(https.get, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("https:", args); | ||
| options.agent = new MockHttpsAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| recordRawFetchHeaders(); | ||
| this.subscriptions.push(() => { | ||
| http.ClientRequest = OriginalClientRequest; | ||
| http.get = originalGet; | ||
| http.request = originalRequest; | ||
| https.get = originalHttpsGet; | ||
| https.request = originalHttpsRequest; | ||
| restoreHeadersPrototype(); | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { ClientRequestInterceptor as t }; | ||
| //# sourceMappingURL=ClientRequest-dRpI9v1o.mjs.map |
Sorry, the diff of this file is too big to display
| import { a as RequestController, c as Interceptor, i as createRequestId, n as FetchResponse, r as canParseUrl, t as FetchRequest } from "./fetchUtils-BKJ1XmiO.mjs"; | ||
| import { n as setRawRequest } from "./getRawRequest-C2-1urzA.mjs"; | ||
| import { i as emitAsync, n as isResponseError, t as handleRequest } from "./handleRequest-DCLzePtS.mjs"; | ||
| import { n as patchesRegistry, t as hasConfigurableGlobal } from "./hasConfigurableGlobal-Cwrs35Xo.mjs"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { until } from "@open-draft/until"; | ||
| import zlib from "node:zlib"; | ||
| //#region src/interceptors/fetch/utils/createNetworkError.ts | ||
| function createNetworkError(cause) { | ||
| return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/followRedirect.ts | ||
| const REQUEST_BODY_HEADERS = [ | ||
| "content-encoding", | ||
| "content-language", | ||
| "content-location", | ||
| "content-type", | ||
| "content-length" | ||
| ]; | ||
| const kRedirectCount = Symbol("kRedirectCount"); | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210 | ||
| */ | ||
| 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"); | ||
| } | ||
| /** | ||
| * @note Undici "safely" extracts the request body. | ||
| * I suspect we cannot dispatch this request again | ||
| * since its body has been read and the stream is locked. | ||
| */ | ||
| requestInit.headers = request.headers; | ||
| const finalResponse = await fetch(new Request(locationUrl, requestInit)); | ||
| Object.defineProperty(finalResponse, "redirected", { | ||
| value: true, | ||
| configurable: true | ||
| }); | ||
| return finalResponse; | ||
| } | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761 | ||
| */ | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/brotli-decompress.ts | ||
| var BrotliDecompressionStream = class extends TransformStream { | ||
| constructor() { | ||
| const decompress = zlib.createBrotliDecompress({ | ||
| flush: zlib.constants.BROTLI_OPERATION_FLUSH, | ||
| finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH | ||
| }); | ||
| super({ async transform(chunk, controller) { | ||
| const buffer = Buffer.from(chunk); | ||
| const decompressed = await new Promise((resolve, reject) => { | ||
| decompress.write(buffer, (error) => { | ||
| if (error) reject(error); | ||
| }); | ||
| decompress.flush(); | ||
| decompress.once("data", (data) => resolve(data)); | ||
| decompress.once("error", (error) => reject(error)); | ||
| decompress.once("end", () => controller.terminate()); | ||
| }).catch((error) => { | ||
| controller.error(error); | ||
| }); | ||
| controller.enqueue(decompressed); | ||
| } }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/decompression.ts | ||
| var PipelineStream = class extends TransformStream { | ||
| constructor(transformStreams, ...strategies) { | ||
| super({}, ...strategies); | ||
| const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform)); | ||
| Object.defineProperty(this, "readable", { get() { | ||
| return readable; | ||
| } }); | ||
| } | ||
| }; | ||
| function parseContentEncoding(contentEncoding) { | ||
| return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim()); | ||
| } | ||
| function createDecompressionStream(contentEncoding) { | ||
| if (contentEncoding === "") return null; | ||
| const codings = parseContentEncoding(contentEncoding); | ||
| if (codings.length === 0) return null; | ||
| return new PipelineStream(codings.reduceRight((transformers, coding) => { | ||
| if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip")); | ||
| else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate")); | ||
| else if (coding === "br") return transformers.concat(new BrotliDecompressionStream()); | ||
| else transformers.length = 0; | ||
| return transformers; | ||
| }, [])); | ||
| } | ||
| function decompressResponse(response) { | ||
| if (response.body === null) return null; | ||
| const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || ""); | ||
| if (!decompressionStream) return null; | ||
| response.body.pipeTo(decompressionStream.writable); | ||
| return decompressionStream.readable; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/index.ts | ||
| var FetchInterceptor = class FetchInterceptor extends Interceptor { | ||
| static { | ||
| this.symbol = Symbol("fetch"); | ||
| } | ||
| constructor() { | ||
| super(FetchInterceptor.symbol); | ||
| } | ||
| checkEnvironment() { | ||
| return hasConfigurableGlobal("fetch"); | ||
| } | ||
| async setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| const pureFetch = globalThis.fetch; | ||
| const fetchProxy = async (input, init) => { | ||
| const requestId = createRequestId(); | ||
| const request = new FetchRequest(typeof input === "string" && typeof location !== "undefined" && !canParseUrl(input) ? new URL(input, location.href) : input, init); | ||
| /** | ||
| * @note Set the raw request only if a Request instance was provided to fetch. | ||
| */ | ||
| if (input instanceof Request) setRawRequest(request, input); | ||
| const responsePromise = new DeferredPromise(); | ||
| const controller = new RequestController(request, { | ||
| passthrough: async () => { | ||
| this.logger.info("request has not been handled, passthrough..."); | ||
| /** | ||
| * @note Clone the request instance right before performing it. | ||
| * This preserves any modifications made to the intercepted request | ||
| * in the "request" listener. This also allows the user to read the | ||
| * request body in the "response" listener (otherwise "unusable"). | ||
| */ | ||
| const requestCloneForResponseEvent = request.clone(); | ||
| const { error: responseError, data: originalResponse } = await until(() => pureFetch(request)); | ||
| if (responseError) return responsePromise.reject(responseError); | ||
| this.logger.info("original fetch performed", originalResponse); | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| const responseClone = FetchResponse.clone(originalResponse); | ||
| await emitAsync(this.emitter, "response", { | ||
| response: responseClone, | ||
| isMockedResponse: false, | ||
| request: requestCloneForResponseEvent, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(originalResponse); | ||
| }, | ||
| respondWith: async (rawResponse) => { | ||
| if (isResponseError(rawResponse)) { | ||
| this.logger.info("request has errored!", { response: rawResponse }); | ||
| responsePromise.reject(createNetworkError(rawResponse)); | ||
| return; | ||
| } | ||
| this.logger.info("received mocked response!", { rawResponse }); | ||
| const response = new FetchResponse(decompressResponse(rawResponse) || rawResponse.body, { | ||
| url: request.url, | ||
| status: rawResponse.status, | ||
| statusText: rawResponse.statusText, | ||
| headers: rawResponse.headers | ||
| }); | ||
| /** | ||
| * Undici's handling of following redirect responses. | ||
| * Treat the "manual" redirect mode as a regular mocked response. | ||
| * This way, the client can manually follow the redirect it receives. | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173 | ||
| */ | ||
| if (FetchResponse.isRedirectResponse(response.status)) { | ||
| if (request.redirect === "error") { | ||
| responsePromise.reject(createNetworkError("unexpected redirect")); | ||
| return; | ||
| } | ||
| if (request.redirect === "follow") { | ||
| followFetchRedirect(request, response).then((response$1) => { | ||
| responsePromise.resolve(response$1); | ||
| }, (reason) => { | ||
| responsePromise.reject(reason); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| await emitAsync(this.emitter, "response", { | ||
| response: FetchResponse.clone(response), | ||
| isMockedResponse: true, | ||
| request, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request has been aborted!", { reason }); | ||
| responsePromise.reject(reason); | ||
| } | ||
| }); | ||
| 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")); | ||
| await handleRequest({ | ||
| request, | ||
| requestId, | ||
| emitter: this.emitter, | ||
| controller | ||
| }); | ||
| return responsePromise; | ||
| }; | ||
| logger.info("patching global fetch..."); | ||
| this.subscriptions.push(patchesRegistry.applyPatch(globalThis, "fetch", () => fetchProxy)); | ||
| logger.info("global fetch patched!", globalThis.fetch.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { FetchInterceptor as t }; | ||
| //# sourceMappingURL=fetch-BciBtVqm.mjs.map |
| {"version":3,"file":"fetch-BciBtVqm.mjs","names":["locationUrl: URL","requestInit: RequestInit","readable","fetchProxy: typeof fetch","response"],"sources":["../../src/interceptors/fetch/utils/createNetworkError.ts","../../src/interceptors/fetch/utils/followRedirect.ts","../../src/interceptors/fetch/utils/brotli-decompress.ts","../../src/interceptors/fetch/utils/decompression.ts","../../src/interceptors/fetch/index.ts"],"sourcesContent":["export function createNetworkError(cause?: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","import { createNetworkError } from './createNetworkError'\n\nconst REQUEST_BODY_HEADERS = [\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-type',\n 'content-length',\n]\n\nconst kRedirectCount = Symbol('kRedirectCount')\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210\n */\nexport async function followFetchRedirect(\n request: Request,\n response: Response\n): Promise<Response> {\n if (response.status !== 303 && request.body != null) {\n return Promise.reject(createNetworkError())\n }\n\n const requestUrl = new URL(request.url)\n\n let locationUrl: URL\n try {\n // If the location is a relative URL, use the request URL as the base URL.\n locationUrl = new URL(response.headers.get('location')!, request.url) \n } catch (error) {\n return Promise.reject(createNetworkError(error))\n }\n\n if (\n !(locationUrl.protocol === 'http:' || locationUrl.protocol === 'https:')\n ) {\n return Promise.reject(\n createNetworkError('URL scheme must be a HTTP(S) scheme')\n )\n }\n\n if (Reflect.get(request, kRedirectCount) > 20) {\n return Promise.reject(createNetworkError('redirect count exceeded'))\n }\n\n Object.defineProperty(request, kRedirectCount, {\n value: (Reflect.get(request, kRedirectCount) || 0) + 1,\n })\n\n if (\n request.mode === 'cors' &&\n (locationUrl.username || locationUrl.password) &&\n !sameOrigin(requestUrl, locationUrl)\n ) {\n return Promise.reject(\n createNetworkError('cross origin not allowed for request mode \"cors\"')\n )\n }\n\n const requestInit: RequestInit = {}\n\n if (\n ([301, 302].includes(response.status) && request.method === 'POST') ||\n (response.status === 303 && !['HEAD', 'GET'].includes(request.method))\n ) {\n requestInit.method = 'GET'\n requestInit.body = null\n\n REQUEST_BODY_HEADERS.forEach((headerName) => {\n request.headers.delete(headerName)\n })\n }\n\n if (!sameOrigin(requestUrl, locationUrl)) {\n request.headers.delete('authorization')\n request.headers.delete('proxy-authorization')\n request.headers.delete('cookie')\n request.headers.delete('host')\n }\n\n /**\n * @note Undici \"safely\" extracts the request body.\n * I suspect we cannot dispatch this request again\n * since its body has been read and the stream is locked.\n */\n\n requestInit.headers = request.headers\n const finalResponse = await fetch(new Request(locationUrl, requestInit))\n Object.defineProperty(finalResponse, 'redirected', {\n value: true,\n configurable: true,\n })\n\n return finalResponse\n}\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761\n */\nfunction sameOrigin(left: URL, right: URL): boolean {\n if (left.origin === right.origin && left.origin === 'null') {\n return true\n }\n\n if (\n left.protocol === right.protocol &&\n left.hostname === right.hostname &&\n left.port === right.port\n ) {\n return true\n }\n\n return false\n}\n","import zlib from 'node:zlib'\n\nexport class BrotliDecompressionStream extends TransformStream {\n constructor() {\n const decompress = zlib.createBrotliDecompress({\n flush: zlib.constants.BROTLI_OPERATION_FLUSH,\n finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,\n })\n\n super({\n async transform(chunk, controller) {\n const buffer = Buffer.from(chunk)\n\n const decompressed = await new Promise<Buffer>((resolve, reject) => {\n decompress.write(buffer, (error) => {\n if (error) reject(error)\n })\n\n decompress.flush()\n decompress.once('data', (data) => resolve(data))\n decompress.once('error', (error) => reject(error))\n decompress.once('end', () => controller.terminate())\n }).catch((error) => {\n controller.error(error)\n })\n\n controller.enqueue(decompressed)\n },\n })\n }\n}\n","// Import from an internal alias that resolves to different modules\n// depending on the environment. This way, we can keep the fetch interceptor\n// intact while using different strategies for Brotli decompression.\nimport { BrotliDecompressionStream } from 'internal:brotli-decompress'\n\nclass PipelineStream extends TransformStream {\n constructor(\n transformStreams: Array<TransformStream>,\n ...strategies: Array<QueuingStrategy>\n ) {\n super({}, ...strategies)\n\n const readable = [super.readable as any, ...transformStreams].reduce(\n (readable, transform) => readable.pipeThrough(transform)\n )\n\n Object.defineProperty(this, 'readable', {\n get() {\n return readable\n },\n })\n }\n}\n\nexport function parseContentEncoding(contentEncoding: string): Array<string> {\n return contentEncoding\n .toLowerCase()\n .split(',')\n .map((coding) => coding.trim())\n}\n\nfunction createDecompressionStream(\n contentEncoding: string\n): TransformStream | null {\n if (contentEncoding === '') {\n return null\n }\n\n const codings = parseContentEncoding(contentEncoding)\n\n if (codings.length === 0) {\n return null\n }\n\n const transformers = codings.reduceRight<Array<TransformStream>>(\n (transformers, coding) => {\n if (coding === 'gzip' || coding === 'x-gzip') {\n return transformers.concat(new DecompressionStream('gzip'))\n } else if (coding === 'deflate') {\n return transformers.concat(new DecompressionStream('deflate'))\n } else if (coding === 'br') {\n return transformers.concat(new BrotliDecompressionStream())\n } else {\n transformers.length = 0\n }\n\n return transformers\n },\n []\n )\n\n return new PipelineStream(transformers)\n}\n\nexport function decompressResponse(\n response: Response\n): ReadableStream<any> | null {\n if (response.body === null) {\n return null\n }\n\n const decompressionStream = createDecompressionStream(\n response.headers.get('content-encoding') || ''\n )\n\n if (!decompressionStream) {\n return null\n }\n\n // Use `pipeTo` and return the decompression stream's readable\n // instead of `pipeThrough` because that will lock the original\n // response stream, making it unusable as the input to Response.\n response.body.pipeTo(decompressionStream.writable)\n return decompressionStream.readable\n}\n","import { until } from '@open-draft/until'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport { createNetworkError } from './utils/createNetworkError'\nimport { followFetchRedirect } from './utils/followRedirect'\nimport { decompressResponse } from './utils/decompression'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { FetchRequest, FetchResponse } from '../../utils/fetchUtils'\nimport { setRawRequest } from '../../getRawRequest'\nimport { isResponseError } from '../../utils/responseUtils'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('fetch')\n }\n\n protected async setup() {\n const logger = this.logger.extend('setup')\n\n const pureFetch = globalThis.fetch\n\n const fetchProxy: typeof fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.href)\n : input\n\n const request = new FetchRequest(resolvedInput, init)\n\n /**\n * @note Set the raw request only if a Request instance was provided to fetch.\n */\n if (input instanceof Request) {\n setRawRequest(request, input)\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const controller = new RequestController(request, {\n passthrough: async () => {\n this.logger.info('request has not been handled, passthrough...')\n\n /**\n * @note Clone the request instance right before performing it.\n * This preserves any modifications made to the intercepted request\n * in the \"request\" listener. This also allows the user to read the\n * request body in the \"response\" listener (otherwise \"unusable\").\n */\n const requestCloneForResponseEvent = request.clone()\n\n // Perform the intercepted request as-is.\n const { error: responseError, data: originalResponse } = await until(\n () => pureFetch(request)\n )\n\n if (responseError) {\n return responsePromise.reject(responseError)\n }\n\n this.logger.info('original fetch performed', originalResponse)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = FetchResponse.clone(originalResponse)\n await emitAsync(this.emitter, 'response', {\n response: responseClone,\n isMockedResponse: false,\n request: requestCloneForResponseEvent,\n requestId,\n })\n }\n\n // Resolve the response promise with the original response\n // since the `fetch()` return this internal promise.\n responsePromise.resolve(originalResponse)\n },\n respondWith: async (rawResponse) => {\n // Handle mocked `Response.error()` (i.e. request errors).\n if (isResponseError(rawResponse)) {\n this.logger.info('request has errored!', { response: rawResponse })\n responsePromise.reject(createNetworkError(rawResponse))\n return\n }\n\n this.logger.info('received mocked response!', {\n rawResponse,\n })\n\n // Decompress the mocked response body, if applicable.\n const decompressedStream = decompressResponse(rawResponse)\n const response = new FetchResponse(\n decompressedStream || rawResponse.body,\n {\n url: request.url,\n status: rawResponse.status,\n statusText: rawResponse.statusText,\n headers: rawResponse.headers,\n }\n )\n\n /**\n * Undici's handling of following redirect responses.\n * Treat the \"manual\" redirect mode as a regular mocked response.\n * This way, the client can manually follow the redirect it receives.\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173\n */\n if (FetchResponse.isRedirectResponse(response.status)) {\n // Reject the request promise if its `redirect` is set to `error`\n // and it receives a mocked redirect response.\n if (request.redirect === 'error') {\n responsePromise.reject(createNetworkError('unexpected redirect'))\n return\n }\n\n if (request.redirect === 'follow') {\n followFetchRedirect(request, response).then(\n (response) => {\n responsePromise.resolve(response)\n },\n (reason) => {\n responsePromise.reject(reason)\n }\n )\n return\n }\n }\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: FetchResponse.clone(response),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n responsePromise.resolve(response)\n },\n errorWith: (reason) => {\n this.logger.info('request has been aborted!', { reason })\n responsePromise.reject(reason)\n },\n })\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n })\n\n return responsePromise\n }\n\n logger.info('patching global fetch...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'fetch', () => fetchProxy)\n )\n\n logger.info('global fetch patched!', globalThis.fetch.name)\n }\n}\n"],"mappings":"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"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-umV5xXBy.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-B6znU6il.cjs'); | ||
| const require_handleRequest = require('./handleRequest-DVOthWJo.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-BtHi5OlL.cjs'); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let _open_draft_until = require("@open-draft/until"); | ||
| let node_zlib = require("node:zlib"); | ||
| node_zlib = require_chunk.__toESM(node_zlib); | ||
| //#region src/interceptors/fetch/utils/createNetworkError.ts | ||
| function createNetworkError(cause) { | ||
| return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/followRedirect.ts | ||
| const REQUEST_BODY_HEADERS = [ | ||
| "content-encoding", | ||
| "content-language", | ||
| "content-location", | ||
| "content-type", | ||
| "content-length" | ||
| ]; | ||
| const kRedirectCount = Symbol("kRedirectCount"); | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210 | ||
| */ | ||
| 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"); | ||
| } | ||
| /** | ||
| * @note Undici "safely" extracts the request body. | ||
| * I suspect we cannot dispatch this request again | ||
| * since its body has been read and the stream is locked. | ||
| */ | ||
| requestInit.headers = request.headers; | ||
| const finalResponse = await fetch(new Request(locationUrl, requestInit)); | ||
| Object.defineProperty(finalResponse, "redirected", { | ||
| value: true, | ||
| configurable: true | ||
| }); | ||
| return finalResponse; | ||
| } | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761 | ||
| */ | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/brotli-decompress.ts | ||
| var BrotliDecompressionStream = class extends TransformStream { | ||
| constructor() { | ||
| const decompress = node_zlib.default.createBrotliDecompress({ | ||
| flush: node_zlib.default.constants.BROTLI_OPERATION_FLUSH, | ||
| finishFlush: node_zlib.default.constants.BROTLI_OPERATION_FLUSH | ||
| }); | ||
| super({ async transform(chunk, controller) { | ||
| const buffer = Buffer.from(chunk); | ||
| const decompressed = await new Promise((resolve, reject) => { | ||
| decompress.write(buffer, (error) => { | ||
| if (error) reject(error); | ||
| }); | ||
| decompress.flush(); | ||
| decompress.once("data", (data) => resolve(data)); | ||
| decompress.once("error", (error) => reject(error)); | ||
| decompress.once("end", () => controller.terminate()); | ||
| }).catch((error) => { | ||
| controller.error(error); | ||
| }); | ||
| controller.enqueue(decompressed); | ||
| } }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/decompression.ts | ||
| var PipelineStream = class extends TransformStream { | ||
| constructor(transformStreams, ...strategies) { | ||
| super({}, ...strategies); | ||
| const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform)); | ||
| Object.defineProperty(this, "readable", { get() { | ||
| return readable; | ||
| } }); | ||
| } | ||
| }; | ||
| function parseContentEncoding(contentEncoding) { | ||
| return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim()); | ||
| } | ||
| function createDecompressionStream(contentEncoding) { | ||
| if (contentEncoding === "") return null; | ||
| const codings = parseContentEncoding(contentEncoding); | ||
| if (codings.length === 0) return null; | ||
| return new PipelineStream(codings.reduceRight((transformers, coding) => { | ||
| if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip")); | ||
| else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate")); | ||
| else if (coding === "br") return transformers.concat(new BrotliDecompressionStream()); | ||
| else transformers.length = 0; | ||
| return transformers; | ||
| }, [])); | ||
| } | ||
| function decompressResponse(response) { | ||
| if (response.body === null) return null; | ||
| const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || ""); | ||
| if (!decompressionStream) return null; | ||
| response.body.pipeTo(decompressionStream.writable); | ||
| return decompressionStream.readable; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/index.ts | ||
| var FetchInterceptor = class FetchInterceptor extends require_fetchUtils.Interceptor { | ||
| static { | ||
| this.symbol = Symbol("fetch"); | ||
| } | ||
| constructor() { | ||
| super(FetchInterceptor.symbol); | ||
| } | ||
| checkEnvironment() { | ||
| return require_hasConfigurableGlobal.hasConfigurableGlobal("fetch"); | ||
| } | ||
| async setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| const pureFetch = globalThis.fetch; | ||
| const fetchProxy = async (input, init) => { | ||
| const requestId = require_fetchUtils.createRequestId(); | ||
| const request = new require_fetchUtils.FetchRequest(typeof input === "string" && typeof location !== "undefined" && !require_fetchUtils.canParseUrl(input) ? new URL(input, location.href) : input, init); | ||
| /** | ||
| * @note Set the raw request only if a Request instance was provided to fetch. | ||
| */ | ||
| if (input instanceof Request) require_getRawRequest.setRawRequest(request, input); | ||
| const responsePromise = new _open_draft_deferred_promise.DeferredPromise(); | ||
| const controller = new require_fetchUtils.RequestController(request, { | ||
| passthrough: async () => { | ||
| this.logger.info("request has not been handled, passthrough..."); | ||
| /** | ||
| * @note Clone the request instance right before performing it. | ||
| * This preserves any modifications made to the intercepted request | ||
| * in the "request" listener. This also allows the user to read the | ||
| * request body in the "response" listener (otherwise "unusable"). | ||
| */ | ||
| const requestCloneForResponseEvent = request.clone(); | ||
| const { error: responseError, data: originalResponse } = await (0, _open_draft_until.until)(() => pureFetch(request)); | ||
| if (responseError) return responsePromise.reject(responseError); | ||
| this.logger.info("original fetch performed", originalResponse); | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| const responseClone = require_fetchUtils.FetchResponse.clone(originalResponse); | ||
| await require_handleRequest.emitAsync(this.emitter, "response", { | ||
| response: responseClone, | ||
| isMockedResponse: false, | ||
| request: requestCloneForResponseEvent, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(originalResponse); | ||
| }, | ||
| respondWith: async (rawResponse) => { | ||
| if (require_handleRequest.isResponseError(rawResponse)) { | ||
| this.logger.info("request has errored!", { response: rawResponse }); | ||
| responsePromise.reject(createNetworkError(rawResponse)); | ||
| return; | ||
| } | ||
| this.logger.info("received mocked response!", { rawResponse }); | ||
| const response = new require_fetchUtils.FetchResponse(decompressResponse(rawResponse) || rawResponse.body, { | ||
| url: request.url, | ||
| status: rawResponse.status, | ||
| statusText: rawResponse.statusText, | ||
| headers: rawResponse.headers | ||
| }); | ||
| /** | ||
| * Undici's handling of following redirect responses. | ||
| * Treat the "manual" redirect mode as a regular mocked response. | ||
| * This way, the client can manually follow the redirect it receives. | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173 | ||
| */ | ||
| if (require_fetchUtils.FetchResponse.isRedirectResponse(response.status)) { | ||
| if (request.redirect === "error") { | ||
| responsePromise.reject(createNetworkError("unexpected redirect")); | ||
| return; | ||
| } | ||
| if (request.redirect === "follow") { | ||
| followFetchRedirect(request, response).then((response$1) => { | ||
| responsePromise.resolve(response$1); | ||
| }, (reason) => { | ||
| responsePromise.reject(reason); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| await require_handleRequest.emitAsync(this.emitter, "response", { | ||
| response: require_fetchUtils.FetchResponse.clone(response), | ||
| isMockedResponse: true, | ||
| request, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request has been aborted!", { reason }); | ||
| responsePromise.reject(reason); | ||
| } | ||
| }); | ||
| 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")); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId, | ||
| emitter: this.emitter, | ||
| controller | ||
| }); | ||
| return responsePromise; | ||
| }; | ||
| logger.info("patching global fetch..."); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.patchesRegistry.applyPatch(globalThis, "fetch", () => fetchProxy)); | ||
| logger.info("global fetch patched!", globalThis.fetch.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'FetchInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=fetch-znfXVEPe.cjs.map |
| {"version":3,"file":"fetch-znfXVEPe.cjs","names":["locationUrl: URL","requestInit: RequestInit","zlib","readable","Interceptor","hasConfigurableGlobal","fetchProxy: typeof fetch","createRequestId","FetchRequest","canParseUrl","DeferredPromise","RequestController","FetchResponse","emitAsync","isResponseError","response","handleRequest","patchesRegistry"],"sources":["../../src/interceptors/fetch/utils/createNetworkError.ts","../../src/interceptors/fetch/utils/followRedirect.ts","../../src/interceptors/fetch/utils/brotli-decompress.ts","../../src/interceptors/fetch/utils/decompression.ts","../../src/interceptors/fetch/index.ts"],"sourcesContent":["export function createNetworkError(cause?: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","import { createNetworkError } from './createNetworkError'\n\nconst REQUEST_BODY_HEADERS = [\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-type',\n 'content-length',\n]\n\nconst kRedirectCount = Symbol('kRedirectCount')\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210\n */\nexport async function followFetchRedirect(\n request: Request,\n response: Response\n): Promise<Response> {\n if (response.status !== 303 && request.body != null) {\n return Promise.reject(createNetworkError())\n }\n\n const requestUrl = new URL(request.url)\n\n let locationUrl: URL\n try {\n // If the location is a relative URL, use the request URL as the base URL.\n locationUrl = new URL(response.headers.get('location')!, request.url) \n } catch (error) {\n return Promise.reject(createNetworkError(error))\n }\n\n if (\n !(locationUrl.protocol === 'http:' || locationUrl.protocol === 'https:')\n ) {\n return Promise.reject(\n createNetworkError('URL scheme must be a HTTP(S) scheme')\n )\n }\n\n if (Reflect.get(request, kRedirectCount) > 20) {\n return Promise.reject(createNetworkError('redirect count exceeded'))\n }\n\n Object.defineProperty(request, kRedirectCount, {\n value: (Reflect.get(request, kRedirectCount) || 0) + 1,\n })\n\n if (\n request.mode === 'cors' &&\n (locationUrl.username || locationUrl.password) &&\n !sameOrigin(requestUrl, locationUrl)\n ) {\n return Promise.reject(\n createNetworkError('cross origin not allowed for request mode \"cors\"')\n )\n }\n\n const requestInit: RequestInit = {}\n\n if (\n ([301, 302].includes(response.status) && request.method === 'POST') ||\n (response.status === 303 && !['HEAD', 'GET'].includes(request.method))\n ) {\n requestInit.method = 'GET'\n requestInit.body = null\n\n REQUEST_BODY_HEADERS.forEach((headerName) => {\n request.headers.delete(headerName)\n })\n }\n\n if (!sameOrigin(requestUrl, locationUrl)) {\n request.headers.delete('authorization')\n request.headers.delete('proxy-authorization')\n request.headers.delete('cookie')\n request.headers.delete('host')\n }\n\n /**\n * @note Undici \"safely\" extracts the request body.\n * I suspect we cannot dispatch this request again\n * since its body has been read and the stream is locked.\n */\n\n requestInit.headers = request.headers\n const finalResponse = await fetch(new Request(locationUrl, requestInit))\n Object.defineProperty(finalResponse, 'redirected', {\n value: true,\n configurable: true,\n })\n\n return finalResponse\n}\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761\n */\nfunction sameOrigin(left: URL, right: URL): boolean {\n if (left.origin === right.origin && left.origin === 'null') {\n return true\n }\n\n if (\n left.protocol === right.protocol &&\n left.hostname === right.hostname &&\n left.port === right.port\n ) {\n return true\n }\n\n return false\n}\n","import zlib from 'node:zlib'\n\nexport class BrotliDecompressionStream extends TransformStream {\n constructor() {\n const decompress = zlib.createBrotliDecompress({\n flush: zlib.constants.BROTLI_OPERATION_FLUSH,\n finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,\n })\n\n super({\n async transform(chunk, controller) {\n const buffer = Buffer.from(chunk)\n\n const decompressed = await new Promise<Buffer>((resolve, reject) => {\n decompress.write(buffer, (error) => {\n if (error) reject(error)\n })\n\n decompress.flush()\n decompress.once('data', (data) => resolve(data))\n decompress.once('error', (error) => reject(error))\n decompress.once('end', () => controller.terminate())\n }).catch((error) => {\n controller.error(error)\n })\n\n controller.enqueue(decompressed)\n },\n })\n }\n}\n","// Import from an internal alias that resolves to different modules\n// depending on the environment. This way, we can keep the fetch interceptor\n// intact while using different strategies for Brotli decompression.\nimport { BrotliDecompressionStream } from 'internal:brotli-decompress'\n\nclass PipelineStream extends TransformStream {\n constructor(\n transformStreams: Array<TransformStream>,\n ...strategies: Array<QueuingStrategy>\n ) {\n super({}, ...strategies)\n\n const readable = [super.readable as any, ...transformStreams].reduce(\n (readable, transform) => readable.pipeThrough(transform)\n )\n\n Object.defineProperty(this, 'readable', {\n get() {\n return readable\n },\n })\n }\n}\n\nexport function parseContentEncoding(contentEncoding: string): Array<string> {\n return contentEncoding\n .toLowerCase()\n .split(',')\n .map((coding) => coding.trim())\n}\n\nfunction createDecompressionStream(\n contentEncoding: string\n): TransformStream | null {\n if (contentEncoding === '') {\n return null\n }\n\n const codings = parseContentEncoding(contentEncoding)\n\n if (codings.length === 0) {\n return null\n }\n\n const transformers = codings.reduceRight<Array<TransformStream>>(\n (transformers, coding) => {\n if (coding === 'gzip' || coding === 'x-gzip') {\n return transformers.concat(new DecompressionStream('gzip'))\n } else if (coding === 'deflate') {\n return transformers.concat(new DecompressionStream('deflate'))\n } else if (coding === 'br') {\n return transformers.concat(new BrotliDecompressionStream())\n } else {\n transformers.length = 0\n }\n\n return transformers\n },\n []\n )\n\n return new PipelineStream(transformers)\n}\n\nexport function decompressResponse(\n response: Response\n): ReadableStream<any> | null {\n if (response.body === null) {\n return null\n }\n\n const decompressionStream = createDecompressionStream(\n response.headers.get('content-encoding') || ''\n )\n\n if (!decompressionStream) {\n return null\n }\n\n // Use `pipeTo` and return the decompression stream's readable\n // instead of `pipeThrough` because that will lock the original\n // response stream, making it unusable as the input to Response.\n response.body.pipeTo(decompressionStream.writable)\n return decompressionStream.readable\n}\n","import { until } from '@open-draft/until'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport { createNetworkError } from './utils/createNetworkError'\nimport { followFetchRedirect } from './utils/followRedirect'\nimport { decompressResponse } from './utils/decompression'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { FetchRequest, FetchResponse } from '../../utils/fetchUtils'\nimport { setRawRequest } from '../../getRawRequest'\nimport { isResponseError } from '../../utils/responseUtils'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('fetch')\n }\n\n protected async setup() {\n const logger = this.logger.extend('setup')\n\n const pureFetch = globalThis.fetch\n\n const fetchProxy: typeof fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.href)\n : input\n\n const request = new FetchRequest(resolvedInput, init)\n\n /**\n * @note Set the raw request only if a Request instance was provided to fetch.\n */\n if (input instanceof Request) {\n setRawRequest(request, input)\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const controller = new RequestController(request, {\n passthrough: async () => {\n this.logger.info('request has not been handled, passthrough...')\n\n /**\n * @note Clone the request instance right before performing it.\n * This preserves any modifications made to the intercepted request\n * in the \"request\" listener. This also allows the user to read the\n * request body in the \"response\" listener (otherwise \"unusable\").\n */\n const requestCloneForResponseEvent = request.clone()\n\n // Perform the intercepted request as-is.\n const { error: responseError, data: originalResponse } = await until(\n () => pureFetch(request)\n )\n\n if (responseError) {\n return responsePromise.reject(responseError)\n }\n\n this.logger.info('original fetch performed', originalResponse)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = FetchResponse.clone(originalResponse)\n await emitAsync(this.emitter, 'response', {\n response: responseClone,\n isMockedResponse: false,\n request: requestCloneForResponseEvent,\n requestId,\n })\n }\n\n // Resolve the response promise with the original response\n // since the `fetch()` return this internal promise.\n responsePromise.resolve(originalResponse)\n },\n respondWith: async (rawResponse) => {\n // Handle mocked `Response.error()` (i.e. request errors).\n if (isResponseError(rawResponse)) {\n this.logger.info('request has errored!', { response: rawResponse })\n responsePromise.reject(createNetworkError(rawResponse))\n return\n }\n\n this.logger.info('received mocked response!', {\n rawResponse,\n })\n\n // Decompress the mocked response body, if applicable.\n const decompressedStream = decompressResponse(rawResponse)\n const response = new FetchResponse(\n decompressedStream || rawResponse.body,\n {\n url: request.url,\n status: rawResponse.status,\n statusText: rawResponse.statusText,\n headers: rawResponse.headers,\n }\n )\n\n /**\n * Undici's handling of following redirect responses.\n * Treat the \"manual\" redirect mode as a regular mocked response.\n * This way, the client can manually follow the redirect it receives.\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173\n */\n if (FetchResponse.isRedirectResponse(response.status)) {\n // Reject the request promise if its `redirect` is set to `error`\n // and it receives a mocked redirect response.\n if (request.redirect === 'error') {\n responsePromise.reject(createNetworkError('unexpected redirect'))\n return\n }\n\n if (request.redirect === 'follow') {\n followFetchRedirect(request, response).then(\n (response) => {\n responsePromise.resolve(response)\n },\n (reason) => {\n responsePromise.reject(reason)\n }\n )\n return\n }\n }\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: FetchResponse.clone(response),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n responsePromise.resolve(response)\n },\n errorWith: (reason) => {\n this.logger.info('request has been aborted!', { reason })\n responsePromise.reject(reason)\n },\n })\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n })\n\n return responsePromise\n }\n\n logger.info('patching global fetch...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'fetch', () => fetchProxy)\n )\n\n logger.info('global fetch patched!', globalThis.fetch.name)\n }\n}\n"],"mappings":";;;;;;;;;;;AAAA,SAAgB,mBAAmB,OAAiB;AAClD,QAAO,OAAO,uBAAO,IAAI,UAAU,kBAAkB,EAAE,EACrD,OACD,CAAC;;;;;ACDJ,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,iBAAiB,OAAO,iBAAiB;;;;AAK/C,eAAsB,oBACpB,SACA,UACmB;AACnB,KAAI,SAAS,WAAW,OAAO,QAAQ,QAAQ,KAC7C,QAAO,QAAQ,OAAO,oBAAoB,CAAC;CAG7C,MAAM,aAAa,IAAI,IAAI,QAAQ,IAAI;CAEvC,IAAIA;AACJ,KAAI;AAEF,gBAAc,IAAI,IAAI,SAAS,QAAQ,IAAI,WAAW,EAAG,QAAQ,IAAI;UAC9D,OAAO;AACd,SAAO,QAAQ,OAAO,mBAAmB,MAAM,CAAC;;AAGlD,KACE,EAAE,YAAY,aAAa,WAAW,YAAY,aAAa,UAE/D,QAAO,QAAQ,OACb,mBAAmB,sCAAsC,CAC1D;AAGH,KAAI,QAAQ,IAAI,SAAS,eAAe,GAAG,GACzC,QAAO,QAAQ,OAAO,mBAAmB,0BAA0B,CAAC;AAGtE,QAAO,eAAe,SAAS,gBAAgB,EAC7C,QAAQ,QAAQ,IAAI,SAAS,eAAe,IAAI,KAAK,GACtD,CAAC;AAEF,KACE,QAAQ,SAAS,WAChB,YAAY,YAAY,YAAY,aACrC,CAAC,WAAW,YAAY,YAAY,CAEpC,QAAO,QAAQ,OACb,mBAAmB,qDAAmD,CACvE;CAGH,MAAMC,cAA2B,EAAE;AAEnC,KACG,CAAC,KAAK,IAAI,CAAC,SAAS,SAAS,OAAO,IAAI,QAAQ,WAAW,UAC3D,SAAS,WAAW,OAAO,CAAC,CAAC,QAAQ,MAAM,CAAC,SAAS,QAAQ,OAAO,EACrE;AACA,cAAY,SAAS;AACrB,cAAY,OAAO;AAEnB,uBAAqB,SAAS,eAAe;AAC3C,WAAQ,QAAQ,OAAO,WAAW;IAClC;;AAGJ,KAAI,CAAC,WAAW,YAAY,YAAY,EAAE;AACxC,UAAQ,QAAQ,OAAO,gBAAgB;AACvC,UAAQ,QAAQ,OAAO,sBAAsB;AAC7C,UAAQ,QAAQ,OAAO,SAAS;AAChC,UAAQ,QAAQ,OAAO,OAAO;;;;;;;AAShC,aAAY,UAAU,QAAQ;CAC9B,MAAM,gBAAgB,MAAM,MAAM,IAAI,QAAQ,aAAa,YAAY,CAAC;AACxE,QAAO,eAAe,eAAe,cAAc;EACjD,OAAO;EACP,cAAc;EACf,CAAC;AAEF,QAAO;;;;;AAMT,SAAS,WAAW,MAAW,OAAqB;AAClD,KAAI,KAAK,WAAW,MAAM,UAAU,KAAK,WAAW,OAClD,QAAO;AAGT,KACE,KAAK,aAAa,MAAM,YACxB,KAAK,aAAa,MAAM,YACxB,KAAK,SAAS,MAAM,KAEpB,QAAO;AAGT,QAAO;;;;;AC9GT,IAAa,4BAAb,cAA+C,gBAAgB;CAC7D,cAAc;EACZ,MAAM,aAAaC,kBAAK,uBAAuB;GAC7C,OAAOA,kBAAK,UAAU;GACtB,aAAaA,kBAAK,UAAU;GAC7B,CAAC;AAEF,QAAM,EACJ,MAAM,UAAU,OAAO,YAAY;GACjC,MAAM,SAAS,OAAO,KAAK,MAAM;GAEjC,MAAM,eAAe,MAAM,IAAI,SAAiB,SAAS,WAAW;AAClE,eAAW,MAAM,SAAS,UAAU;AAClC,SAAI,MAAO,QAAO,MAAM;MACxB;AAEF,eAAW,OAAO;AAClB,eAAW,KAAK,SAAS,SAAS,QAAQ,KAAK,CAAC;AAChD,eAAW,KAAK,UAAU,UAAU,OAAO,MAAM,CAAC;AAClD,eAAW,KAAK,aAAa,WAAW,WAAW,CAAC;KACpD,CAAC,OAAO,UAAU;AAClB,eAAW,MAAM,MAAM;KACvB;AAEF,cAAW,QAAQ,aAAa;KAEnC,CAAC;;;;;;ACvBN,IAAM,iBAAN,cAA6B,gBAAgB;CAC3C,YACE,kBACA,GAAG,YACH;AACA,QAAM,EAAE,EAAE,GAAG,WAAW;EAExB,MAAM,WAAW,CAAC,MAAM,UAAiB,GAAG,iBAAiB,CAAC,QAC3D,YAAU,cAAcC,WAAS,YAAY,UAAU,CACzD;AAED,SAAO,eAAe,MAAM,YAAY,EACtC,MAAM;AACJ,UAAO;KAEV,CAAC;;;AAIN,SAAgB,qBAAqB,iBAAwC;AAC3E,QAAO,gBACJ,aAAa,CACb,MAAM,IAAI,CACV,KAAK,WAAW,OAAO,MAAM,CAAC;;AAGnC,SAAS,0BACP,iBACwB;AACxB,KAAI,oBAAoB,GACtB,QAAO;CAGT,MAAM,UAAU,qBAAqB,gBAAgB;AAErD,KAAI,QAAQ,WAAW,EACrB,QAAO;AAoBT,QAAO,IAAI,eAjBU,QAAQ,aAC1B,cAAc,WAAW;AACxB,MAAI,WAAW,UAAU,WAAW,SAClC,QAAO,aAAa,OAAO,IAAI,oBAAoB,OAAO,CAAC;WAClD,WAAW,UACpB,QAAO,aAAa,OAAO,IAAI,oBAAoB,UAAU,CAAC;WACrD,WAAW,KACpB,QAAO,aAAa,OAAO,IAAI,2BAA2B,CAAC;MAE3D,cAAa,SAAS;AAGxB,SAAO;IAET,EAAE,CACH,CAEsC;;AAGzC,SAAgB,mBACd,UAC4B;AAC5B,KAAI,SAAS,SAAS,KACpB,QAAO;CAGT,MAAM,sBAAsB,0BAC1B,SAAS,QAAQ,IAAI,mBAAmB,IAAI,GAC7C;AAED,KAAI,CAAC,oBACH,QAAO;AAMT,UAAS,KAAK,OAAO,oBAAoB,SAAS;AAClD,QAAO,oBAAoB;;;;;ACjE7B,IAAa,mBAAb,MAAa,yBAAyBC,+BAAiC;;gBACrD,OAAO,QAAQ;;CAE/B,cAAc;AACZ,QAAM,iBAAiB,OAAO;;CAGhC,AAAU,mBAAmB;AAC3B,SAAOC,oDAAsB,QAAQ;;CAGvC,MAAgB,QAAQ;EACtB,MAAM,SAAS,KAAK,OAAO,OAAO,QAAQ;EAE1C,MAAM,YAAY,WAAW;EAE7B,MAAMC,aAA2B,OAAO,OAAO,SAAS;GACtD,MAAM,YAAYC,oCAAiB;GAenC,MAAM,UAAU,IAAIC,gCANlB,OAAO,UAAU,YACjB,OAAO,aAAa,eACpB,CAACC,+BAAY,MAAM,GACf,IAAI,IAAI,OAAO,SAAS,KAAK,GAC7B,OAE0C,KAAK;;;;AAKrD,OAAI,iBAAiB,QACnB,qCAAc,SAAS,MAAM;GAG/B,MAAM,kBAAkB,IAAIC,8CAA2B;GAEvD,MAAM,aAAa,IAAIC,qCAAkB,SAAS;IAChD,aAAa,YAAY;AACvB,UAAK,OAAO,KAAK,+CAA+C;;;;;;;KAQhE,MAAM,+BAA+B,QAAQ,OAAO;KAGpD,MAAM,EAAE,OAAO,eAAe,MAAM,qBAAqB,yCACjD,UAAU,QAAQ,CACzB;AAED,SAAI,cACF,QAAO,gBAAgB,OAAO,cAAc;AAG9C,UAAK,OAAO,KAAK,4BAA4B,iBAAiB;AAE9D,SAAI,KAAK,QAAQ,cAAc,WAAW,GAAG,GAAG;AAC9C,WAAK,OAAO,KAAK,qCAAmC;MAEpD,MAAM,gBAAgBC,iCAAc,MAAM,iBAAiB;AAC3D,YAAMC,gCAAU,KAAK,SAAS,YAAY;OACxC,UAAU;OACV,kBAAkB;OAClB,SAAS;OACT;OACD,CAAC;;AAKJ,qBAAgB,QAAQ,iBAAiB;;IAE3C,aAAa,OAAO,gBAAgB;AAElC,SAAIC,sCAAgB,YAAY,EAAE;AAChC,WAAK,OAAO,KAAK,wBAAwB,EAAE,UAAU,aAAa,CAAC;AACnE,sBAAgB,OAAO,mBAAmB,YAAY,CAAC;AACvD;;AAGF,UAAK,OAAO,KAAK,6BAA6B,EAC5C,aACD,CAAC;KAIF,MAAM,WAAW,IAAIF,iCADM,mBAAmB,YAAY,IAElC,YAAY,MAClC;MACE,KAAK,QAAQ;MACb,QAAQ,YAAY;MACpB,YAAY,YAAY;MACxB,SAAS,YAAY;MACtB,CACF;;;;;;;AAQD,SAAIA,iCAAc,mBAAmB,SAAS,OAAO,EAAE;AAGrD,UAAI,QAAQ,aAAa,SAAS;AAChC,uBAAgB,OAAO,mBAAmB,sBAAsB,CAAC;AACjE;;AAGF,UAAI,QAAQ,aAAa,UAAU;AACjC,2BAAoB,SAAS,SAAS,CAAC,MACpC,eAAa;AACZ,wBAAgB,QAAQG,WAAS;WAElC,WAAW;AACV,wBAAgB,OAAO,OAAO;SAEjC;AACD;;;AAIJ,SAAI,KAAK,QAAQ,cAAc,WAAW,GAAG,GAAG;AAC9C,WAAK,OAAO,KAAK,qCAAmC;AAKpD,YAAMF,gCAAU,KAAK,SAAS,YAAY;OAIxC,UAAUD,iCAAc,MAAM,SAAS;OACvC,kBAAkB;OAClB;OACA;OACD,CAAC;;AAGJ,qBAAgB,QAAQ,SAAS;;IAEnC,YAAY,WAAW;AACrB,UAAK,OAAO,KAAK,6BAA6B,EAAE,QAAQ,CAAC;AACzD,qBAAgB,OAAO,OAAO;;IAEjC,CAAC;AAEF,QAAK,OAAO,KAAK,WAAW,QAAQ,QAAQ,QAAQ,IAAI;AACxD,QAAK,OAAO,KAAK,sCAAsC;AAEvD,QAAK,OAAO,KACV,wDACA,KAAK,QAAQ,cAAc,UAAU,CACtC;AAED,SAAMI,oCAAc;IAClB;IACA;IACA,SAAS,KAAK;IACd;IACD,CAAC;AAEF,UAAO;;AAGT,SAAO,KAAK,2BAA2B;AAEvC,OAAK,cAAc,KACjBC,8CAAgB,WAAW,YAAY,eAAe,WAAW,CAClE;AAED,SAAO,KAAK,yBAAyB,WAAW,MAAM,KAAK"} |
| import { Logger } from "@open-draft/logger"; | ||
| import { Emitter } from "strict-event-emitter"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { invariant } from "outvariant"; | ||
| //#region src/Interceptor.ts | ||
| /** | ||
| * Request header name to detect when a single request | ||
| * is being handled by nested interceptors (XHR -> ClientRequest). | ||
| * Obscure by design to prevent collisions with user-defined headers. | ||
| * Ideally, come up with the Interceptor-level mechanism for this. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| const INTERNAL_REQUEST_ID_HEADER_NAME = "x-interceptors-internal-request-id"; | ||
| function getGlobalSymbol(symbol) { | ||
| return globalThis[symbol] || void 0; | ||
| } | ||
| function setGlobalSymbol(symbol, value) { | ||
| globalThis[symbol] = value; | ||
| } | ||
| function deleteGlobalSymbol(symbol) { | ||
| delete globalThis[symbol]; | ||
| } | ||
| let InterceptorReadyState = /* @__PURE__ */ function(InterceptorReadyState$1) { | ||
| InterceptorReadyState$1["INACTIVE"] = "INACTIVE"; | ||
| InterceptorReadyState$1["APPLYING"] = "APPLYING"; | ||
| InterceptorReadyState$1["APPLIED"] = "APPLIED"; | ||
| InterceptorReadyState$1["DISPOSING"] = "DISPOSING"; | ||
| InterceptorReadyState$1["DISPOSED"] = "DISPOSED"; | ||
| return InterceptorReadyState$1; | ||
| }({}); | ||
| var Interceptor = class { | ||
| constructor(symbol) { | ||
| this.symbol = symbol; | ||
| this.readyState = InterceptorReadyState.INACTIVE; | ||
| this.emitter = new Emitter(); | ||
| this.subscriptions = []; | ||
| this.logger = new Logger(symbol.description); | ||
| this.emitter.setMaxListeners(0); | ||
| this.logger.info("constructing the interceptor..."); | ||
| } | ||
| /** | ||
| * Determine if this interceptor can be applied | ||
| * in the current environment. | ||
| */ | ||
| checkEnvironment() { | ||
| return true; | ||
| } | ||
| /** | ||
| * Apply this interceptor to the current process. | ||
| * Returns an already running interceptor instance if it's present. | ||
| */ | ||
| apply() { | ||
| const logger = this.logger.extend("apply"); | ||
| logger.info("applying the interceptor..."); | ||
| if (this.readyState === InterceptorReadyState.APPLIED) { | ||
| logger.info("intercepted already applied!"); | ||
| return; | ||
| } | ||
| if (!this.checkEnvironment()) { | ||
| logger.info("the interceptor cannot be applied in this environment!"); | ||
| return; | ||
| } | ||
| this.readyState = InterceptorReadyState.APPLYING; | ||
| const runningInstance = this.getInstance(); | ||
| if (runningInstance) { | ||
| logger.info("found a running instance, reusing..."); | ||
| this.on = (event, listener) => { | ||
| logger.info("proxying the \"%s\" listener", event); | ||
| runningInstance.emitter.addListener(event, listener); | ||
| this.subscriptions.push(() => { | ||
| runningInstance.emitter.removeListener(event, listener); | ||
| logger.info("removed proxied \"%s\" listener!", event); | ||
| }); | ||
| return this; | ||
| }; | ||
| this.readyState = InterceptorReadyState.APPLIED; | ||
| return; | ||
| } | ||
| logger.info("no running instance found, setting up a new instance..."); | ||
| this.setup(); | ||
| this.setInstance(); | ||
| this.readyState = InterceptorReadyState.APPLIED; | ||
| } | ||
| /** | ||
| * Setup the module augments and stubs necessary for this interceptor. | ||
| * This method is not run if there's a running interceptor instance | ||
| * to prevent instantiating an interceptor multiple times. | ||
| */ | ||
| setup() {} | ||
| /** | ||
| * Listen to the interceptor's public events. | ||
| */ | ||
| on(event, listener) { | ||
| const logger = this.logger.extend("on"); | ||
| if (this.readyState === InterceptorReadyState.DISPOSING || this.readyState === InterceptorReadyState.DISPOSED) { | ||
| logger.info("cannot listen to events, already disposed!"); | ||
| return this; | ||
| } | ||
| logger.info("adding \"%s\" event listener:", event, listener); | ||
| this.emitter.on(event, listener); | ||
| return this; | ||
| } | ||
| once(event, listener) { | ||
| this.emitter.once(event, listener); | ||
| return this; | ||
| } | ||
| off(event, listener) { | ||
| this.emitter.off(event, listener); | ||
| return this; | ||
| } | ||
| removeAllListeners(event) { | ||
| this.emitter.removeAllListeners(event); | ||
| return this; | ||
| } | ||
| /** | ||
| * Disposes of any side-effects this interceptor has introduced. | ||
| */ | ||
| dispose() { | ||
| const logger = this.logger.extend("dispose"); | ||
| if (this.readyState === InterceptorReadyState.DISPOSED) { | ||
| logger.info("cannot dispose, already disposed!"); | ||
| return; | ||
| } | ||
| logger.info("disposing the interceptor..."); | ||
| this.readyState = InterceptorReadyState.DISPOSING; | ||
| if (!this.getInstance()) { | ||
| logger.info("no interceptors running, skipping dispose..."); | ||
| return; | ||
| } | ||
| this.clearInstance(); | ||
| logger.info("global symbol deleted:", getGlobalSymbol(this.symbol)); | ||
| if (this.subscriptions.length > 0) { | ||
| logger.info("disposing of %d subscriptions...", this.subscriptions.length); | ||
| for (const dispose of this.subscriptions) dispose(); | ||
| this.subscriptions = []; | ||
| logger.info("disposed of all subscriptions!", this.subscriptions.length); | ||
| } | ||
| this.emitter.removeAllListeners(); | ||
| logger.info("destroyed the listener!"); | ||
| this.readyState = InterceptorReadyState.DISPOSED; | ||
| } | ||
| getInstance() { | ||
| const instance = getGlobalSymbol(this.symbol); | ||
| this.logger.info("retrieved global instance:", instance?.constructor?.name); | ||
| return instance; | ||
| } | ||
| setInstance() { | ||
| setGlobalSymbol(this.symbol, this); | ||
| this.logger.info("set global instance!", this.symbol.description); | ||
| } | ||
| clearInstance() { | ||
| deleteGlobalSymbol(this.symbol); | ||
| this.logger.info("cleared global instance!", this.symbol.description); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/InterceptorError.ts | ||
| var InterceptorError = class InterceptorError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "InterceptorError"; | ||
| Object.setPrototypeOf(this, InterceptorError.prototype); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/RequestController.ts | ||
| var RequestController = class RequestController { | ||
| static { | ||
| this.PENDING = 0; | ||
| } | ||
| static { | ||
| this.PASSTHROUGH = 1; | ||
| } | ||
| static { | ||
| this.RESPONSE = 2; | ||
| } | ||
| static { | ||
| this.ERROR = 3; | ||
| } | ||
| constructor(request, source) { | ||
| this.request = request; | ||
| this.source = source; | ||
| this.readyState = RequestController.PENDING; | ||
| this.handled = new DeferredPromise(); | ||
| } | ||
| get #handled() { | ||
| return this.handled; | ||
| } | ||
| /** | ||
| * Perform this request as-is. | ||
| */ | ||
| async passthrough() { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url); | ||
| this.readyState = RequestController.PASSTHROUGH; | ||
| await this.source.passthrough(); | ||
| this.#handled.resolve(); | ||
| } | ||
| /** | ||
| * Respond to this request with the given `Response` instance. | ||
| * | ||
| * @example | ||
| * controller.respondWith(new Response()) | ||
| * controller.respondWith(Response.json({ id })) | ||
| * controller.respondWith(Response.error()) | ||
| */ | ||
| respondWith(response) { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState); | ||
| this.readyState = RequestController.RESPONSE; | ||
| this.#handled.resolve(); | ||
| /** | ||
| * @note Although `source.respondWith()` is potentially asynchronous, | ||
| * do NOT await it for backward-compatibility. Awaiting it will short-circuit | ||
| * the request listener invocation as soon as a listener responds to a request. | ||
| * Ideally, that's what we want, but that's not what we promise the user. | ||
| */ | ||
| this.source.respondWith(response); | ||
| } | ||
| /** | ||
| * Error this request with the given reason. | ||
| * | ||
| * @example | ||
| * controller.errorWith() | ||
| * controller.errorWith(new Error('Oops!')) | ||
| * controller.errorWith({ message: 'Oops!'}) | ||
| */ | ||
| errorWith(reason) { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState); | ||
| this.readyState = RequestController.ERROR; | ||
| this.source.errorWith(reason); | ||
| this.#handled.resolve(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/createRequestId.ts | ||
| /** | ||
| * Generate a random ID string to represent a request. | ||
| * @example | ||
| * createRequestId() | ||
| * // "f774b6c9c600f" | ||
| */ | ||
| function createRequestId() { | ||
| return Math.random().toString(16).slice(2); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/canParseUrl.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given URL string | ||
| * can be parsed into a `URL` instance. | ||
| * A substitute for `URL.canParse()` for Node.js 18. | ||
| */ | ||
| function canParseUrl(url) { | ||
| try { | ||
| new URL(url); | ||
| return true; | ||
| } catch (_error) { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/getValueBySymbol.ts | ||
| /** | ||
| * Returns the value behind the symbol with the given name. | ||
| */ | ||
| function getValueBySymbol(symbolName, source) { | ||
| const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => { | ||
| return symbol$1.description === symbolName; | ||
| }); | ||
| if (symbol) return Reflect.get(source, symbol); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/fetchUtils.ts | ||
| var FetchRequest = class FetchRequest extends Request { | ||
| static #resolveProperty(input, init = {}, key) { | ||
| return init[key] ?? (input instanceof Request ? input[key] : void 0); | ||
| } | ||
| /** | ||
| * Check if the given request method is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#methods | ||
| */ | ||
| static isConfigurableMethod(method) { | ||
| return method !== "CONNECT" && method !== "TRACE" && method !== "TRACK"; | ||
| } | ||
| static isMethodWithBody(method) { | ||
| return method !== "HEAD" && method !== "GET" && FetchRequest.isConfigurableMethod(method); | ||
| } | ||
| /** | ||
| * Check if the given request `mode` is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#concept-request-mode | ||
| */ | ||
| static isConfigurableMode(mode) { | ||
| return mode !== "navigate" && mode !== "websocket" && mode !== "webtransport"; | ||
| } | ||
| constructor(input, init) { | ||
| const method = FetchRequest.#resolveProperty(input, init, "method") || "GET"; | ||
| const safeMethod = FetchRequest.isConfigurableMethod(method) ? method : "GET"; | ||
| const hasExplicitBody = init != null && "body" in init; | ||
| /** | ||
| * Only include `body` in the super init when it needs to be overridden. | ||
| * When `input` is a Request and no explicit body is in `init`, let the | ||
| * Request constructor handle body transfer naturally so it properly | ||
| * marks the original request's body as consumed (bodyUsed = true). | ||
| */ | ||
| const bodyInit = !FetchRequest.isMethodWithBody(method) ? { body: void 0 } : hasExplicitBody ? { body: init.body } : {}; | ||
| const mode = FetchRequest.#resolveProperty(input, init, "mode") ?? void 0; | ||
| const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : void 0; | ||
| super(input, { | ||
| ...init || {}, | ||
| method: safeMethod, | ||
| mode: safeMode, | ||
| duplex: init?.duplex ?? (FetchRequest.isMethodWithBody(method) ? "half" : void 0), | ||
| ...bodyInit | ||
| }); | ||
| if (method !== safeMethod) this.#setInternalProperty("method", method); | ||
| if (method === "CONNECT") { | ||
| const url = new URL(input instanceof Request ? input.url : input); | ||
| let authority; | ||
| /** | ||
| * @note Node.js has a bug parsing raw CONNECT requests URLs like | ||
| * "http://127.0.0.1:1337/localhost:80". It would treat "localhost:" as a protocol. | ||
| */ | ||
| if (url.protocol === "localhost:") authority = url.href; | ||
| else authority = url.pathname.replace(/^\/+/, ""); | ||
| /** | ||
| * @note Define "url" as a getter because Undici uses their own | ||
| * logic to resolve the "request.url" property. Simply reassigning | ||
| * its value doesn't do anything. This is a destructive action | ||
| * but it's safe because "CONNECT" requests are forbidden per fetch. | ||
| */ | ||
| Object.defineProperty(this, "url", { | ||
| get: () => authority, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| if (mode != null && mode !== safeMode) this.#setInternalProperty("mode", mode); | ||
| } | ||
| #setInternalProperty(key, value) { | ||
| const internalState = getValueBySymbol("state", this); | ||
| if (internalState) Reflect.set(internalState, key, value); | ||
| else Object.defineProperty(this, key, { | ||
| value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| }; | ||
| const kStatus = Symbol("kStatus"); | ||
| const kUrl = Symbol("kUrl"); | ||
| var FetchResponse = class FetchResponse extends Response { | ||
| static { | ||
| this.STATUS_CODES_WITHOUT_BODY = [ | ||
| 101, | ||
| 103, | ||
| 204, | ||
| 205, | ||
| 304 | ||
| ]; | ||
| } | ||
| static { | ||
| this.STATUS_CODES_WITH_REDIRECT = [ | ||
| 301, | ||
| 302, | ||
| 303, | ||
| 307, | ||
| 308 | ||
| ]; | ||
| } | ||
| static isConfigurableStatusCode(status) { | ||
| return status >= 200 && status <= 599; | ||
| } | ||
| static isRedirectResponse(status) { | ||
| return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status); | ||
| } | ||
| /** | ||
| * Returns a boolean indicating whether the given response status | ||
| * code represents a response that can have a body. | ||
| */ | ||
| static isResponseWithBody(status) { | ||
| return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status); | ||
| } | ||
| static setStatus(status, response) { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const internalState = getValueBySymbol("state", response); | ||
| if (internalState) internalState.status = status; | ||
| else Object.defineProperty(response, "status", { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| Object.defineProperty(response, kStatus, { | ||
| value: status, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| static setUrl(url, response) { | ||
| if (!url || url === "about:" || !canParseUrl(url)) return; | ||
| const state = getValueBySymbol("state", response); | ||
| if (state) state.urlList.push(new URL(url)); | ||
| else Object.defineProperty(response, "url", { | ||
| value: url, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| Object.defineProperty(response, kUrl, { | ||
| value: url, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| /** | ||
| * Parses the given raw HTTP headers into a Fetch API `Headers` instance. | ||
| */ | ||
| static parseRawHeaders(rawHeaders) { | ||
| const headers = new Headers(); | ||
| for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]); | ||
| return headers; | ||
| } | ||
| /** | ||
| * Safely clones the given `Response`. | ||
| * Coerces response clone exceptions into 500 mocked responses. | ||
| * Handy in the environments that introduce arbitrary response | ||
| * cloning restrictions, like "101 Switching Protocols" cloning | ||
| * in "miniflare". | ||
| */ | ||
| static clone(response) { | ||
| try { | ||
| return response.clone(); | ||
| } catch (error) { | ||
| return Response.json(error instanceof Error ? { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| } : {}, { | ||
| status: 500, | ||
| statusText: "Unclonable Response" | ||
| }); | ||
| } | ||
| } | ||
| constructor(body, init = {}) { | ||
| const status = init.status ?? 200; | ||
| const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200; | ||
| const finalBody = FetchResponse.isResponseWithBody(status) ? body : null; | ||
| super(finalBody, { | ||
| status: safeStatus, | ||
| statusText: init.statusText, | ||
| headers: init.headers | ||
| }); | ||
| /** | ||
| * Since Node.js v24, Undici stores the Response state in an inaccessible field "#state". | ||
| * Forward the modified status/URL to the cloned response manually. | ||
| * @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242 | ||
| */ | ||
| if (status !== safeStatus) FetchResponse.setStatus(status, this); | ||
| FetchResponse.setUrl(init.url, this); | ||
| } | ||
| clone() { | ||
| const clonedResponse = super.clone(); | ||
| const customStatus = Reflect.get(this, kStatus); | ||
| if (customStatus) FetchResponse.setStatus(customStatus, clonedResponse); | ||
| const customUrl = Reflect.get(this, kUrl); | ||
| if (customUrl) FetchResponse.setUrl(customUrl, clonedResponse); | ||
| return clonedResponse; | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { RequestController as a, Interceptor as c, getGlobalSymbol as d, createRequestId as i, InterceptorReadyState as l, FetchResponse as n, InterceptorError as o, canParseUrl as r, INTERNAL_REQUEST_ID_HEADER_NAME as s, FetchRequest as t, deleteGlobalSymbol as u }; | ||
| //# sourceMappingURL=fetchUtils-BKJ1XmiO.mjs.map |
| {"version":3,"file":"fetchUtils-BKJ1XmiO.mjs","names":["symbol: symbol","request: Request","source: RequestControllerSource","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/Interceptor.ts","../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/createRequestId.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts"],"sourcesContent":["import { Logger } from '@open-draft/logger'\nimport { Emitter, Listener } from 'strict-event-emitter'\n\nexport type InterceptorEventMap = Record<string, any>\nexport type InterceptorSubscription = () => void\n\n/**\n * Request header name to detect when a single request\n * is being handled by nested interceptors (XHR -> ClientRequest).\n * Obscure by design to prevent collisions with user-defined headers.\n * Ideally, come up with the Interceptor-level mechanism for this.\n * @see https://github.com/mswjs/interceptors/issues/378\n */\nexport const INTERNAL_REQUEST_ID_HEADER_NAME =\n 'x-interceptors-internal-request-id'\n\nexport function getGlobalSymbol<V>(symbol: Symbol): V | undefined {\n return (\n // @ts-ignore https://github.com/Microsoft/TypeScript/issues/24587\n globalThis[symbol] || undefined\n )\n}\n\nfunction setGlobalSymbol(symbol: Symbol, value: any): void {\n // @ts-ignore\n globalThis[symbol] = value\n}\n\nexport function deleteGlobalSymbol(symbol: Symbol): void {\n // @ts-ignore\n delete globalThis[symbol]\n}\n\nexport enum InterceptorReadyState {\n INACTIVE = 'INACTIVE',\n APPLYING = 'APPLYING',\n APPLIED = 'APPLIED',\n DISPOSING = 'DISPOSING',\n DISPOSED = 'DISPOSED',\n}\n\nexport type ExtractEventNames<Events extends Record<string, any>> =\n Events extends Record<infer EventName, any> ? EventName : never\n\nexport class Interceptor<Events extends InterceptorEventMap> {\n protected emitter: Emitter<Events>\n protected subscriptions: Array<InterceptorSubscription>\n protected logger: Logger\n\n public readyState: InterceptorReadyState\n\n constructor(private readonly symbol: symbol) {\n this.readyState = InterceptorReadyState.INACTIVE\n\n this.emitter = new Emitter()\n this.subscriptions = []\n this.logger = new Logger(symbol.description!)\n\n // Do not limit the maximum number of listeners\n // so not to limit the maximum amount of parallel events emitted.\n this.emitter.setMaxListeners(0)\n\n this.logger.info('constructing the interceptor...')\n }\n\n /**\n * Determine if this interceptor can be applied\n * in the current environment.\n */\n protected checkEnvironment(): boolean {\n return true\n }\n\n /**\n * Apply this interceptor to the current process.\n * Returns an already running interceptor instance if it's present.\n */\n public apply(): void {\n const logger = this.logger.extend('apply')\n logger.info('applying the interceptor...')\n\n if (this.readyState === InterceptorReadyState.APPLIED) {\n logger.info('intercepted already applied!')\n return\n }\n\n const shouldApply = this.checkEnvironment()\n\n if (!shouldApply) {\n logger.info('the interceptor cannot be applied in this environment!')\n return\n }\n\n this.readyState = InterceptorReadyState.APPLYING\n\n // Whenever applying a new interceptor, check if it hasn't been applied already.\n // This enables to apply the same interceptor multiple times, for example from a different\n // interceptor, only proxying events but keeping the stubs in a single place.\n const runningInstance = this.getInstance()\n\n if (runningInstance) {\n logger.info('found a running instance, reusing...')\n\n // Proxy any listeners you set on this instance to the running instance.\n this.on = (event, listener) => {\n logger.info('proxying the \"%s\" listener', event)\n\n // Add listeners to the running instance so they appear\n // at the top of the event listeners list and are executed first.\n runningInstance.emitter.addListener(event, listener)\n\n // Ensure that once this interceptor instance is disposed,\n // it removes all listeners it has appended to the running interceptor instance.\n this.subscriptions.push(() => {\n runningInstance.emitter.removeListener(event, listener)\n logger.info('removed proxied \"%s\" listener!', event)\n })\n\n return this\n }\n\n this.readyState = InterceptorReadyState.APPLIED\n\n return\n }\n\n logger.info('no running instance found, setting up a new instance...')\n\n // Setup the interceptor.\n this.setup()\n\n // Store the newly applied interceptor instance globally.\n this.setInstance()\n\n this.readyState = InterceptorReadyState.APPLIED\n }\n\n /**\n * Setup the module augments and stubs necessary for this interceptor.\n * This method is not run if there's a running interceptor instance\n * to prevent instantiating an interceptor multiple times.\n */\n protected setup(): void {}\n\n /**\n * Listen to the interceptor's public events.\n */\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n const logger = this.logger.extend('on')\n\n if (\n this.readyState === InterceptorReadyState.DISPOSING ||\n this.readyState === InterceptorReadyState.DISPOSED\n ) {\n logger.info('cannot listen to events, already disposed!')\n return this\n }\n\n logger.info('adding \"%s\" event listener:', event, listener)\n\n this.emitter.on(event, listener)\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n this.emitter.once(event, listener)\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n this.emitter.off(event, listener)\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName\n ): this {\n this.emitter.removeAllListeners(event)\n return this\n }\n\n /**\n * Disposes of any side-effects this interceptor has introduced.\n */\n public dispose(): void {\n const logger = this.logger.extend('dispose')\n\n if (this.readyState === InterceptorReadyState.DISPOSED) {\n logger.info('cannot dispose, already disposed!')\n return\n }\n\n logger.info('disposing the interceptor...')\n this.readyState = InterceptorReadyState.DISPOSING\n\n if (!this.getInstance()) {\n logger.info('no interceptors running, skipping dispose...')\n return\n }\n\n // Delete the global symbol as soon as possible,\n // indicating that the interceptor is no longer running.\n this.clearInstance()\n\n logger.info('global symbol deleted:', getGlobalSymbol(this.symbol))\n\n if (this.subscriptions.length > 0) {\n logger.info('disposing of %d subscriptions...', this.subscriptions.length)\n\n for (const dispose of this.subscriptions) {\n dispose()\n }\n\n this.subscriptions = []\n\n logger.info('disposed of all subscriptions!', this.subscriptions.length)\n }\n\n this.emitter.removeAllListeners()\n logger.info('destroyed the listener!')\n\n this.readyState = InterceptorReadyState.DISPOSED\n }\n\n private getInstance(): this | undefined {\n const instance = getGlobalSymbol<this>(this.symbol)\n this.logger.info('retrieved global instance:', instance?.constructor?.name)\n return instance\n }\n\n private setInstance(): void {\n setGlobalSymbol(this.symbol, this)\n this.logger.info('set global instance!', this.symbol.description)\n }\n\n private clearInstance(): void {\n deleteGlobalSymbol(this.symbol)\n this.logger.info('cleared global instance!', this.symbol.description)\n }\n}\n","export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Generate a random ID string to represent a request.\n * @example\n * createRequestId()\n * // \"f774b6c9c600f\"\n */\nexport function createRequestId(): string {\n return Math.random().toString(16).slice(2)\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nconst kStatus = Symbol('kStatus')\nconst kUrl = Symbol('kUrl')\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setStatus(status: number, response: Response): void {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const internalState = getValueBySymbol<UndiciResponseState>(\n 'state',\n response\n )\n\n if (internalState) {\n internalState.status = status\n } else {\n Object.defineProperty(response, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n Object.defineProperty(response, kStatus, {\n value: status,\n enumerable: false,\n })\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n Object.defineProperty(response, kUrl, {\n value: url,\n enumerable: false,\n })\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n /**\n * Since Node.js v24, Undici stores the Response state in an inaccessible field \"#state\".\n * Forward the modified status/URL to the cloned response manually.\n * @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242\n */\n if (status !== safeStatus) {\n FetchResponse.setStatus(status, this)\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n\n public clone() {\n const clonedResponse = super.clone()\n\n const customStatus = Reflect.get(this, kStatus) as number | undefined\n\n if (customStatus) {\n FetchResponse.setStatus(customStatus, clonedResponse)\n }\n\n const customUrl = Reflect.get(this, kUrl) as string | undefined\n\n if (customUrl) {\n FetchResponse.setUrl(customUrl, clonedResponse)\n }\n\n return clonedResponse\n }\n}\n"],"mappings":"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"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| let _open_draft_logger = require("@open-draft/logger"); | ||
| let strict_event_emitter = require("strict-event-emitter"); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let outvariant = require("outvariant"); | ||
| //#region src/Interceptor.ts | ||
| /** | ||
| * Request header name to detect when a single request | ||
| * is being handled by nested interceptors (XHR -> ClientRequest). | ||
| * Obscure by design to prevent collisions with user-defined headers. | ||
| * Ideally, come up with the Interceptor-level mechanism for this. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| const INTERNAL_REQUEST_ID_HEADER_NAME = "x-interceptors-internal-request-id"; | ||
| function getGlobalSymbol(symbol) { | ||
| return globalThis[symbol] || void 0; | ||
| } | ||
| function setGlobalSymbol(symbol, value) { | ||
| globalThis[symbol] = value; | ||
| } | ||
| function deleteGlobalSymbol(symbol) { | ||
| delete globalThis[symbol]; | ||
| } | ||
| let InterceptorReadyState = /* @__PURE__ */ function(InterceptorReadyState$1) { | ||
| InterceptorReadyState$1["INACTIVE"] = "INACTIVE"; | ||
| InterceptorReadyState$1["APPLYING"] = "APPLYING"; | ||
| InterceptorReadyState$1["APPLIED"] = "APPLIED"; | ||
| InterceptorReadyState$1["DISPOSING"] = "DISPOSING"; | ||
| InterceptorReadyState$1["DISPOSED"] = "DISPOSED"; | ||
| return InterceptorReadyState$1; | ||
| }({}); | ||
| var Interceptor = class { | ||
| constructor(symbol) { | ||
| this.symbol = symbol; | ||
| this.readyState = InterceptorReadyState.INACTIVE; | ||
| this.emitter = new strict_event_emitter.Emitter(); | ||
| this.subscriptions = []; | ||
| this.logger = new _open_draft_logger.Logger(symbol.description); | ||
| this.emitter.setMaxListeners(0); | ||
| this.logger.info("constructing the interceptor..."); | ||
| } | ||
| /** | ||
| * Determine if this interceptor can be applied | ||
| * in the current environment. | ||
| */ | ||
| checkEnvironment() { | ||
| return true; | ||
| } | ||
| /** | ||
| * Apply this interceptor to the current process. | ||
| * Returns an already running interceptor instance if it's present. | ||
| */ | ||
| apply() { | ||
| const logger = this.logger.extend("apply"); | ||
| logger.info("applying the interceptor..."); | ||
| if (this.readyState === InterceptorReadyState.APPLIED) { | ||
| logger.info("intercepted already applied!"); | ||
| return; | ||
| } | ||
| if (!this.checkEnvironment()) { | ||
| logger.info("the interceptor cannot be applied in this environment!"); | ||
| return; | ||
| } | ||
| this.readyState = InterceptorReadyState.APPLYING; | ||
| const runningInstance = this.getInstance(); | ||
| if (runningInstance) { | ||
| logger.info("found a running instance, reusing..."); | ||
| this.on = (event, listener) => { | ||
| logger.info("proxying the \"%s\" listener", event); | ||
| runningInstance.emitter.addListener(event, listener); | ||
| this.subscriptions.push(() => { | ||
| runningInstance.emitter.removeListener(event, listener); | ||
| logger.info("removed proxied \"%s\" listener!", event); | ||
| }); | ||
| return this; | ||
| }; | ||
| this.readyState = InterceptorReadyState.APPLIED; | ||
| return; | ||
| } | ||
| logger.info("no running instance found, setting up a new instance..."); | ||
| this.setup(); | ||
| this.setInstance(); | ||
| this.readyState = InterceptorReadyState.APPLIED; | ||
| } | ||
| /** | ||
| * Setup the module augments and stubs necessary for this interceptor. | ||
| * This method is not run if there's a running interceptor instance | ||
| * to prevent instantiating an interceptor multiple times. | ||
| */ | ||
| setup() {} | ||
| /** | ||
| * Listen to the interceptor's public events. | ||
| */ | ||
| on(event, listener) { | ||
| const logger = this.logger.extend("on"); | ||
| if (this.readyState === InterceptorReadyState.DISPOSING || this.readyState === InterceptorReadyState.DISPOSED) { | ||
| logger.info("cannot listen to events, already disposed!"); | ||
| return this; | ||
| } | ||
| logger.info("adding \"%s\" event listener:", event, listener); | ||
| this.emitter.on(event, listener); | ||
| return this; | ||
| } | ||
| once(event, listener) { | ||
| this.emitter.once(event, listener); | ||
| return this; | ||
| } | ||
| off(event, listener) { | ||
| this.emitter.off(event, listener); | ||
| return this; | ||
| } | ||
| removeAllListeners(event) { | ||
| this.emitter.removeAllListeners(event); | ||
| return this; | ||
| } | ||
| /** | ||
| * Disposes of any side-effects this interceptor has introduced. | ||
| */ | ||
| dispose() { | ||
| const logger = this.logger.extend("dispose"); | ||
| if (this.readyState === InterceptorReadyState.DISPOSED) { | ||
| logger.info("cannot dispose, already disposed!"); | ||
| return; | ||
| } | ||
| logger.info("disposing the interceptor..."); | ||
| this.readyState = InterceptorReadyState.DISPOSING; | ||
| if (!this.getInstance()) { | ||
| logger.info("no interceptors running, skipping dispose..."); | ||
| return; | ||
| } | ||
| this.clearInstance(); | ||
| logger.info("global symbol deleted:", getGlobalSymbol(this.symbol)); | ||
| if (this.subscriptions.length > 0) { | ||
| logger.info("disposing of %d subscriptions...", this.subscriptions.length); | ||
| for (const dispose of this.subscriptions) dispose(); | ||
| this.subscriptions = []; | ||
| logger.info("disposed of all subscriptions!", this.subscriptions.length); | ||
| } | ||
| this.emitter.removeAllListeners(); | ||
| logger.info("destroyed the listener!"); | ||
| this.readyState = InterceptorReadyState.DISPOSED; | ||
| } | ||
| getInstance() { | ||
| const instance = getGlobalSymbol(this.symbol); | ||
| this.logger.info("retrieved global instance:", instance?.constructor?.name); | ||
| return instance; | ||
| } | ||
| setInstance() { | ||
| setGlobalSymbol(this.symbol, this); | ||
| this.logger.info("set global instance!", this.symbol.description); | ||
| } | ||
| clearInstance() { | ||
| deleteGlobalSymbol(this.symbol); | ||
| this.logger.info("cleared global instance!", this.symbol.description); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/InterceptorError.ts | ||
| var InterceptorError = class InterceptorError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "InterceptorError"; | ||
| Object.setPrototypeOf(this, InterceptorError.prototype); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/RequestController.ts | ||
| var RequestController = class RequestController { | ||
| static { | ||
| this.PENDING = 0; | ||
| } | ||
| static { | ||
| this.PASSTHROUGH = 1; | ||
| } | ||
| static { | ||
| this.RESPONSE = 2; | ||
| } | ||
| static { | ||
| this.ERROR = 3; | ||
| } | ||
| constructor(request, source) { | ||
| this.request = request; | ||
| this.source = source; | ||
| this.readyState = RequestController.PENDING; | ||
| this.handled = new _open_draft_deferred_promise.DeferredPromise(); | ||
| } | ||
| get #handled() { | ||
| return this.handled; | ||
| } | ||
| /** | ||
| * Perform this request as-is. | ||
| */ | ||
| async passthrough() { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url); | ||
| this.readyState = RequestController.PASSTHROUGH; | ||
| await this.source.passthrough(); | ||
| this.#handled.resolve(); | ||
| } | ||
| /** | ||
| * Respond to this request with the given `Response` instance. | ||
| * | ||
| * @example | ||
| * controller.respondWith(new Response()) | ||
| * controller.respondWith(Response.json({ id })) | ||
| * controller.respondWith(Response.error()) | ||
| */ | ||
| respondWith(response) { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState); | ||
| this.readyState = RequestController.RESPONSE; | ||
| this.#handled.resolve(); | ||
| /** | ||
| * @note Although `source.respondWith()` is potentially asynchronous, | ||
| * do NOT await it for backward-compatibility. Awaiting it will short-circuit | ||
| * the request listener invocation as soon as a listener responds to a request. | ||
| * Ideally, that's what we want, but that's not what we promise the user. | ||
| */ | ||
| this.source.respondWith(response); | ||
| } | ||
| /** | ||
| * Error this request with the given reason. | ||
| * | ||
| * @example | ||
| * controller.errorWith() | ||
| * controller.errorWith(new Error('Oops!')) | ||
| * controller.errorWith({ message: 'Oops!'}) | ||
| */ | ||
| errorWith(reason) { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState); | ||
| this.readyState = RequestController.ERROR; | ||
| this.source.errorWith(reason); | ||
| this.#handled.resolve(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/createRequestId.ts | ||
| /** | ||
| * Generate a random ID string to represent a request. | ||
| * @example | ||
| * createRequestId() | ||
| * // "f774b6c9c600f" | ||
| */ | ||
| function createRequestId() { | ||
| return Math.random().toString(16).slice(2); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/canParseUrl.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given URL string | ||
| * can be parsed into a `URL` instance. | ||
| * A substitute for `URL.canParse()` for Node.js 18. | ||
| */ | ||
| function canParseUrl(url) { | ||
| try { | ||
| new URL(url); | ||
| return true; | ||
| } catch (_error) { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/getValueBySymbol.ts | ||
| /** | ||
| * Returns the value behind the symbol with the given name. | ||
| */ | ||
| function getValueBySymbol(symbolName, source) { | ||
| const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => { | ||
| return symbol$1.description === symbolName; | ||
| }); | ||
| if (symbol) return Reflect.get(source, symbol); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/fetchUtils.ts | ||
| var FetchRequest = class FetchRequest extends Request { | ||
| static #resolveProperty(input, init = {}, key) { | ||
| return init[key] ?? (input instanceof Request ? input[key] : void 0); | ||
| } | ||
| /** | ||
| * Check if the given request method is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#methods | ||
| */ | ||
| static isConfigurableMethod(method) { | ||
| return method !== "CONNECT" && method !== "TRACE" && method !== "TRACK"; | ||
| } | ||
| static isMethodWithBody(method) { | ||
| return method !== "HEAD" && method !== "GET" && FetchRequest.isConfigurableMethod(method); | ||
| } | ||
| /** | ||
| * Check if the given request `mode` is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#concept-request-mode | ||
| */ | ||
| static isConfigurableMode(mode) { | ||
| return mode !== "navigate" && mode !== "websocket" && mode !== "webtransport"; | ||
| } | ||
| constructor(input, init) { | ||
| const method = FetchRequest.#resolveProperty(input, init, "method") || "GET"; | ||
| const safeMethod = FetchRequest.isConfigurableMethod(method) ? method : "GET"; | ||
| const hasExplicitBody = init != null && "body" in init; | ||
| /** | ||
| * Only include `body` in the super init when it needs to be overridden. | ||
| * When `input` is a Request and no explicit body is in `init`, let the | ||
| * Request constructor handle body transfer naturally so it properly | ||
| * marks the original request's body as consumed (bodyUsed = true). | ||
| */ | ||
| const bodyInit = !FetchRequest.isMethodWithBody(method) ? { body: void 0 } : hasExplicitBody ? { body: init.body } : {}; | ||
| const mode = FetchRequest.#resolveProperty(input, init, "mode") ?? void 0; | ||
| const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : void 0; | ||
| super(input, { | ||
| ...init || {}, | ||
| method: safeMethod, | ||
| mode: safeMode, | ||
| duplex: init?.duplex ?? (FetchRequest.isMethodWithBody(method) ? "half" : void 0), | ||
| ...bodyInit | ||
| }); | ||
| if (method !== safeMethod) this.#setInternalProperty("method", method); | ||
| if (method === "CONNECT") { | ||
| const url = new URL(input instanceof Request ? input.url : input); | ||
| let authority; | ||
| /** | ||
| * @note Node.js has a bug parsing raw CONNECT requests URLs like | ||
| * "http://127.0.0.1:1337/localhost:80". It would treat "localhost:" as a protocol. | ||
| */ | ||
| if (url.protocol === "localhost:") authority = url.href; | ||
| else authority = url.pathname.replace(/^\/+/, ""); | ||
| /** | ||
| * @note Define "url" as a getter because Undici uses their own | ||
| * logic to resolve the "request.url" property. Simply reassigning | ||
| * its value doesn't do anything. This is a destructive action | ||
| * but it's safe because "CONNECT" requests are forbidden per fetch. | ||
| */ | ||
| Object.defineProperty(this, "url", { | ||
| get: () => authority, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| if (mode != null && mode !== safeMode) this.#setInternalProperty("mode", mode); | ||
| } | ||
| #setInternalProperty(key, value) { | ||
| const internalState = getValueBySymbol("state", this); | ||
| if (internalState) Reflect.set(internalState, key, value); | ||
| else Object.defineProperty(this, key, { | ||
| value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| }; | ||
| const kStatus = Symbol("kStatus"); | ||
| const kUrl = Symbol("kUrl"); | ||
| var FetchResponse = class FetchResponse extends Response { | ||
| static { | ||
| this.STATUS_CODES_WITHOUT_BODY = [ | ||
| 101, | ||
| 103, | ||
| 204, | ||
| 205, | ||
| 304 | ||
| ]; | ||
| } | ||
| static { | ||
| this.STATUS_CODES_WITH_REDIRECT = [ | ||
| 301, | ||
| 302, | ||
| 303, | ||
| 307, | ||
| 308 | ||
| ]; | ||
| } | ||
| static isConfigurableStatusCode(status) { | ||
| return status >= 200 && status <= 599; | ||
| } | ||
| static isRedirectResponse(status) { | ||
| return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status); | ||
| } | ||
| /** | ||
| * Returns a boolean indicating whether the given response status | ||
| * code represents a response that can have a body. | ||
| */ | ||
| static isResponseWithBody(status) { | ||
| return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status); | ||
| } | ||
| static setStatus(status, response) { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const internalState = getValueBySymbol("state", response); | ||
| if (internalState) internalState.status = status; | ||
| else Object.defineProperty(response, "status", { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| Object.defineProperty(response, kStatus, { | ||
| value: status, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| static setUrl(url, response) { | ||
| if (!url || url === "about:" || !canParseUrl(url)) return; | ||
| const state = getValueBySymbol("state", response); | ||
| if (state) state.urlList.push(new URL(url)); | ||
| else Object.defineProperty(response, "url", { | ||
| value: url, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| Object.defineProperty(response, kUrl, { | ||
| value: url, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| /** | ||
| * Parses the given raw HTTP headers into a Fetch API `Headers` instance. | ||
| */ | ||
| static parseRawHeaders(rawHeaders) { | ||
| const headers = new Headers(); | ||
| for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]); | ||
| return headers; | ||
| } | ||
| /** | ||
| * Safely clones the given `Response`. | ||
| * Coerces response clone exceptions into 500 mocked responses. | ||
| * Handy in the environments that introduce arbitrary response | ||
| * cloning restrictions, like "101 Switching Protocols" cloning | ||
| * in "miniflare". | ||
| */ | ||
| static clone(response) { | ||
| try { | ||
| return response.clone(); | ||
| } catch (error) { | ||
| return Response.json(error instanceof Error ? { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| } : {}, { | ||
| status: 500, | ||
| statusText: "Unclonable Response" | ||
| }); | ||
| } | ||
| } | ||
| constructor(body, init = {}) { | ||
| const status = init.status ?? 200; | ||
| const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200; | ||
| const finalBody = FetchResponse.isResponseWithBody(status) ? body : null; | ||
| super(finalBody, { | ||
| status: safeStatus, | ||
| statusText: init.statusText, | ||
| headers: init.headers | ||
| }); | ||
| /** | ||
| * Since Node.js v24, Undici stores the Response state in an inaccessible field "#state". | ||
| * Forward the modified status/URL to the cloned response manually. | ||
| * @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242 | ||
| */ | ||
| if (status !== safeStatus) FetchResponse.setStatus(status, this); | ||
| FetchResponse.setUrl(init.url, this); | ||
| } | ||
| clone() { | ||
| const clonedResponse = super.clone(); | ||
| const customStatus = Reflect.get(this, kStatus); | ||
| if (customStatus) FetchResponse.setStatus(customStatus, clonedResponse); | ||
| const customUrl = Reflect.get(this, kUrl); | ||
| if (customUrl) FetchResponse.setUrl(customUrl, clonedResponse); | ||
| return clonedResponse; | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'FetchRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'FetchResponse', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchResponse; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'INTERNAL_REQUEST_ID_HEADER_NAME', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return INTERNAL_REQUEST_ID_HEADER_NAME; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'Interceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return Interceptor; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'InterceptorError', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return InterceptorError; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'InterceptorReadyState', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return InterceptorReadyState; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'RequestController', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return RequestController; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'canParseUrl', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return canParseUrl; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createRequestId', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createRequestId; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'deleteGlobalSymbol', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return deleteGlobalSymbol; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'getGlobalSymbol', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return getGlobalSymbol; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=fetchUtils-umV5xXBy.cjs.map |
| {"version":3,"file":"fetchUtils-umV5xXBy.cjs","names":["symbol: symbol","Emitter","Logger","request: Request","source: RequestControllerSource","DeferredPromise","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/Interceptor.ts","../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/createRequestId.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts"],"sourcesContent":["import { Logger } from '@open-draft/logger'\nimport { Emitter, Listener } from 'strict-event-emitter'\n\nexport type InterceptorEventMap = Record<string, any>\nexport type InterceptorSubscription = () => void\n\n/**\n * Request header name to detect when a single request\n * is being handled by nested interceptors (XHR -> ClientRequest).\n * Obscure by design to prevent collisions with user-defined headers.\n * Ideally, come up with the Interceptor-level mechanism for this.\n * @see https://github.com/mswjs/interceptors/issues/378\n */\nexport const INTERNAL_REQUEST_ID_HEADER_NAME =\n 'x-interceptors-internal-request-id'\n\nexport function getGlobalSymbol<V>(symbol: Symbol): V | undefined {\n return (\n // @ts-ignore https://github.com/Microsoft/TypeScript/issues/24587\n globalThis[symbol] || undefined\n )\n}\n\nfunction setGlobalSymbol(symbol: Symbol, value: any): void {\n // @ts-ignore\n globalThis[symbol] = value\n}\n\nexport function deleteGlobalSymbol(symbol: Symbol): void {\n // @ts-ignore\n delete globalThis[symbol]\n}\n\nexport enum InterceptorReadyState {\n INACTIVE = 'INACTIVE',\n APPLYING = 'APPLYING',\n APPLIED = 'APPLIED',\n DISPOSING = 'DISPOSING',\n DISPOSED = 'DISPOSED',\n}\n\nexport type ExtractEventNames<Events extends Record<string, any>> =\n Events extends Record<infer EventName, any> ? EventName : never\n\nexport class Interceptor<Events extends InterceptorEventMap> {\n protected emitter: Emitter<Events>\n protected subscriptions: Array<InterceptorSubscription>\n protected logger: Logger\n\n public readyState: InterceptorReadyState\n\n constructor(private readonly symbol: symbol) {\n this.readyState = InterceptorReadyState.INACTIVE\n\n this.emitter = new Emitter()\n this.subscriptions = []\n this.logger = new Logger(symbol.description!)\n\n // Do not limit the maximum number of listeners\n // so not to limit the maximum amount of parallel events emitted.\n this.emitter.setMaxListeners(0)\n\n this.logger.info('constructing the interceptor...')\n }\n\n /**\n * Determine if this interceptor can be applied\n * in the current environment.\n */\n protected checkEnvironment(): boolean {\n return true\n }\n\n /**\n * Apply this interceptor to the current process.\n * Returns an already running interceptor instance if it's present.\n */\n public apply(): void {\n const logger = this.logger.extend('apply')\n logger.info('applying the interceptor...')\n\n if (this.readyState === InterceptorReadyState.APPLIED) {\n logger.info('intercepted already applied!')\n return\n }\n\n const shouldApply = this.checkEnvironment()\n\n if (!shouldApply) {\n logger.info('the interceptor cannot be applied in this environment!')\n return\n }\n\n this.readyState = InterceptorReadyState.APPLYING\n\n // Whenever applying a new interceptor, check if it hasn't been applied already.\n // This enables to apply the same interceptor multiple times, for example from a different\n // interceptor, only proxying events but keeping the stubs in a single place.\n const runningInstance = this.getInstance()\n\n if (runningInstance) {\n logger.info('found a running instance, reusing...')\n\n // Proxy any listeners you set on this instance to the running instance.\n this.on = (event, listener) => {\n logger.info('proxying the \"%s\" listener', event)\n\n // Add listeners to the running instance so they appear\n // at the top of the event listeners list and are executed first.\n runningInstance.emitter.addListener(event, listener)\n\n // Ensure that once this interceptor instance is disposed,\n // it removes all listeners it has appended to the running interceptor instance.\n this.subscriptions.push(() => {\n runningInstance.emitter.removeListener(event, listener)\n logger.info('removed proxied \"%s\" listener!', event)\n })\n\n return this\n }\n\n this.readyState = InterceptorReadyState.APPLIED\n\n return\n }\n\n logger.info('no running instance found, setting up a new instance...')\n\n // Setup the interceptor.\n this.setup()\n\n // Store the newly applied interceptor instance globally.\n this.setInstance()\n\n this.readyState = InterceptorReadyState.APPLIED\n }\n\n /**\n * Setup the module augments and stubs necessary for this interceptor.\n * This method is not run if there's a running interceptor instance\n * to prevent instantiating an interceptor multiple times.\n */\n protected setup(): void {}\n\n /**\n * Listen to the interceptor's public events.\n */\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n const logger = this.logger.extend('on')\n\n if (\n this.readyState === InterceptorReadyState.DISPOSING ||\n this.readyState === InterceptorReadyState.DISPOSED\n ) {\n logger.info('cannot listen to events, already disposed!')\n return this\n }\n\n logger.info('adding \"%s\" event listener:', event, listener)\n\n this.emitter.on(event, listener)\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n this.emitter.once(event, listener)\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n this.emitter.off(event, listener)\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName\n ): this {\n this.emitter.removeAllListeners(event)\n return this\n }\n\n /**\n * Disposes of any side-effects this interceptor has introduced.\n */\n public dispose(): void {\n const logger = this.logger.extend('dispose')\n\n if (this.readyState === InterceptorReadyState.DISPOSED) {\n logger.info('cannot dispose, already disposed!')\n return\n }\n\n logger.info('disposing the interceptor...')\n this.readyState = InterceptorReadyState.DISPOSING\n\n if (!this.getInstance()) {\n logger.info('no interceptors running, skipping dispose...')\n return\n }\n\n // Delete the global symbol as soon as possible,\n // indicating that the interceptor is no longer running.\n this.clearInstance()\n\n logger.info('global symbol deleted:', getGlobalSymbol(this.symbol))\n\n if (this.subscriptions.length > 0) {\n logger.info('disposing of %d subscriptions...', this.subscriptions.length)\n\n for (const dispose of this.subscriptions) {\n dispose()\n }\n\n this.subscriptions = []\n\n logger.info('disposed of all subscriptions!', this.subscriptions.length)\n }\n\n this.emitter.removeAllListeners()\n logger.info('destroyed the listener!')\n\n this.readyState = InterceptorReadyState.DISPOSED\n }\n\n private getInstance(): this | undefined {\n const instance = getGlobalSymbol<this>(this.symbol)\n this.logger.info('retrieved global instance:', instance?.constructor?.name)\n return instance\n }\n\n private setInstance(): void {\n setGlobalSymbol(this.symbol, this)\n this.logger.info('set global instance!', this.symbol.description)\n }\n\n private clearInstance(): void {\n deleteGlobalSymbol(this.symbol)\n this.logger.info('cleared global instance!', this.symbol.description)\n }\n}\n","export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Generate a random ID string to represent a request.\n * @example\n * createRequestId()\n * // \"f774b6c9c600f\"\n */\nexport function createRequestId(): string {\n return Math.random().toString(16).slice(2)\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nconst kStatus = Symbol('kStatus')\nconst kUrl = Symbol('kUrl')\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setStatus(status: number, response: Response): void {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const internalState = getValueBySymbol<UndiciResponseState>(\n 'state',\n response\n )\n\n if (internalState) {\n internalState.status = status\n } else {\n Object.defineProperty(response, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n Object.defineProperty(response, kStatus, {\n value: status,\n enumerable: false,\n })\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n\n Object.defineProperty(response, kUrl, {\n value: url,\n enumerable: false,\n })\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n /**\n * Since Node.js v24, Undici stores the Response state in an inaccessible field \"#state\".\n * Forward the modified status/URL to the cloned response manually.\n * @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242\n */\n if (status !== safeStatus) {\n FetchResponse.setStatus(status, this)\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n\n public clone() {\n const clonedResponse = super.clone()\n\n const customStatus = Reflect.get(this, kStatus) as number | undefined\n\n if (customStatus) {\n FetchResponse.setStatus(customStatus, clonedResponse)\n }\n\n const customUrl = Reflect.get(this, kUrl) as string | undefined\n\n if (customUrl) {\n FetchResponse.setUrl(customUrl, clonedResponse)\n }\n\n return clonedResponse\n }\n}\n"],"mappings":"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"} |
| import { a as RequestController, o as InterceptorError } from "./fetchUtils-BKJ1XmiO.mjs"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { until } from "@open-draft/until"; | ||
| //#region src/utils/isPropertyAccessible.ts | ||
| /** | ||
| * A function that validates if property access is possible on an object | ||
| * without throwing. It returns `true` if the property access is possible | ||
| * and `false` otherwise. | ||
| * | ||
| * Environments like miniflare will throw on property access on certain objects | ||
| * like Request and Response, for unimplemented properties. | ||
| */ | ||
| function isPropertyAccessible(obj, key) { | ||
| try { | ||
| obj[key]; | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/emitAsync.ts | ||
| /** | ||
| * Emits an event on the given emitter but executes | ||
| * the listeners sequentially. This accounts for asynchronous | ||
| * listeners (e.g. those having "sleep" and handling the request). | ||
| */ | ||
| async function emitAsync(emitter, eventName, ...data) { | ||
| const listeners = emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| for (const listener of listeners) await listener.apply(emitter, data); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isObject.ts | ||
| /** | ||
| * Determines if a given value is an instance of object. | ||
| */ | ||
| function isObject(value, loose = false) { | ||
| return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]"; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/responseUtils.ts | ||
| /** | ||
| * Creates a generic 500 Unhandled Exception response. | ||
| */ | ||
| function createServerErrorResponse(body) { | ||
| return new Response(JSON.stringify(body instanceof Error ? { | ||
| name: body.name, | ||
| message: body.message, | ||
| stack: body.stack | ||
| } : body), { | ||
| status: 500, | ||
| statusText: "Unhandled Exception", | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| /** | ||
| * Check if the given response is a `Response.error()`. | ||
| * | ||
| * @note Some environments, like Miniflare (Cloudflare) do not | ||
| * implement the "Response.type" property and throw on its access. | ||
| * Safely check if we can access "type" on "Response" before continuing. | ||
| * @see https://github.com/mswjs/msw/issues/1834 | ||
| */ | ||
| function isResponseError(response) { | ||
| return response != null && response instanceof Response && isPropertyAccessible(response, "type") && response.type === "error"; | ||
| } | ||
| /** | ||
| * Check if the given value is a `Response` or a Response-like object. | ||
| * This is different from `value instanceof Response` because it supports | ||
| * custom `Response` constructors, like the one when using Undici directly. | ||
| */ | ||
| function isResponseLike(value) { | ||
| return isObject(value, true) && isPropertyAccessible(value, "status") && isPropertyAccessible(value, "statusText") && isPropertyAccessible(value, "bodyUsed"); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isNodeLikeError.ts | ||
| function isNodeLikeError(error) { | ||
| if (error == null) return false; | ||
| if (!(error instanceof Error)) return false; | ||
| return "code" in error && "errno" in error; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/handleRequest.ts | ||
| async function handleRequest(options) { | ||
| const handleResponse = async (response) => { | ||
| if (response instanceof Error) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| if (isResponseError(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| /** | ||
| * Handle normal responses or response-like objects. | ||
| * @note This must come before the arbitrary object check | ||
| * since Response instances are, in fact, objects. | ||
| */ | ||
| if (isResponseLike(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| if (isObject(response)) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| const handleResponseError = async (error) => { | ||
| if (error instanceof InterceptorError) throw result.error; | ||
| if (isNodeLikeError(error)) { | ||
| await options.controller.errorWith(error); | ||
| return true; | ||
| } | ||
| if (error instanceof Response) return await handleResponse(error); | ||
| return false; | ||
| }; | ||
| const requestAbortPromise = new DeferredPromise(); | ||
| /** | ||
| * @note `signal` is not always defined in React Native. | ||
| */ | ||
| if (options.request.signal) { | ||
| if (options.request.signal.aborted) { | ||
| await options.controller.errorWith(options.request.signal.reason); | ||
| return; | ||
| } | ||
| options.request.signal.addEventListener("abort", () => { | ||
| requestAbortPromise.reject(options.request.signal.reason); | ||
| }, { once: true }); | ||
| } | ||
| const result = await until(async () => { | ||
| const requestListenersPromise = emitAsync(options.emitter, "request", { | ||
| requestId: options.requestId, | ||
| request: options.request, | ||
| controller: options.controller | ||
| }); | ||
| await Promise.race([ | ||
| requestAbortPromise, | ||
| requestListenersPromise, | ||
| options.controller.handled | ||
| ]); | ||
| }); | ||
| if (requestAbortPromise.state === "rejected") { | ||
| await options.controller.errorWith(requestAbortPromise.rejectionReason); | ||
| return; | ||
| } | ||
| if (result.error) { | ||
| if (await handleResponseError(result.error)) return; | ||
| if (options.emitter.listenerCount("unhandledException") > 0) { | ||
| const unhandledExceptionController = new RequestController(options.request, { | ||
| passthrough() {}, | ||
| async respondWith(response) { | ||
| await handleResponse(response); | ||
| }, | ||
| async errorWith(reason) { | ||
| /** | ||
| * @note Handle the result of the unhandled controller | ||
| * in the same way as the original request controller. | ||
| * The exception here is that thrown errors within the | ||
| * "unhandledException" event do NOT result in another | ||
| * emit of the same event. They are forwarded as-is. | ||
| */ | ||
| await options.controller.errorWith(reason); | ||
| } | ||
| }); | ||
| await emitAsync(options.emitter, "unhandledException", { | ||
| error: result.error, | ||
| request: options.request, | ||
| requestId: options.requestId, | ||
| controller: unhandledExceptionController | ||
| }); | ||
| if (unhandledExceptionController.readyState !== RequestController.PENDING) return; | ||
| } | ||
| await options.controller.respondWith(createServerErrorResponse(result.error)); | ||
| return; | ||
| } | ||
| if (options.controller.readyState === RequestController.PENDING) return await options.controller.passthrough(); | ||
| return options.controller.handled; | ||
| } | ||
| //#endregion | ||
| export { isPropertyAccessible as a, emitAsync as i, isResponseError as n, isObject as r, handleRequest as t }; | ||
| //# sourceMappingURL=handleRequest-DCLzePtS.mjs.map |
| {"version":3,"file":"handleRequest-DCLzePtS.mjs","names":[],"sources":["../../src/utils/isPropertyAccessible.ts","../../src/utils/emitAsync.ts","../../src/utils/isObject.ts","../../src/utils/responseUtils.ts","../../src/utils/isNodeLikeError.ts","../../src/utils/handleRequest.ts"],"sourcesContent":["/**\n * A function that validates if property access is possible on an object\n * without throwing. It returns `true` if the property access is possible\n * and `false` otherwise.\n *\n * Environments like miniflare will throw on property access on certain objects\n * like Request and Response, for unimplemented properties.\n */\nexport function isPropertyAccessible<Obj extends Record<string, any>>(\n obj: Obj,\n key: keyof Obj\n) {\n try {\n obj[key]\n return true\n } catch {\n return false\n }\n}\n","import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","/**\n * Determines if a given value is an instance of object.\n */\nexport function isObject<T>(value: any, loose = false): value is T {\n return loose\n ? Object.prototype.toString.call(value).startsWith('[object ')\n : Object.prototype.toString.call(value) === '[object Object]'\n}\n","import { isObject } from './isObject'\nimport { isPropertyAccessible } from './isPropertyAccessible'\n\n/**\n * Creates a generic 500 Unhandled Exception response.\n */\nexport function createServerErrorResponse(body: unknown): Response {\n return new Response(\n JSON.stringify(\n body instanceof Error\n ? {\n name: body.name,\n message: body.message,\n stack: body.stack,\n }\n : body\n ),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n}\n\nexport type ResponseError = Response & { type: 'error' }\n\n/**\n * Check if the given response is a `Response.error()`.\n *\n * @note Some environments, like Miniflare (Cloudflare) do not\n * implement the \"Response.type\" property and throw on its access.\n * Safely check if we can access \"type\" on \"Response\" before continuing.\n * @see https://github.com/mswjs/msw/issues/1834\n */\nexport function isResponseError(response: unknown): response is ResponseError {\n return (\n response != null &&\n response instanceof Response &&\n isPropertyAccessible(response, 'type') &&\n response.type === 'error'\n )\n}\n\n/**\n * Check if the given value is a `Response` or a Response-like object.\n * This is different from `value instanceof Response` because it supports\n * custom `Response` constructors, like the one when using Undici directly.\n */\nexport function isResponseLike(value: unknown): value is Response {\n return (\n isObject<Record<string, any>>(value, true) &&\n isPropertyAccessible(value, 'status') &&\n isPropertyAccessible(value, 'statusText') &&\n isPropertyAccessible(value, 'bodyUsed')\n )\n}\n","export function isNodeLikeError(\n error: unknown\n): error is NodeJS.ErrnoException {\n if (error == null) {\n return false\n }\n\n if (!(error instanceof Error)) {\n return false\n }\n\n return 'code' in error && 'errno' in error\n}\n","import type { Emitter } from 'strict-event-emitter'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport type { HttpRequestEventMap } from '../glossary'\nimport { emitAsync } from './emitAsync'\nimport { RequestController } from '../RequestController'\nimport {\n createServerErrorResponse,\n isResponseError,\n isResponseLike,\n} from './responseUtils'\nimport { InterceptorError } from '../InterceptorError'\nimport { isNodeLikeError } from './isNodeLikeError'\nimport { isObject } from './isObject'\n\ninterface HandleRequestOptions {\n requestId: string\n request: Request\n emitter: Emitter<HttpRequestEventMap>\n controller: RequestController\n}\n\nexport async function handleRequest(\n options: HandleRequestOptions\n): Promise<void> {\n const handleResponse = async (\n response: Response | Error | Record<string, any>\n ) => {\n if (response instanceof Error) {\n await options.controller.errorWith(response)\n return true\n }\n\n // Handle \"Response.error()\" instances.\n if (isResponseError(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n /**\n * Handle normal responses or response-like objects.\n * @note This must come before the arbitrary object check\n * since Response instances are, in fact, objects.\n */\n if (isResponseLike(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n // Handle arbitrary objects provided to `.errorWith(reason)`.\n if (isObject(response)) {\n await options.controller.errorWith(response)\n return true\n }\n\n return false\n }\n\n const handleResponseError = async (error: unknown): Promise<boolean> => {\n // Forward the special interceptor error instances\n // to the developer. These must not be handled in any way.\n if (error instanceof InterceptorError) {\n throw result.error\n }\n\n // Support mocking Node.js-like errors.\n if (isNodeLikeError(error)) {\n await options.controller.errorWith(error)\n return true\n }\n\n // Handle thrown responses.\n if (error instanceof Response) {\n return await handleResponse(error)\n }\n\n return false\n }\n\n // Add the last \"request\" listener to check if the request\n // has been handled in any way. If it hasn't, resolve the\n // response promise with undefined.\n // options.emitter.once('request', async ({ requestId: pendingRequestId }) => {\n // if (\n // pendingRequestId === options.requestId &&\n // options.controller.readyState === RequestController.PENDING\n // ) {\n // await options.controller.passthrough()\n // }\n // })\n\n const requestAbortPromise = new DeferredPromise<void, unknown>()\n\n /**\n * @note `signal` is not always defined in React Native.\n */\n if (options.request.signal) {\n if (options.request.signal.aborted) {\n await options.controller.errorWith(options.request.signal.reason)\n return\n }\n\n options.request.signal.addEventListener(\n 'abort',\n () => {\n requestAbortPromise.reject(options.request.signal.reason)\n },\n { once: true }\n )\n }\n\n const result = await until(async () => {\n // Emit the \"request\" event and wait until all the listeners\n // for that event are finished (e.g. async listeners awaited).\n // By the end of this promise, the developer cannot affect the\n // request anymore.\n const requestListenersPromise = emitAsync(options.emitter, 'request', {\n requestId: options.requestId,\n request: options.request,\n controller: options.controller,\n })\n\n await Promise.race([\n // Short-circuit the request handling promise if the request gets aborted.\n requestAbortPromise,\n requestListenersPromise,\n options.controller.handled,\n ])\n })\n\n // Handle the request being aborted while waiting for the request listeners.\n if (requestAbortPromise.state === 'rejected') {\n await options.controller.errorWith(requestAbortPromise.rejectionReason)\n return\n }\n\n if (result.error) {\n // Handle the error during the request listener execution.\n // These can be thrown responses or request errors.\n if (await handleResponseError(result.error)) {\n return\n }\n\n // If the developer has added \"unhandledException\" listeners,\n // allow them to handle the error. They can translate it to a\n // mocked response, network error, or forward it as-is.\n if (options.emitter.listenerCount('unhandledException') > 0) {\n // Create a new request controller just for the unhandled exception case.\n // This is needed because the original controller might have been already\n // interacted with (e.g. \"respondWith\" or \"errorWith\" called on it).\n const unhandledExceptionController = new RequestController(\n options.request,\n {\n /**\n * @note Intentionally empty passthrough handle.\n * This controller is created within another controller and we only need\n * to know if `unhandledException` listeners handled the request.\n */\n passthrough() {},\n async respondWith(response) {\n await handleResponse(response)\n },\n async errorWith(reason) {\n /**\n * @note Handle the result of the unhandled controller\n * in the same way as the original request controller.\n * The exception here is that thrown errors within the\n * \"unhandledException\" event do NOT result in another\n * emit of the same event. They are forwarded as-is.\n */\n await options.controller.errorWith(reason)\n },\n }\n )\n\n await emitAsync(options.emitter, 'unhandledException', {\n error: result.error,\n request: options.request,\n requestId: options.requestId,\n controller: unhandledExceptionController,\n })\n\n // If all the \"unhandledException\" listeners have finished\n // but have not handled the request in any way, passthrough.\n if (\n unhandledExceptionController.readyState !== RequestController.PENDING\n ) {\n return\n }\n }\n\n // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.\n await options.controller.respondWith(\n createServerErrorResponse(result.error)\n )\n return\n }\n\n // If the request hasn't been handled by this point, passthrough.\n if (options.controller.readyState === RequestController.PENDING) {\n return await options.controller.passthrough()\n }\n\n return options.controller.handled\n}\n"],"mappings":"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"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-umV5xXBy.cjs'); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let _open_draft_until = require("@open-draft/until"); | ||
| //#region src/utils/isPropertyAccessible.ts | ||
| /** | ||
| * A function that validates if property access is possible on an object | ||
| * without throwing. It returns `true` if the property access is possible | ||
| * and `false` otherwise. | ||
| * | ||
| * Environments like miniflare will throw on property access on certain objects | ||
| * like Request and Response, for unimplemented properties. | ||
| */ | ||
| function isPropertyAccessible(obj, key) { | ||
| try { | ||
| obj[key]; | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/emitAsync.ts | ||
| /** | ||
| * Emits an event on the given emitter but executes | ||
| * the listeners sequentially. This accounts for asynchronous | ||
| * listeners (e.g. those having "sleep" and handling the request). | ||
| */ | ||
| async function emitAsync(emitter, eventName, ...data) { | ||
| const listeners = emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| for (const listener of listeners) await listener.apply(emitter, data); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isObject.ts | ||
| /** | ||
| * Determines if a given value is an instance of object. | ||
| */ | ||
| function isObject(value, loose = false) { | ||
| return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]"; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/responseUtils.ts | ||
| /** | ||
| * Creates a generic 500 Unhandled Exception response. | ||
| */ | ||
| function createServerErrorResponse(body) { | ||
| return new Response(JSON.stringify(body instanceof Error ? { | ||
| name: body.name, | ||
| message: body.message, | ||
| stack: body.stack | ||
| } : body), { | ||
| status: 500, | ||
| statusText: "Unhandled Exception", | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| /** | ||
| * Check if the given response is a `Response.error()`. | ||
| * | ||
| * @note Some environments, like Miniflare (Cloudflare) do not | ||
| * implement the "Response.type" property and throw on its access. | ||
| * Safely check if we can access "type" on "Response" before continuing. | ||
| * @see https://github.com/mswjs/msw/issues/1834 | ||
| */ | ||
| function isResponseError(response) { | ||
| return response != null && response instanceof Response && isPropertyAccessible(response, "type") && response.type === "error"; | ||
| } | ||
| /** | ||
| * Check if the given value is a `Response` or a Response-like object. | ||
| * This is different from `value instanceof Response` because it supports | ||
| * custom `Response` constructors, like the one when using Undici directly. | ||
| */ | ||
| function isResponseLike(value) { | ||
| return isObject(value, true) && isPropertyAccessible(value, "status") && isPropertyAccessible(value, "statusText") && isPropertyAccessible(value, "bodyUsed"); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isNodeLikeError.ts | ||
| function isNodeLikeError(error) { | ||
| if (error == null) return false; | ||
| if (!(error instanceof Error)) return false; | ||
| return "code" in error && "errno" in error; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/handleRequest.ts | ||
| async function handleRequest(options) { | ||
| const handleResponse = async (response) => { | ||
| if (response instanceof Error) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| if (isResponseError(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| /** | ||
| * Handle normal responses or response-like objects. | ||
| * @note This must come before the arbitrary object check | ||
| * since Response instances are, in fact, objects. | ||
| */ | ||
| if (isResponseLike(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| if (isObject(response)) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| const handleResponseError = async (error) => { | ||
| if (error instanceof require_fetchUtils.InterceptorError) throw result.error; | ||
| if (isNodeLikeError(error)) { | ||
| await options.controller.errorWith(error); | ||
| return true; | ||
| } | ||
| if (error instanceof Response) return await handleResponse(error); | ||
| return false; | ||
| }; | ||
| const requestAbortPromise = new _open_draft_deferred_promise.DeferredPromise(); | ||
| /** | ||
| * @note `signal` is not always defined in React Native. | ||
| */ | ||
| if (options.request.signal) { | ||
| if (options.request.signal.aborted) { | ||
| await options.controller.errorWith(options.request.signal.reason); | ||
| return; | ||
| } | ||
| options.request.signal.addEventListener("abort", () => { | ||
| requestAbortPromise.reject(options.request.signal.reason); | ||
| }, { once: true }); | ||
| } | ||
| const result = await (0, _open_draft_until.until)(async () => { | ||
| const requestListenersPromise = emitAsync(options.emitter, "request", { | ||
| requestId: options.requestId, | ||
| request: options.request, | ||
| controller: options.controller | ||
| }); | ||
| await Promise.race([ | ||
| requestAbortPromise, | ||
| requestListenersPromise, | ||
| options.controller.handled | ||
| ]); | ||
| }); | ||
| if (requestAbortPromise.state === "rejected") { | ||
| await options.controller.errorWith(requestAbortPromise.rejectionReason); | ||
| return; | ||
| } | ||
| if (result.error) { | ||
| if (await handleResponseError(result.error)) return; | ||
| if (options.emitter.listenerCount("unhandledException") > 0) { | ||
| const unhandledExceptionController = new require_fetchUtils.RequestController(options.request, { | ||
| passthrough() {}, | ||
| async respondWith(response) { | ||
| await handleResponse(response); | ||
| }, | ||
| async errorWith(reason) { | ||
| /** | ||
| * @note Handle the result of the unhandled controller | ||
| * in the same way as the original request controller. | ||
| * The exception here is that thrown errors within the | ||
| * "unhandledException" event do NOT result in another | ||
| * emit of the same event. They are forwarded as-is. | ||
| */ | ||
| await options.controller.errorWith(reason); | ||
| } | ||
| }); | ||
| await emitAsync(options.emitter, "unhandledException", { | ||
| error: result.error, | ||
| request: options.request, | ||
| requestId: options.requestId, | ||
| controller: unhandledExceptionController | ||
| }); | ||
| if (unhandledExceptionController.readyState !== require_fetchUtils.RequestController.PENDING) return; | ||
| } | ||
| await options.controller.respondWith(createServerErrorResponse(result.error)); | ||
| return; | ||
| } | ||
| if (options.controller.readyState === require_fetchUtils.RequestController.PENDING) return await options.controller.passthrough(); | ||
| return options.controller.handled; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'emitAsync', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return emitAsync; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'handleRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return handleRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isObject', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isObject; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isPropertyAccessible', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isPropertyAccessible; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isResponseError', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isResponseError; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=handleRequest-DVOthWJo.cjs.map |
| {"version":3,"file":"handleRequest-DVOthWJo.cjs","names":["InterceptorError","DeferredPromise","RequestController"],"sources":["../../src/utils/isPropertyAccessible.ts","../../src/utils/emitAsync.ts","../../src/utils/isObject.ts","../../src/utils/responseUtils.ts","../../src/utils/isNodeLikeError.ts","../../src/utils/handleRequest.ts"],"sourcesContent":["/**\n * A function that validates if property access is possible on an object\n * without throwing. It returns `true` if the property access is possible\n * and `false` otherwise.\n *\n * Environments like miniflare will throw on property access on certain objects\n * like Request and Response, for unimplemented properties.\n */\nexport function isPropertyAccessible<Obj extends Record<string, any>>(\n obj: Obj,\n key: keyof Obj\n) {\n try {\n obj[key]\n return true\n } catch {\n return false\n }\n}\n","import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","/**\n * Determines if a given value is an instance of object.\n */\nexport function isObject<T>(value: any, loose = false): value is T {\n return loose\n ? Object.prototype.toString.call(value).startsWith('[object ')\n : Object.prototype.toString.call(value) === '[object Object]'\n}\n","import { isObject } from './isObject'\nimport { isPropertyAccessible } from './isPropertyAccessible'\n\n/**\n * Creates a generic 500 Unhandled Exception response.\n */\nexport function createServerErrorResponse(body: unknown): Response {\n return new Response(\n JSON.stringify(\n body instanceof Error\n ? {\n name: body.name,\n message: body.message,\n stack: body.stack,\n }\n : body\n ),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n}\n\nexport type ResponseError = Response & { type: 'error' }\n\n/**\n * Check if the given response is a `Response.error()`.\n *\n * @note Some environments, like Miniflare (Cloudflare) do not\n * implement the \"Response.type\" property and throw on its access.\n * Safely check if we can access \"type\" on \"Response\" before continuing.\n * @see https://github.com/mswjs/msw/issues/1834\n */\nexport function isResponseError(response: unknown): response is ResponseError {\n return (\n response != null &&\n response instanceof Response &&\n isPropertyAccessible(response, 'type') &&\n response.type === 'error'\n )\n}\n\n/**\n * Check if the given value is a `Response` or a Response-like object.\n * This is different from `value instanceof Response` because it supports\n * custom `Response` constructors, like the one when using Undici directly.\n */\nexport function isResponseLike(value: unknown): value is Response {\n return (\n isObject<Record<string, any>>(value, true) &&\n isPropertyAccessible(value, 'status') &&\n isPropertyAccessible(value, 'statusText') &&\n isPropertyAccessible(value, 'bodyUsed')\n )\n}\n","export function isNodeLikeError(\n error: unknown\n): error is NodeJS.ErrnoException {\n if (error == null) {\n return false\n }\n\n if (!(error instanceof Error)) {\n return false\n }\n\n return 'code' in error && 'errno' in error\n}\n","import type { Emitter } from 'strict-event-emitter'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport type { HttpRequestEventMap } from '../glossary'\nimport { emitAsync } from './emitAsync'\nimport { RequestController } from '../RequestController'\nimport {\n createServerErrorResponse,\n isResponseError,\n isResponseLike,\n} from './responseUtils'\nimport { InterceptorError } from '../InterceptorError'\nimport { isNodeLikeError } from './isNodeLikeError'\nimport { isObject } from './isObject'\n\ninterface HandleRequestOptions {\n requestId: string\n request: Request\n emitter: Emitter<HttpRequestEventMap>\n controller: RequestController\n}\n\nexport async function handleRequest(\n options: HandleRequestOptions\n): Promise<void> {\n const handleResponse = async (\n response: Response | Error | Record<string, any>\n ) => {\n if (response instanceof Error) {\n await options.controller.errorWith(response)\n return true\n }\n\n // Handle \"Response.error()\" instances.\n if (isResponseError(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n /**\n * Handle normal responses or response-like objects.\n * @note This must come before the arbitrary object check\n * since Response instances are, in fact, objects.\n */\n if (isResponseLike(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n // Handle arbitrary objects provided to `.errorWith(reason)`.\n if (isObject(response)) {\n await options.controller.errorWith(response)\n return true\n }\n\n return false\n }\n\n const handleResponseError = async (error: unknown): Promise<boolean> => {\n // Forward the special interceptor error instances\n // to the developer. These must not be handled in any way.\n if (error instanceof InterceptorError) {\n throw result.error\n }\n\n // Support mocking Node.js-like errors.\n if (isNodeLikeError(error)) {\n await options.controller.errorWith(error)\n return true\n }\n\n // Handle thrown responses.\n if (error instanceof Response) {\n return await handleResponse(error)\n }\n\n return false\n }\n\n // Add the last \"request\" listener to check if the request\n // has been handled in any way. If it hasn't, resolve the\n // response promise with undefined.\n // options.emitter.once('request', async ({ requestId: pendingRequestId }) => {\n // if (\n // pendingRequestId === options.requestId &&\n // options.controller.readyState === RequestController.PENDING\n // ) {\n // await options.controller.passthrough()\n // }\n // })\n\n const requestAbortPromise = new DeferredPromise<void, unknown>()\n\n /**\n * @note `signal` is not always defined in React Native.\n */\n if (options.request.signal) {\n if (options.request.signal.aborted) {\n await options.controller.errorWith(options.request.signal.reason)\n return\n }\n\n options.request.signal.addEventListener(\n 'abort',\n () => {\n requestAbortPromise.reject(options.request.signal.reason)\n },\n { once: true }\n )\n }\n\n const result = await until(async () => {\n // Emit the \"request\" event and wait until all the listeners\n // for that event are finished (e.g. async listeners awaited).\n // By the end of this promise, the developer cannot affect the\n // request anymore.\n const requestListenersPromise = emitAsync(options.emitter, 'request', {\n requestId: options.requestId,\n request: options.request,\n controller: options.controller,\n })\n\n await Promise.race([\n // Short-circuit the request handling promise if the request gets aborted.\n requestAbortPromise,\n requestListenersPromise,\n options.controller.handled,\n ])\n })\n\n // Handle the request being aborted while waiting for the request listeners.\n if (requestAbortPromise.state === 'rejected') {\n await options.controller.errorWith(requestAbortPromise.rejectionReason)\n return\n }\n\n if (result.error) {\n // Handle the error during the request listener execution.\n // These can be thrown responses or request errors.\n if (await handleResponseError(result.error)) {\n return\n }\n\n // If the developer has added \"unhandledException\" listeners,\n // allow them to handle the error. They can translate it to a\n // mocked response, network error, or forward it as-is.\n if (options.emitter.listenerCount('unhandledException') > 0) {\n // Create a new request controller just for the unhandled exception case.\n // This is needed because the original controller might have been already\n // interacted with (e.g. \"respondWith\" or \"errorWith\" called on it).\n const unhandledExceptionController = new RequestController(\n options.request,\n {\n /**\n * @note Intentionally empty passthrough handle.\n * This controller is created within another controller and we only need\n * to know if `unhandledException` listeners handled the request.\n */\n passthrough() {},\n async respondWith(response) {\n await handleResponse(response)\n },\n async errorWith(reason) {\n /**\n * @note Handle the result of the unhandled controller\n * in the same way as the original request controller.\n * The exception here is that thrown errors within the\n * \"unhandledException\" event do NOT result in another\n * emit of the same event. They are forwarded as-is.\n */\n await options.controller.errorWith(reason)\n },\n }\n )\n\n await emitAsync(options.emitter, 'unhandledException', {\n error: result.error,\n request: options.request,\n requestId: options.requestId,\n controller: unhandledExceptionController,\n })\n\n // If all the \"unhandledException\" listeners have finished\n // but have not handled the request in any way, passthrough.\n if (\n unhandledExceptionController.readyState !== RequestController.PENDING\n ) {\n return\n }\n }\n\n // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.\n await options.controller.respondWith(\n createServerErrorResponse(result.error)\n )\n return\n }\n\n // If the request hasn't been handled by this point, passthrough.\n if (options.controller.readyState === RequestController.PENDING) {\n return await options.controller.passthrough()\n }\n\n return options.controller.handled\n}\n"],"mappings":"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"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| let outvariant = require("outvariant"); | ||
| //#region src/utils/patchesRegistry.ts | ||
| var PatchesRegistry = class { | ||
| #replacements = /* @__PURE__ */ new Map(); | ||
| applyPatch(owner, key, getNextValue) { | ||
| const ownerReplacements = this.#replacements.get(owner); | ||
| (0, outvariant.invariant)(!ownerReplacements?.has(key), `Failed to replace a global value at "${String(key)}": already replaced.`); | ||
| const match = getDeepPropertyDescriptor(owner, key); | ||
| if (typeof match === "undefined") { | ||
| console.warn(`Failed to replace a global value at "${String(key)}": not a global value.`); | ||
| return () => {}; | ||
| } | ||
| Object.defineProperty(owner, key, { | ||
| value: getNextValue(owner[key]), | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| const restorePatch = () => { | ||
| const currentReplacements = this.#replacements.get(owner); | ||
| if (!currentReplacements?.has(key)) return; | ||
| if (match.owner === owner) Object.defineProperty(match.owner, key, match.descriptor); | ||
| else | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the match's owner isn't the original owner, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(owner, key); | ||
| currentReplacements.delete(key); | ||
| if (currentReplacements.size === 0) this.#replacements.delete(owner); | ||
| }; | ||
| if (ownerReplacements) ownerReplacements.set(key, restorePatch); | ||
| else this.#replacements.set(owner, new Map([[key, restorePatch]])); | ||
| return restorePatch; | ||
| } | ||
| restoreAllPatches() { | ||
| const errors = []; | ||
| for (const [, ownerReplacements] of this.#replacements) for (const [, restorePatch] of ownerReplacements) try { | ||
| restorePatch(); | ||
| } catch (error) { | ||
| if (error instanceof Error) errors.push(error); | ||
| else throw error; | ||
| } | ||
| if (errors.length > 0) throw new AggregateError(errors, "FOO!"); | ||
| } | ||
| }; | ||
| const patchesRegistry = new PatchesRegistry(); | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| function getDeepPropertyDescriptor(owner, key) { | ||
| let currentOwner = owner; | ||
| let descriptor; | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key); | ||
| if (descriptor) return { | ||
| owner: currentOwner, | ||
| descriptor | ||
| }; | ||
| currentOwner = Object.getPrototypeOf(currentOwner); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/hasConfigurableGlobal.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given global property | ||
| * is defined and is configurable. | ||
| */ | ||
| function hasConfigurableGlobal(propertyName) { | ||
| const match = getDeepPropertyDescriptor(globalThis, propertyName); | ||
| if (typeof match === "undefined") return false; | ||
| const { descriptor } = match; | ||
| if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false; | ||
| if (typeof descriptor.get === "undefined" && descriptor.value == null) return false; | ||
| if (typeof descriptor.set === "undefined" && !descriptor.configurable) { | ||
| console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'hasConfigurableGlobal', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return hasConfigurableGlobal; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'patchesRegistry', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return patchesRegistry; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=hasConfigurableGlobal-BtHi5OlL.cjs.map |
| {"version":3,"file":"hasConfigurableGlobal-BtHi5OlL.cjs","names":["#replacements","errors: Array<Error>","currentOwner: Owner | null","descriptor: PropertyDescriptor | undefined"],"sources":["../../src/utils/patchesRegistry.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { invariant } from 'outvariant'\n\nclass PatchesRegistry {\n #replacements = new Map<object, Map<PropertyKey, () => void>>()\n\n public applyPatch<Owner extends object, K extends keyof Owner>(\n owner: Owner,\n key: K,\n getNextValue: (realValue: Owner[K]) => Owner[K]\n ): () => void {\n const ownerReplacements = this.#replacements.get(owner)\n\n invariant(\n !ownerReplacements?.has(key),\n `Failed to replace a global value at \"${String(key)}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(owner, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${String(key)}\": not a global value.`\n )\n return () => {}\n }\n\n Object.defineProperty(owner, key, {\n value: getNextValue(owner[key]),\n enumerable: true,\n configurable: true,\n })\n\n const restorePatch = () => {\n const currentReplacements = this.#replacements.get(owner)\n\n if (!currentReplacements?.has(key)) {\n return\n }\n\n if (match.owner === owner) {\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the match's owner isn't the original owner, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(owner, key)\n }\n\n currentReplacements.delete(key)\n\n if (currentReplacements.size === 0) {\n this.#replacements.delete(owner)\n }\n }\n\n if (ownerReplacements) {\n ownerReplacements.set(key, restorePatch)\n } else {\n this.#replacements.set(owner, new Map([[key, restorePatch]]))\n }\n\n return restorePatch\n }\n\n public restoreAllPatches(): void {\n const errors: Array<Error> = []\n\n for (const [, ownerReplacements] of this.#replacements) {\n for (const [, restorePatch] of ownerReplacements) {\n try {\n restorePatch()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const patchesRegistry = new PatchesRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: keyof Owner\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n","import { getDeepPropertyDescriptor } from './patchesRegistry'\n\n/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(\n propertyName: keyof typeof globalThis\n): boolean {\n const match = getDeepPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof match === 'undefined') {\n return false\n }\n\n const { descriptor } = match\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;;AAEA,IAAM,kBAAN,MAAsB;CACpB,gCAAgB,IAAI,KAA2C;CAE/D,AAAO,WACL,OACA,KACA,cACY;EACZ,MAAM,oBAAoB,MAAKA,aAAc,IAAI,MAAM;AAEvD,4BACE,CAAC,mBAAmB,IAAI,IAAI,EAC5B,wCAAwC,OAAO,IAAI,CAAC,sBACrD;EAED,MAAM,QAAQ,0BAA0B,OAAO,IAAI;AAEnD,MAAI,OAAO,UAAU,aAAa;AAChC,WAAQ,KACN,wCAAwC,OAAO,IAAI,CAAC,wBACrD;AACD,gBAAa;;AAGf,SAAO,eAAe,OAAO,KAAK;GAChC,OAAO,aAAa,MAAM,KAAK;GAC/B,YAAY;GACZ,cAAc;GACf,CAAC;EAEF,MAAM,qBAAqB;GACzB,MAAM,sBAAsB,MAAKA,aAAc,IAAI,MAAM;AAEzD,OAAI,CAAC,qBAAqB,IAAI,IAAI,CAChC;AAGF,OAAI,MAAM,UAAU,MAClB,QAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;;;;;;;AAOzD,WAAQ,eAAe,OAAO,IAAI;AAGpC,uBAAoB,OAAO,IAAI;AAE/B,OAAI,oBAAoB,SAAS,EAC/B,OAAKA,aAAc,OAAO,MAAM;;AAIpC,MAAI,kBACF,mBAAkB,IAAI,KAAK,aAAa;MAExC,OAAKA,aAAc,IAAI,OAAO,IAAI,IAAI,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC;AAG/D,SAAO;;CAGT,AAAO,oBAA0B;EAC/B,MAAMC,SAAuB,EAAE;AAE/B,OAAK,MAAM,GAAG,sBAAsB,MAAKD,aACvC,MAAK,MAAM,GAAG,iBAAiB,kBAC7B,KAAI;AACF,iBAAc;WACP,OAAO;AACd,OAAI,iBAAiB,MACnB,QAAO,KAAK,MAAM;OAElB,OAAM;;AAMd,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,QAAQ,OAAO;;;AAK9C,MAAa,kBAAkB,IAAI,iBAAiB;;;;;;;AAapD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAIE,eAA6B;CACjC,IAAIC;AAEJ,QAAO,cAAc;AACnB,eAAa,OAAO,yBAAyB,cAAc,IAAI;AAE/D,MAAI,WACF,QAAO;GACL,OAAO;GACP;GACD;AAGH,iBAAe,OAAO,eAAe,aAAa;;;;;;;;;;ACjHtD,SAAgB,sBACd,cACS;CACT,MAAM,QAAQ,0BAA0B,YAAY,aAAa;AAGjE,KAAI,OAAO,UAAU,YACnB,QAAO;CAGT,MAAM,EAAE,eAAe;AAGvB,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"} |
| import { invariant } from "outvariant"; | ||
| //#region src/utils/patchesRegistry.ts | ||
| var PatchesRegistry = class { | ||
| #replacements = /* @__PURE__ */ new Map(); | ||
| applyPatch(owner, key, getNextValue) { | ||
| const ownerReplacements = this.#replacements.get(owner); | ||
| invariant(!ownerReplacements?.has(key), `Failed to replace a global value at "${String(key)}": already replaced.`); | ||
| const match = getDeepPropertyDescriptor(owner, key); | ||
| if (typeof match === "undefined") { | ||
| console.warn(`Failed to replace a global value at "${String(key)}": not a global value.`); | ||
| return () => {}; | ||
| } | ||
| Object.defineProperty(owner, key, { | ||
| value: getNextValue(owner[key]), | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| const restorePatch = () => { | ||
| const currentReplacements = this.#replacements.get(owner); | ||
| if (!currentReplacements?.has(key)) return; | ||
| if (match.owner === owner) Object.defineProperty(match.owner, key, match.descriptor); | ||
| else | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the match's owner isn't the original owner, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(owner, key); | ||
| currentReplacements.delete(key); | ||
| if (currentReplacements.size === 0) this.#replacements.delete(owner); | ||
| }; | ||
| if (ownerReplacements) ownerReplacements.set(key, restorePatch); | ||
| else this.#replacements.set(owner, new Map([[key, restorePatch]])); | ||
| return restorePatch; | ||
| } | ||
| restoreAllPatches() { | ||
| const errors = []; | ||
| for (const [, ownerReplacements] of this.#replacements) for (const [, restorePatch] of ownerReplacements) try { | ||
| restorePatch(); | ||
| } catch (error) { | ||
| if (error instanceof Error) errors.push(error); | ||
| else throw error; | ||
| } | ||
| if (errors.length > 0) throw new AggregateError(errors, "FOO!"); | ||
| } | ||
| }; | ||
| const patchesRegistry = new PatchesRegistry(); | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| function getDeepPropertyDescriptor(owner, key) { | ||
| let currentOwner = owner; | ||
| let descriptor; | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key); | ||
| if (descriptor) return { | ||
| owner: currentOwner, | ||
| descriptor | ||
| }; | ||
| currentOwner = Object.getPrototypeOf(currentOwner); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/hasConfigurableGlobal.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given global property | ||
| * is defined and is configurable. | ||
| */ | ||
| function hasConfigurableGlobal(propertyName) { | ||
| const match = getDeepPropertyDescriptor(globalThis, propertyName); | ||
| if (typeof match === "undefined") return false; | ||
| const { descriptor } = match; | ||
| if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false; | ||
| if (typeof descriptor.get === "undefined" && descriptor.value == null) return false; | ||
| if (typeof descriptor.set === "undefined" && !descriptor.configurable) { | ||
| console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| //#endregion | ||
| export { patchesRegistry as n, hasConfigurableGlobal as t }; | ||
| //# sourceMappingURL=hasConfigurableGlobal-Cwrs35Xo.mjs.map |
| {"version":3,"file":"hasConfigurableGlobal-Cwrs35Xo.mjs","names":["#replacements","errors: Array<Error>","currentOwner: Owner | null","descriptor: PropertyDescriptor | undefined"],"sources":["../../src/utils/patchesRegistry.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { invariant } from 'outvariant'\n\nclass PatchesRegistry {\n #replacements = new Map<object, Map<PropertyKey, () => void>>()\n\n public applyPatch<Owner extends object, K extends keyof Owner>(\n owner: Owner,\n key: K,\n getNextValue: (realValue: Owner[K]) => Owner[K]\n ): () => void {\n const ownerReplacements = this.#replacements.get(owner)\n\n invariant(\n !ownerReplacements?.has(key),\n `Failed to replace a global value at \"${String(key)}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(owner, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${String(key)}\": not a global value.`\n )\n return () => {}\n }\n\n Object.defineProperty(owner, key, {\n value: getNextValue(owner[key]),\n enumerable: true,\n configurable: true,\n })\n\n const restorePatch = () => {\n const currentReplacements = this.#replacements.get(owner)\n\n if (!currentReplacements?.has(key)) {\n return\n }\n\n if (match.owner === owner) {\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the match's owner isn't the original owner, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(owner, key)\n }\n\n currentReplacements.delete(key)\n\n if (currentReplacements.size === 0) {\n this.#replacements.delete(owner)\n }\n }\n\n if (ownerReplacements) {\n ownerReplacements.set(key, restorePatch)\n } else {\n this.#replacements.set(owner, new Map([[key, restorePatch]]))\n }\n\n return restorePatch\n }\n\n public restoreAllPatches(): void {\n const errors: Array<Error> = []\n\n for (const [, ownerReplacements] of this.#replacements) {\n for (const [, restorePatch] of ownerReplacements) {\n try {\n restorePatch()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const patchesRegistry = new PatchesRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: keyof Owner\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n","import { getDeepPropertyDescriptor } from './patchesRegistry'\n\n/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(\n propertyName: keyof typeof globalThis\n): boolean {\n const match = getDeepPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof match === 'undefined') {\n return false\n }\n\n const { descriptor } = match\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;AAEA,IAAM,kBAAN,MAAsB;CACpB,gCAAgB,IAAI,KAA2C;CAE/D,AAAO,WACL,OACA,KACA,cACY;EACZ,MAAM,oBAAoB,MAAKA,aAAc,IAAI,MAAM;AAEvD,YACE,CAAC,mBAAmB,IAAI,IAAI,EAC5B,wCAAwC,OAAO,IAAI,CAAC,sBACrD;EAED,MAAM,QAAQ,0BAA0B,OAAO,IAAI;AAEnD,MAAI,OAAO,UAAU,aAAa;AAChC,WAAQ,KACN,wCAAwC,OAAO,IAAI,CAAC,wBACrD;AACD,gBAAa;;AAGf,SAAO,eAAe,OAAO,KAAK;GAChC,OAAO,aAAa,MAAM,KAAK;GAC/B,YAAY;GACZ,cAAc;GACf,CAAC;EAEF,MAAM,qBAAqB;GACzB,MAAM,sBAAsB,MAAKA,aAAc,IAAI,MAAM;AAEzD,OAAI,CAAC,qBAAqB,IAAI,IAAI,CAChC;AAGF,OAAI,MAAM,UAAU,MAClB,QAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;;;;;;;AAOzD,WAAQ,eAAe,OAAO,IAAI;AAGpC,uBAAoB,OAAO,IAAI;AAE/B,OAAI,oBAAoB,SAAS,EAC/B,OAAKA,aAAc,OAAO,MAAM;;AAIpC,MAAI,kBACF,mBAAkB,IAAI,KAAK,aAAa;MAExC,OAAKA,aAAc,IAAI,OAAO,IAAI,IAAI,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC;AAG/D,SAAO;;CAGT,AAAO,oBAA0B;EAC/B,MAAMC,SAAuB,EAAE;AAE/B,OAAK,MAAM,GAAG,sBAAsB,MAAKD,aACvC,MAAK,MAAM,GAAG,iBAAiB,kBAC7B,KAAI;AACF,iBAAc;WACP,OAAO;AACd,OAAI,iBAAiB,MACnB,QAAO,KAAK,MAAM;OAElB,OAAM;;AAMd,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,QAAQ,OAAO;;;AAK9C,MAAa,kBAAkB,IAAI,iBAAiB;;;;;;;AAapD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAIE,eAA6B;CACjC,IAAIC;AAEJ,QAAO,cAAc;AACnB,eAAa,OAAO,yBAAyB,cAAc,IAAI;AAE/D,MAAI,WACF,QAAO;GACL,OAAO;GACP;GACD;AAGH,iBAAe,OAAO,eAAe,aAAa;;;;;;;;;;ACjHtD,SAAgB,sBACd,cACS;CACT,MAAM,QAAQ,0BAA0B,YAAY,aAAa;AAGjE,KAAI,OAAO,UAAU,YACnB,QAAO;CAGT,MAAM,EAAE,eAAe;AAGvB,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"} |
| import { a as RequestController, c as Interceptor, i as createRequestId, n as FetchResponse, s as INTERNAL_REQUEST_ID_HEADER_NAME, t as FetchRequest } from "./fetchUtils-BKJ1XmiO.mjs"; | ||
| import { n as encodeBuffer, r as toArrayBuffer, t as decodeBuffer } from "./bufferUtils-DxPxwff_.mjs"; | ||
| import { n as setRawRequest } from "./getRawRequest-C2-1urzA.mjs"; | ||
| import { n as isResponseError, t as handleRequest } from "./handleRequest-DCLzePtS.mjs"; | ||
| import { n as patchesRegistry, t as hasConfigurableGlobal } from "./hasConfigurableGlobal-Cwrs35Xo.mjs"; | ||
| import { invariant } from "outvariant"; | ||
| import { isNodeProcess } from "is-node-process"; | ||
| //#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts | ||
| /** | ||
| * Concatenate two `Uint8Array` buffers. | ||
| */ | ||
| function concatArrayBuffer(left, right) { | ||
| const result = new Uint8Array(left.byteLength + right.byteLength); | ||
| result.set(left, 0); | ||
| result.set(right, left.byteLength); | ||
| return result; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts | ||
| var EventPolyfill = class { | ||
| constructor(type, options) { | ||
| this.NONE = 0; | ||
| this.CAPTURING_PHASE = 1; | ||
| this.AT_TARGET = 2; | ||
| this.BUBBLING_PHASE = 3; | ||
| this.type = ""; | ||
| this.srcElement = null; | ||
| this.currentTarget = null; | ||
| this.eventPhase = 0; | ||
| this.isTrusted = true; | ||
| this.composed = false; | ||
| this.cancelable = true; | ||
| this.defaultPrevented = false; | ||
| this.bubbles = true; | ||
| this.lengthComputable = true; | ||
| this.loaded = 0; | ||
| this.total = 0; | ||
| this.cancelBubble = false; | ||
| this.returnValue = true; | ||
| this.type = type; | ||
| this.target = options?.target || null; | ||
| this.currentTarget = options?.currentTarget || null; | ||
| this.timeStamp = Date.now(); | ||
| } | ||
| composedPath() { | ||
| return []; | ||
| } | ||
| initEvent(type, bubbles, cancelable) { | ||
| this.type = type; | ||
| this.bubbles = !!bubbles; | ||
| this.cancelable = !!cancelable; | ||
| } | ||
| preventDefault() { | ||
| this.defaultPrevented = true; | ||
| } | ||
| stopPropagation() {} | ||
| stopImmediatePropagation() {} | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts | ||
| var ProgressEventPolyfill = class extends EventPolyfill { | ||
| constructor(type, init) { | ||
| super(type); | ||
| this.lengthComputable = init?.lengthComputable || false; | ||
| this.composed = init?.composed || false; | ||
| this.loaded = init?.loaded || 0; | ||
| this.total = init?.total || 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createEvent.ts | ||
| const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined"; | ||
| function createEvent(target, type, init) { | ||
| const progressEvents = [ | ||
| "error", | ||
| "progress", | ||
| "loadstart", | ||
| "loadend", | ||
| "load", | ||
| "timeout", | ||
| "abort" | ||
| ]; | ||
| /** | ||
| * `ProgressEvent` is not supported in React Native. | ||
| * @see https://github.com/mswjs/interceptors/issues/40 | ||
| */ | ||
| const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill; | ||
| return progressEvents.includes(type) ? new ProgressEventClass(type, { | ||
| lengthComputable: true, | ||
| loaded: init?.loaded || 0, | ||
| total: init?.total || 0 | ||
| }) : new EventPolyfill(type, { | ||
| target, | ||
| currentTarget: target | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/findPropertySource.ts | ||
| /** | ||
| * Returns the source object of the given property on the target object | ||
| * (the target itself, any parent in its prototype, or null). | ||
| */ | ||
| function findPropertySource(target, propertyName) { | ||
| if (!(propertyName in target)) return null; | ||
| if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target; | ||
| const prototype = Reflect.getPrototypeOf(target); | ||
| return prototype ? findPropertySource(prototype, propertyName) : null; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/createProxy.ts | ||
| function createProxy(target, options) { | ||
| return new Proxy(target, optionsToProxyHandler(options)); | ||
| } | ||
| function optionsToProxyHandler(options) { | ||
| const { constructorCall, methodCall, getProperty, setProperty } = options; | ||
| const handler = {}; | ||
| if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) { | ||
| const next = Reflect.construct.bind(null, target, args, newTarget); | ||
| return constructorCall.call(newTarget, args, next); | ||
| }; | ||
| handler.set = function(target, propertyName, nextValue) { | ||
| const next = () => { | ||
| const propertySource = findPropertySource(target, propertyName) || target; | ||
| const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName); | ||
| if (typeof ownDescriptors?.set !== "undefined") { | ||
| ownDescriptors.set.apply(target, [nextValue]); | ||
| return true; | ||
| } | ||
| return Reflect.defineProperty(propertySource, propertyName, { | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| value: nextValue | ||
| }); | ||
| }; | ||
| if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next); | ||
| return next(); | ||
| }; | ||
| handler.get = function(target, propertyName, receiver) { | ||
| /** | ||
| * @note Using `Reflect.get()` here causes "TypeError: Illegal invocation". | ||
| */ | ||
| const next = () => target[propertyName]; | ||
| const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next(); | ||
| if (typeof value === "function") return (...args) => { | ||
| const next$1 = value.bind(target, ...args); | ||
| if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1); | ||
| return next$1(); | ||
| }; | ||
| return value; | ||
| }; | ||
| return handler; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts | ||
| function isDomParserSupportedType(type) { | ||
| return [ | ||
| "application/xhtml+xml", | ||
| "application/xml", | ||
| "image/svg+xml", | ||
| "text/html", | ||
| "text/xml" | ||
| ].some((supportedType) => { | ||
| return type.startsWith(supportedType); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/parseJson.ts | ||
| /** | ||
| * Parses a given string into JSON. | ||
| * Gracefully handles invalid JSON by returning `null`. | ||
| */ | ||
| function parseJson(data) { | ||
| try { | ||
| return JSON.parse(data); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createResponse.ts | ||
| /** | ||
| * Creates a Fetch API `Response` instance from the given | ||
| * `XMLHttpRequest` instance and a response body. | ||
| */ | ||
| function createResponse(request, body) { | ||
| return new FetchResponse(FetchResponse.isResponseWithBody(request.status) ? body : null, { | ||
| url: request.responseURL, | ||
| status: request.status, | ||
| statusText: request.statusText, | ||
| headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders()) | ||
| }); | ||
| } | ||
| function createHeadersFromXMLHttpRequestHeaders(headersString) { | ||
| const headers = new Headers(); | ||
| const lines = headersString.split(/[\r\n]+/); | ||
| for (const line of lines) { | ||
| if (line.trim() === "") continue; | ||
| const [name, ...parts] = line.split(": "); | ||
| const value = parts.join(": "); | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts | ||
| /** | ||
| * Return a total byte length of the given request/response body. | ||
| * If the `Content-Length` header is present, it will be used as the byte length. | ||
| */ | ||
| async function getBodyByteLength(input) { | ||
| const explicitContentLength = input.headers.get("content-length"); | ||
| if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength); | ||
| return (await input.arrayBuffer()).byteLength; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts | ||
| const kIsRequestHandled = Symbol("kIsRequestHandled"); | ||
| const IS_NODE = isNodeProcess(); | ||
| const kFetchRequest = Symbol("kFetchRequest"); | ||
| /** | ||
| * An `XMLHttpRequest` instance controller that allows us | ||
| * to handle any given request instance (e.g. responding to it). | ||
| */ | ||
| var XMLHttpRequestController = class { | ||
| constructor(initialRequest, logger) { | ||
| this.initialRequest = initialRequest; | ||
| this.logger = logger; | ||
| this.method = "GET"; | ||
| this.url = null; | ||
| this[kIsRequestHandled] = false; | ||
| this.events = /* @__PURE__ */ new Map(); | ||
| this.uploadEvents = /* @__PURE__ */ new Map(); | ||
| this.requestId = createRequestId(); | ||
| this.requestHeaders = new Headers(); | ||
| this.responseBuffer = new Uint8Array(); | ||
| this.request = createProxy(initialRequest, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "ontimeout": { | ||
| const eventName = propertyName.slice(2); | ||
| /** | ||
| * @note Proxy callbacks to event listeners because JSDOM has trouble | ||
| * translating these properties to callbacks. It seemed to be operating | ||
| * on events exclusively. | ||
| */ | ||
| this.request.addEventListener(eventName, nextValue); | ||
| return invoke(); | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "open": { | ||
| const [method, url] = args; | ||
| if (typeof url === "undefined") { | ||
| this.method = "GET"; | ||
| this.url = toAbsoluteUrl(method); | ||
| } else { | ||
| this.method = method; | ||
| this.url = toAbsoluteUrl(url); | ||
| } | ||
| this.logger = this.logger.extend(`${this.method} ${this.url.href}`); | ||
| this.logger.info("open", this.method, this.url.href); | ||
| return invoke(); | ||
| } | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerEvent(eventName, listener); | ||
| this.logger.info("addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| case "setRequestHeader": { | ||
| const [name, value] = args; | ||
| this.requestHeaders.set(name, value); | ||
| this.logger.info("setRequestHeader", name, value); | ||
| return invoke(); | ||
| } | ||
| case "send": { | ||
| const [body] = args; | ||
| this.request.addEventListener("load", () => { | ||
| if (typeof this.onResponse !== "undefined") { | ||
| const fetchResponse = createResponse( | ||
| this.request, | ||
| /** | ||
| * The `response` property is the right way to read | ||
| * the ambiguous response body, as the request's "responseType" may differ. | ||
| * @see https://xhr.spec.whatwg.org/#the-response-attribute | ||
| */ | ||
| this.request.response | ||
| ); | ||
| this.onResponse.call(this, { | ||
| response: fetchResponse, | ||
| isMockedResponse: this[kIsRequestHandled], | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }); | ||
| } | ||
| }); | ||
| const requestBody = typeof body === "string" ? encodeBuffer(body) : body; | ||
| const fetchRequest = this.toFetchApiRequest(requestBody); | ||
| this[kFetchRequest] = fetchRequest.clone(); | ||
| /** | ||
| * @note Start request handling on the next tick so that the user | ||
| * could add event listeners for "loadend" before the interceptor fires it. | ||
| */ | ||
| queueMicrotask(() => { | ||
| (this.onRequest?.call(this, { | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }) || Promise.resolve()).finally(() => { | ||
| if (!this[kIsRequestHandled]) { | ||
| this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState); | ||
| /** | ||
| * @note Set the intercepted request ID on the original request in Node.js | ||
| * so that if it triggers any other interceptors, they don't attempt | ||
| * to process it once again. | ||
| * | ||
| * For instance, XMLHttpRequest is often implemented via "http.ClientRequest" | ||
| * and we don't want for both XHR and ClientRequest interceptors to | ||
| * handle the same request at the same time (e.g. emit the "response" event twice). | ||
| */ | ||
| if (IS_NODE) this.request.setRequestHeader(INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId); | ||
| return invoke(); | ||
| } | ||
| }); | ||
| }); | ||
| break; | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| } | ||
| }); | ||
| /** | ||
| * Proxy the `.upload` property to gather the event listeners/callbacks. | ||
| */ | ||
| define(this.request, "upload", createProxy(this.request.upload, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "onloadstart": | ||
| case "onprogress": | ||
| case "onaboart": | ||
| case "onerror": | ||
| case "onload": | ||
| case "ontimeout": | ||
| case "onloadend": { | ||
| const eventName = propertyName.slice(2); | ||
| this.registerUploadEvent(eventName, nextValue); | ||
| } | ||
| } | ||
| return invoke(); | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerUploadEvent(eventName, listener); | ||
| this.logger.info("upload.addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| } | ||
| } | ||
| })); | ||
| } | ||
| registerEvent(eventName, listener) { | ||
| const nextEvents = (this.events.get(eventName) || []).concat(listener); | ||
| this.events.set(eventName, nextEvents); | ||
| this.logger.info("registered event \"%s\"", eventName, listener); | ||
| } | ||
| registerUploadEvent(eventName, listener) { | ||
| const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener); | ||
| this.uploadEvents.set(eventName, nextEvents); | ||
| this.logger.info("registered upload event \"%s\"", eventName, listener); | ||
| } | ||
| /** | ||
| * Responds to the current request with the given | ||
| * Fetch API `Response` instance. | ||
| */ | ||
| async respondWith(response) { | ||
| /** | ||
| * @note Since `XMLHttpRequestController` delegates the handling of the responses | ||
| * to the "load" event listener that doesn't distinguish between the mocked and original | ||
| * responses, mark the request that had a mocked response with a corresponding symbol. | ||
| * | ||
| * Mark this request as having a mocked response immediately since | ||
| * calculating request/response total body length is asynchronous. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| /** | ||
| * Dispatch request upload events for requests with a body. | ||
| * @see https://github.com/mswjs/interceptors/issues/573 | ||
| */ | ||
| if (this[kFetchRequest]) { | ||
| const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]); | ||
| this.trigger("loadstart", this.request.upload, { | ||
| loaded: 0, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("progress", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("load", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| } | ||
| this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText); | ||
| define(this.request, "status", response.status); | ||
| define(this.request, "statusText", response.statusText); | ||
| define(this.request, "responseURL", this.url.href); | ||
| this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => { | ||
| this.logger.info("getResponseHeader", args[0]); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning null"); | ||
| return null; | ||
| } | ||
| const headerValue = response.headers.get(args[0]); | ||
| this.logger.info("resolved response header \"%s\" to", args[0], headerValue); | ||
| return headerValue; | ||
| } }); | ||
| this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => { | ||
| this.logger.info("getAllResponseHeaders"); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning empty string"); | ||
| return ""; | ||
| } | ||
| const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => { | ||
| return `${headerName}: ${headerValue}`; | ||
| }).join("\r\n"); | ||
| this.logger.info("resolved all response headers to", allHeaders); | ||
| return allHeaders; | ||
| } }); | ||
| Object.defineProperties(this.request, { | ||
| response: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.response | ||
| }, | ||
| responseText: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseText | ||
| }, | ||
| responseXML: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseXML | ||
| } | ||
| }); | ||
| const totalResponseBodyLength = await getBodyByteLength(response.clone()); | ||
| this.logger.info("calculated response body length", totalResponseBodyLength); | ||
| this.trigger("loadstart", this.request, { | ||
| loaded: 0, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.setReadyState(this.request.HEADERS_RECEIVED); | ||
| this.setReadyState(this.request.LOADING); | ||
| const finalizeResponse = () => { | ||
| this.logger.info("finalizing the mocked response..."); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("load", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| }; | ||
| if (response.body) { | ||
| this.logger.info("mocked response has body, streaming..."); | ||
| const reader = response.body.getReader(); | ||
| const readNextResponseBodyChunk = async () => { | ||
| const { value, done } = await reader.read(); | ||
| if (done) { | ||
| this.logger.info("response body stream done!"); | ||
| finalizeResponse(); | ||
| return; | ||
| } | ||
| if (value) { | ||
| this.logger.info("read response body chunk:", value); | ||
| this.responseBuffer = concatArrayBuffer(this.responseBuffer, value); | ||
| this.trigger("progress", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| } | ||
| readNextResponseBodyChunk(); | ||
| }; | ||
| readNextResponseBodyChunk(); | ||
| } else finalizeResponse(); | ||
| } | ||
| responseBufferToText() { | ||
| return decodeBuffer(this.responseBuffer); | ||
| } | ||
| get response() { | ||
| this.logger.info("getResponse (responseType: %s)", this.request.responseType); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| switch (this.request.responseType) { | ||
| case "json": { | ||
| const responseJson = parseJson(this.responseBufferToText()); | ||
| this.logger.info("resolved response JSON", responseJson); | ||
| return responseJson; | ||
| } | ||
| case "arraybuffer": { | ||
| const arrayBuffer = toArrayBuffer(this.responseBuffer); | ||
| this.logger.info("resolved response ArrayBuffer", arrayBuffer); | ||
| return arrayBuffer; | ||
| } | ||
| case "blob": { | ||
| const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain"; | ||
| const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType }); | ||
| this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType); | ||
| return responseBlob; | ||
| } | ||
| default: { | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText); | ||
| return responseText; | ||
| } | ||
| } | ||
| } | ||
| get responseText() { | ||
| /** | ||
| * Throw when trying to read the response body as text when the | ||
| * "responseType" doesn't expect text. This just respects the spec better. | ||
| * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute | ||
| */ | ||
| invariant(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return ""; | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("getResponseText: \"%s\"", responseText); | ||
| return responseText; | ||
| } | ||
| get responseXML() { | ||
| invariant(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| const contentType = this.request.getResponseHeader("Content-Type") || ""; | ||
| if (typeof DOMParser === "undefined") { | ||
| console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly."); | ||
| return null; | ||
| } | ||
| if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType); | ||
| return null; | ||
| } | ||
| errorWith(error) { | ||
| /** | ||
| * @note Mark this request as handled even if it received a mock error. | ||
| * This prevents the controller from trying to perform this request as-is. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| this.logger.info("responding with an error"); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("error", this.request); | ||
| this.trigger("loadend", this.request); | ||
| } | ||
| /** | ||
| * Transitions this request's `readyState` to the given one. | ||
| */ | ||
| setReadyState(nextReadyState) { | ||
| this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState); | ||
| if (this.request.readyState === nextReadyState) { | ||
| this.logger.info("ready state identical, skipping transition..."); | ||
| return; | ||
| } | ||
| define(this.request, "readyState", nextReadyState); | ||
| this.logger.info("set readyState to: %d", nextReadyState); | ||
| if (nextReadyState !== this.request.UNSENT) { | ||
| this.logger.info("triggering \"readystatechange\" event..."); | ||
| this.trigger("readystatechange", this.request); | ||
| } | ||
| } | ||
| /** | ||
| * Triggers given event on the `XMLHttpRequest` instance. | ||
| */ | ||
| trigger(eventName, target, options) { | ||
| const callback = target[`on${eventName}`]; | ||
| const event = createEvent(target, eventName, options); | ||
| this.logger.info("trigger \"%s\"", eventName, options || ""); | ||
| if (typeof callback === "function") { | ||
| this.logger.info("found a direct \"%s\" callback, calling...", eventName); | ||
| callback.call(target, event); | ||
| } | ||
| const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events; | ||
| for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) { | ||
| this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName); | ||
| listeners.forEach((listener) => listener.call(target, event)); | ||
| } | ||
| } | ||
| /** | ||
| * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance. | ||
| */ | ||
| toFetchApiRequest(body) { | ||
| this.logger.info("converting request to a Fetch API Request..."); | ||
| const resolvedBody = body instanceof Document ? body.documentElement.innerText : body; | ||
| const fetchRequest = new FetchRequest(this.url.href, { | ||
| method: this.method, | ||
| headers: this.requestHeaders, | ||
| credentials: this.request.withCredentials ? "include" : "same-origin", | ||
| body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody | ||
| }); | ||
| define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "append": | ||
| case "set": { | ||
| const [headerName, headerValue] = args; | ||
| this.request.setRequestHeader(headerName, headerValue); | ||
| break; | ||
| } | ||
| case "delete": { | ||
| const [headerName] = args; | ||
| console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`); | ||
| break; | ||
| } | ||
| } | ||
| return invoke(); | ||
| } })); | ||
| setRawRequest(fetchRequest, this.request); | ||
| this.logger.info("converted request to a Fetch API Request!", fetchRequest); | ||
| return fetchRequest; | ||
| } | ||
| }; | ||
| function toAbsoluteUrl(url) { | ||
| /** | ||
| * @note XMLHttpRequest interceptor may run in environments | ||
| * that implement XMLHttpRequest but don't implement "location" | ||
| * (for example, React Native). If that's the case, return the | ||
| * input URL as-is (nothing to be relative to). | ||
| * @see https://github.com/mswjs/msw/issues/1777 | ||
| */ | ||
| if (typeof location === "undefined") return new URL(url); | ||
| return new URL(url.toString(), location.href); | ||
| } | ||
| function define(target, property, value) { | ||
| Reflect.defineProperty(target, property, { | ||
| writable: true, | ||
| enumerable: true, | ||
| value | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts | ||
| /** | ||
| * Create a proxied `XMLHttpRequest` class. | ||
| * The proxied class establishes spies on certain methods, | ||
| * allowing us to intercept requests and respond to them. | ||
| */ | ||
| function createXMLHttpRequestProxy({ emitter, logger }) { | ||
| return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) { | ||
| logger.info("constructed new XMLHttpRequest"); | ||
| const originalRequest = Reflect.construct(target, args, newTarget); | ||
| /** | ||
| * @note Forward prototype descriptors onto the proxied object. | ||
| * XMLHttpRequest is implemented in JSDOM in a way that assigns | ||
| * a bunch of descriptors, like "set responseType()" on the prototype. | ||
| * With this propagation, we make sure that those descriptors trigger | ||
| * when the user operates with the proxied request instance. | ||
| */ | ||
| const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype); | ||
| for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]); | ||
| const xhrRequestController = new XMLHttpRequestController(originalRequest, logger); | ||
| xhrRequestController.onRequest = async function({ request, requestId }) { | ||
| const controller = new RequestController(request, { | ||
| passthrough: () => { | ||
| this.logger.info("no mocked response received, performing request as-is..."); | ||
| }, | ||
| respondWith: async (response) => { | ||
| if (isResponseError(response)) { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| await this.respondWith(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request errored!", { error: reason }); | ||
| if (reason instanceof Error) this.errorWith(reason); | ||
| } | ||
| }); | ||
| this.logger.info("awaiting mocked response..."); | ||
| this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request")); | ||
| await handleRequest({ | ||
| request, | ||
| requestId, | ||
| controller, | ||
| emitter | ||
| }); | ||
| }; | ||
| xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) { | ||
| this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response")); | ||
| emitter.emit("response", { | ||
| response, | ||
| isMockedResponse, | ||
| request, | ||
| requestId | ||
| }); | ||
| }; | ||
| return xhrRequestController.request; | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/index.ts | ||
| var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends Interceptor { | ||
| static { | ||
| this.interceptorSymbol = Symbol("xhr"); | ||
| } | ||
| constructor() { | ||
| super(XMLHttpRequestInterceptor.interceptorSymbol); | ||
| } | ||
| checkEnvironment() { | ||
| return hasConfigurableGlobal("XMLHttpRequest"); | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("patching global XMLHttpRequest..."); | ||
| this.subscriptions.push(patchesRegistry.applyPatch(globalThis, "XMLHttpRequest", () => { | ||
| return createXMLHttpRequestProxy({ | ||
| emitter: this.emitter, | ||
| logger: this.logger | ||
| }); | ||
| })); | ||
| logger.info("global XMLHttpRequest patched!", globalThis.XMLHttpRequest.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { XMLHttpRequestInterceptor as t }; | ||
| //# sourceMappingURL=XMLHttpRequest-BgkpwNOu.mjs.map |
| {"version":3,"file":"XMLHttpRequest-BgkpwNOu.mjs","names":["handler: ProxyHandler<T>","next","initialRequest: XMLHttpRequest","logger: Logger"],"sources":["../../src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts","../../src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts","../../src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts","../../src/interceptors/XMLHttpRequest/utils/createEvent.ts","../../src/utils/findPropertySource.ts","../../src/utils/createProxy.ts","../../src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts","../../src/utils/parseJson.ts","../../src/interceptors/XMLHttpRequest/utils/createResponse.ts","../../src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts","../../src/interceptors/XMLHttpRequest/index.ts"],"sourcesContent":["/**\n * Concatenate two `Uint8Array` buffers.\n */\nexport function concatArrayBuffer(\n left: Uint8Array,\n right: Uint8Array\n): Uint8Array {\n const result = new Uint8Array(left.byteLength + right.byteLength)\n result.set(left, 0)\n result.set(right, left.byteLength)\n return result\n}\n","export class EventPolyfill implements Event {\n readonly NONE = 0\n readonly CAPTURING_PHASE = 1\n readonly AT_TARGET = 2\n readonly BUBBLING_PHASE = 3\n\n public type: string = ''\n public srcElement: EventTarget | null = null\n public target: EventTarget | null\n public currentTarget: EventTarget | null = null\n public eventPhase: number = 0\n public timeStamp: number\n public isTrusted: boolean = true\n public composed: boolean = false\n public cancelable: boolean = true\n public defaultPrevented: boolean = false\n public bubbles: boolean = true\n public lengthComputable: boolean = true\n public loaded: number = 0\n public total: number = 0\n\n cancelBubble: boolean = false\n returnValue: boolean = true\n\n constructor(\n type: string,\n options?: { target: EventTarget; currentTarget: EventTarget }\n ) {\n this.type = type\n this.target = options?.target || null\n this.currentTarget = options?.currentTarget || null\n this.timeStamp = Date.now()\n }\n\n public composedPath(): EventTarget[] {\n return []\n }\n\n public initEvent(type: string, bubbles?: boolean, cancelable?: boolean) {\n this.type = type\n this.bubbles = !!bubbles\n this.cancelable = !!cancelable\n }\n\n public preventDefault() {\n this.defaultPrevented = true\n }\n\n public stopPropagation() {}\n public stopImmediatePropagation() {}\n}\n","import { EventPolyfill } from './EventPolyfill'\n\nexport class ProgressEventPolyfill extends EventPolyfill {\n readonly lengthComputable: boolean\n readonly composed: boolean\n readonly loaded: number\n readonly total: number\n\n constructor(type: string, init?: ProgressEventInit) {\n super(type)\n\n this.lengthComputable = init?.lengthComputable || false\n this.composed = init?.composed || false\n this.loaded = init?.loaded || 0\n this.total = init?.total || 0\n }\n}\n","import { EventPolyfill } from '../polyfills/EventPolyfill'\nimport { ProgressEventPolyfill } from '../polyfills/ProgressEventPolyfill'\n\nconst SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined'\n\nexport function createEvent(\n target: XMLHttpRequest | XMLHttpRequestUpload,\n type: string,\n init?: ProgressEventInit\n): EventPolyfill | ProgressEvent {\n const progressEvents = [\n 'error',\n 'progress',\n 'loadstart',\n 'loadend',\n 'load',\n 'timeout',\n 'abort',\n ]\n\n /**\n * `ProgressEvent` is not supported in React Native.\n * @see https://github.com/mswjs/interceptors/issues/40\n */\n const ProgressEventClass = SUPPORTS_PROGRESS_EVENT\n ? ProgressEvent\n : ProgressEventPolyfill\n\n const event = progressEvents.includes(type)\n ? new ProgressEventClass(type, {\n lengthComputable: true,\n loaded: init?.loaded || 0,\n total: init?.total || 0,\n })\n : new EventPolyfill(type, {\n target,\n currentTarget: target,\n })\n\n return event\n}\n","/**\n * Returns the source object of the given property on the target object\n * (the target itself, any parent in its prototype, or null).\n */\nexport function findPropertySource(\n target: object,\n propertyName: string | symbol\n): object | null {\n if (!(propertyName in target)) {\n return null\n }\n\n const hasProperty = Object.prototype.hasOwnProperty.call(target, propertyName)\n if (hasProperty) {\n return target\n }\n\n const prototype = Reflect.getPrototypeOf(target)\n return prototype ? findPropertySource(prototype, propertyName) : null\n}\n","import { findPropertySource } from './findPropertySource'\n\nexport interface ProxyOptions<Target extends Record<string, any>> {\n constructorCall?(args: Array<unknown>, next: NextFunction<Target>): Target\n\n methodCall?<F extends keyof Target>(\n this: Target,\n data: [methodName: F, args: Array<unknown>],\n next: NextFunction<void>\n ): void\n\n setProperty?(\n data: [propertyName: string | symbol, nextValue: unknown],\n next: NextFunction<boolean>\n ): boolean\n\n getProperty?(\n data: [propertyName: string | symbol, receiver: Target],\n next: NextFunction<void>\n ): void\n}\n\nexport type NextFunction<ReturnType> = () => ReturnType\n\nexport function createProxy<Target extends object>(\n target: Target,\n options: ProxyOptions<Target>\n): Target {\n const proxy = new Proxy(target, optionsToProxyHandler(options))\n\n return proxy\n}\n\nfunction optionsToProxyHandler<T extends Record<string, any>>(\n options: ProxyOptions<T>\n): ProxyHandler<T> {\n const { constructorCall, methodCall, getProperty, setProperty } = options\n const handler: ProxyHandler<T> = {}\n\n if (typeof constructorCall !== 'undefined') {\n handler.construct = function (target, args, newTarget) {\n const next = Reflect.construct.bind(null, target as any, args, newTarget)\n return constructorCall.call(newTarget, args, next)\n }\n }\n\n handler.set = function (target, propertyName, nextValue) {\n const next = () => {\n const propertySource = findPropertySource(target, propertyName) || target\n const ownDescriptors = Reflect.getOwnPropertyDescriptor(\n propertySource,\n propertyName\n )\n\n // Respect any custom setters present for this property.\n if (typeof ownDescriptors?.set !== 'undefined') {\n ownDescriptors.set.apply(target, [nextValue])\n return true\n }\n\n // Otherwise, set the property on the source.\n return Reflect.defineProperty(propertySource, propertyName, {\n writable: true,\n enumerable: true,\n configurable: true,\n value: nextValue,\n })\n }\n\n if (typeof setProperty !== 'undefined') {\n return setProperty.call(target, [propertyName, nextValue], next)\n }\n\n return next()\n }\n\n handler.get = function (target, propertyName, receiver) {\n /**\n * @note Using `Reflect.get()` here causes \"TypeError: Illegal invocation\".\n */\n const next = () => target[propertyName as any]\n\n const value =\n typeof getProperty !== 'undefined'\n ? getProperty.call(target, [propertyName, receiver], next)\n : next()\n\n if (typeof value === 'function') {\n return (...args: Array<any>) => {\n const next = value.bind(target, ...args)\n\n if (typeof methodCall !== 'undefined') {\n return methodCall.call(target, [propertyName as any, args], next)\n }\n\n return next()\n }\n }\n\n return value\n }\n\n return handler\n}\n","export function isDomParserSupportedType(\n type: string\n): type is DOMParserSupportedType {\n const supportedTypes: Array<DOMParserSupportedType> = [\n 'application/xhtml+xml',\n 'application/xml',\n 'image/svg+xml',\n 'text/html',\n 'text/xml',\n ]\n return supportedTypes.some((supportedType) => {\n return type.startsWith(supportedType)\n })\n}\n","/**\n * Parses a given string into JSON.\n * Gracefully handles invalid JSON by returning `null`.\n */\nexport function parseJson(data: string): Record<string, unknown> | null {\n try {\n const json = JSON.parse(data)\n return json\n } catch (_) {\n return null\n }\n}\n","import { FetchResponse } from '../../../utils/fetchUtils'\n\n/**\n * Creates a Fetch API `Response` instance from the given\n * `XMLHttpRequest` instance and a response body.\n */\nexport function createResponse(\n request: XMLHttpRequest,\n body: BodyInit | null\n): Response {\n /**\n * Handle XMLHttpRequest responses that must have null as the\n * response body when represented using Fetch API Response.\n * XMLHttpRequest response will always have an empty string\n * as the \"request.response\" in those cases, resulting in an error\n * when constructing a Response instance.\n * @see https://github.com/mswjs/interceptors/issues/379\n */\n const responseBodyOrNull = FetchResponse.isResponseWithBody(request.status)\n ? body\n : null\n\n return new FetchResponse(responseBodyOrNull, {\n url: request.responseURL,\n status: request.status,\n statusText: request.statusText,\n headers: createHeadersFromXMLHttpRequestHeaders(\n request.getAllResponseHeaders()\n ),\n })\n}\n\nfunction createHeadersFromXMLHttpRequestHeaders(headersString: string): Headers {\n const headers = new Headers()\n\n const lines = headersString.split(/[\\r\\n]+/)\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n const [name, ...parts] = line.split(': ')\n const value = parts.join(': ')\n\n headers.append(name, value)\n }\n\n return headers\n}\n","/**\n * Return a total byte length of the given request/response body.\n * If the `Content-Length` header is present, it will be used as the byte length.\n */\nexport async function getBodyByteLength(\n input: Request | Response\n): Promise<number> {\n const explicitContentLength = input.headers.get('content-length')\n\n if (explicitContentLength != null && explicitContentLength !== '') {\n return Number(explicitContentLength)\n }\n\n const buffer = await input.arrayBuffer()\n return buffer.byteLength\n}\n","import { invariant } from 'outvariant'\nimport { isNodeProcess } from 'is-node-process'\nimport type { Logger } from '@open-draft/logger'\nimport { concatArrayBuffer } from './utils/concatArrayBuffer'\nimport { createEvent } from './utils/createEvent'\nimport {\n decodeBuffer,\n encodeBuffer,\n toArrayBuffer,\n} from '../../utils/bufferUtils'\nimport { createProxy } from '../../utils/createProxy'\nimport { isDomParserSupportedType } from './utils/isDomParserSupportedType'\nimport { parseJson } from '../../utils/parseJson'\nimport { createResponse } from './utils/createResponse'\nimport { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'\nimport { createRequestId } from '../../createRequestId'\nimport { getBodyByteLength } from './utils/getBodyByteLength'\nimport { setRawRequest } from '../../getRawRequest'\nimport { FetchRequest } from '../../utils/fetchUtils'\n\nconst kIsRequestHandled = Symbol('kIsRequestHandled')\nconst IS_NODE = isNodeProcess()\nconst kFetchRequest = Symbol('kFetchRequest')\n\n/**\n * An `XMLHttpRequest` instance controller that allows us\n * to handle any given request instance (e.g. responding to it).\n */\nexport class XMLHttpRequestController {\n public request: XMLHttpRequest\n public requestId: string\n public onRequest?: (\n this: XMLHttpRequestController,\n args: {\n request: Request\n requestId: string\n }\n ) => Promise<void>\n public onResponse?: (\n this: XMLHttpRequestController,\n args: {\n response: Response\n isMockedResponse: boolean\n request: Request\n requestId: string\n }\n ) => void;\n\n [kIsRequestHandled]: boolean;\n [kFetchRequest]?: Request\n private method: string = 'GET'\n private url: URL = null as any\n private requestHeaders: Headers\n private responseBuffer: Uint8Array\n private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>\n private uploadEvents: Map<\n keyof XMLHttpRequestEventTargetEventMap,\n Array<Function>\n >\n\n constructor(\n readonly initialRequest: XMLHttpRequest,\n public logger: Logger\n ) {\n this[kIsRequestHandled] = false\n\n this.events = new Map()\n this.uploadEvents = new Map()\n this.requestId = createRequestId()\n this.requestHeaders = new Headers()\n this.responseBuffer = new Uint8Array()\n\n this.request = createProxy(initialRequest, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'ontimeout': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n /**\n * @note Proxy callbacks to event listeners because JSDOM has trouble\n * translating these properties to callbacks. It seemed to be operating\n * on events exclusively.\n */\n this.request.addEventListener(eventName, nextValue as any)\n\n return invoke()\n }\n\n default: {\n return invoke()\n }\n }\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'open': {\n const [method, url] = args as [string, string | undefined]\n\n if (typeof url === 'undefined') {\n this.method = 'GET'\n this.url = toAbsoluteUrl(method)\n } else {\n this.method = method\n this.url = toAbsoluteUrl(url)\n }\n\n this.logger = this.logger.extend(`${this.method} ${this.url.href}`)\n this.logger.info('open', this.method, this.url.href)\n\n return invoke()\n }\n\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n\n this.registerEvent(eventName, listener)\n this.logger.info('addEventListener', eventName, listener)\n\n return invoke()\n }\n\n case 'setRequestHeader': {\n const [name, value] = args as [string, string]\n this.requestHeaders.set(name, value)\n\n this.logger.info('setRequestHeader', name, value)\n\n return invoke()\n }\n\n case 'send': {\n const [body] = args as [\n body?: XMLHttpRequestBodyInit | Document | null,\n ]\n\n this.request.addEventListener('load', () => {\n if (typeof this.onResponse !== 'undefined') {\n // Create a Fetch API Response representation of whichever\n // response this XMLHttpRequest received. Note those may\n // be either a mocked and the original response.\n const fetchResponse = createResponse(\n this.request,\n /**\n * The `response` property is the right way to read\n * the ambiguous response body, as the request's \"responseType\" may differ.\n * @see https://xhr.spec.whatwg.org/#the-response-attribute\n */\n this.request.response\n )\n\n // Notify the consumer about the response.\n this.onResponse.call(this, {\n response: fetchResponse,\n isMockedResponse: this[kIsRequestHandled],\n request: fetchRequest,\n requestId: this.requestId!,\n })\n }\n })\n\n const requestBody =\n typeof body === 'string' ? encodeBuffer(body) : body\n\n // Delegate request handling to the consumer.\n const fetchRequest = this.toFetchApiRequest(requestBody)\n this[kFetchRequest] = fetchRequest.clone()\n\n /**\n * @note Start request handling on the next tick so that the user\n * could add event listeners for \"loadend\" before the interceptor fires it.\n */\n queueMicrotask(() => {\n const onceRequestSettled =\n this.onRequest?.call(this, {\n request: fetchRequest,\n requestId: this.requestId!,\n }) || Promise.resolve()\n\n onceRequestSettled.finally(() => {\n // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.\n if (!this[kIsRequestHandled]) {\n this.logger.info(\n 'request callback settled but request has not been handled (readystate %d), performing as-is...',\n this.request.readyState\n )\n\n /**\n * @note Set the intercepted request ID on the original request in Node.js\n * so that if it triggers any other interceptors, they don't attempt\n * to process it once again.\n *\n * For instance, XMLHttpRequest is often implemented via \"http.ClientRequest\"\n * and we don't want for both XHR and ClientRequest interceptors to\n * handle the same request at the same time (e.g. emit the \"response\" event twice).\n */\n if (IS_NODE) {\n this.request.setRequestHeader(\n INTERNAL_REQUEST_ID_HEADER_NAME,\n this.requestId!\n )\n }\n\n return invoke()\n }\n })\n })\n\n break\n }\n\n default: {\n return invoke()\n }\n }\n },\n })\n\n /**\n * Proxy the `.upload` property to gather the event listeners/callbacks.\n */\n define(\n this.request,\n 'upload',\n createProxy(this.request.upload, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'onloadstart':\n case 'onprogress':\n case 'onaboart':\n case 'onerror':\n case 'onload':\n case 'ontimeout':\n case 'onloadend': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n this.registerUploadEvent(eventName, nextValue as Function)\n }\n }\n\n return invoke()\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n this.registerUploadEvent(eventName, listener)\n this.logger.info('upload.addEventListener', eventName, listener)\n\n return invoke()\n }\n }\n },\n })\n )\n }\n\n private registerEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.events.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.events.set(eventName, nextEvents)\n\n this.logger.info('registered event \"%s\"', eventName, listener)\n }\n\n private registerUploadEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.uploadEvents.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.uploadEvents.set(eventName, nextEvents)\n\n this.logger.info('registered upload event \"%s\"', eventName, listener)\n }\n\n /**\n * Responds to the current request with the given\n * Fetch API `Response` instance.\n */\n public async respondWith(response: Response): Promise<void> {\n /**\n * @note Since `XMLHttpRequestController` delegates the handling of the responses\n * to the \"load\" event listener that doesn't distinguish between the mocked and original\n * responses, mark the request that had a mocked response with a corresponding symbol.\n *\n * Mark this request as having a mocked response immediately since\n * calculating request/response total body length is asynchronous.\n */\n this[kIsRequestHandled] = true\n\n /**\n * Dispatch request upload events for requests with a body.\n * @see https://github.com/mswjs/interceptors/issues/573\n */\n if (this[kFetchRequest]) {\n const totalRequestBodyLength = await getBodyByteLength(\n this[kFetchRequest]\n )\n\n this.trigger('loadstart', this.request.upload, {\n loaded: 0,\n total: totalRequestBodyLength,\n })\n this.trigger('progress', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n this.trigger('load', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n\n this.trigger('loadend', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n }\n\n this.logger.info(\n 'responding with a mocked response: %d %s',\n response.status,\n response.statusText\n )\n\n define(this.request, 'status', response.status)\n define(this.request, 'statusText', response.statusText)\n define(this.request, 'responseURL', this.url.href)\n\n this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {\n apply: (_, __, args: [name: string]) => {\n this.logger.info('getResponseHeader', args[0])\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning null')\n\n // Headers not received yet, nothing to return.\n return null\n }\n\n const headerValue = response.headers.get(args[0])\n this.logger.info(\n 'resolved response header \"%s\" to',\n args[0],\n headerValue\n )\n\n return headerValue\n },\n })\n\n this.request.getAllResponseHeaders = new Proxy(\n this.request.getAllResponseHeaders,\n {\n apply: () => {\n this.logger.info('getAllResponseHeaders')\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning empty string')\n\n // Headers not received yet, nothing to return.\n return ''\n }\n\n const headersList = Array.from(response.headers.entries())\n const allHeaders = headersList\n .map(([headerName, headerValue]) => {\n return `${headerName}: ${headerValue}`\n })\n .join('\\r\\n')\n\n this.logger.info('resolved all response headers to', allHeaders)\n\n return allHeaders\n },\n }\n )\n\n // Update the response getters to resolve against the mocked response.\n Object.defineProperties(this.request, {\n response: {\n enumerable: true,\n configurable: false,\n get: () => this.response,\n },\n responseText: {\n enumerable: true,\n configurable: false,\n get: () => this.responseText,\n },\n responseXML: {\n enumerable: true,\n configurable: false,\n get: () => this.responseXML,\n },\n })\n\n const totalResponseBodyLength = await getBodyByteLength(response.clone())\n\n this.logger.info('calculated response body length', totalResponseBodyLength)\n\n this.trigger('loadstart', this.request, {\n loaded: 0,\n total: totalResponseBodyLength,\n })\n\n this.setReadyState(this.request.HEADERS_RECEIVED)\n this.setReadyState(this.request.LOADING)\n\n const finalizeResponse = () => {\n this.logger.info('finalizing the mocked response...')\n\n this.setReadyState(this.request.DONE)\n\n this.trigger('load', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n\n this.trigger('loadend', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n if (response.body) {\n this.logger.info('mocked response has body, streaming...')\n\n const reader = response.body.getReader()\n\n const readNextResponseBodyChunk = async () => {\n const { value, done } = await reader.read()\n\n if (done) {\n this.logger.info('response body stream done!')\n finalizeResponse()\n return\n }\n\n if (value) {\n this.logger.info('read response body chunk:', value)\n this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)\n\n this.trigger('progress', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n readNextResponseBodyChunk()\n }\n\n readNextResponseBodyChunk()\n } else {\n finalizeResponse()\n }\n }\n\n private responseBufferToText(): string {\n return decodeBuffer(this.responseBuffer)\n }\n\n get response(): unknown {\n this.logger.info(\n 'getResponse (responseType: %s)',\n this.request.responseType\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n switch (this.request.responseType) {\n case 'json': {\n const responseJson = parseJson(this.responseBufferToText())\n this.logger.info('resolved response JSON', responseJson)\n\n return responseJson\n }\n\n case 'arraybuffer': {\n const arrayBuffer = toArrayBuffer(this.responseBuffer)\n this.logger.info('resolved response ArrayBuffer', arrayBuffer)\n\n return arrayBuffer\n }\n\n case 'blob': {\n const mimeType =\n this.request.getResponseHeader('Content-Type') || 'text/plain'\n const responseBlob = new Blob([this.responseBufferToText()], {\n type: mimeType,\n })\n\n this.logger.info(\n 'resolved response Blob (mime type: %s)',\n responseBlob,\n mimeType\n )\n\n return responseBlob\n }\n\n default: {\n const responseText = this.responseBufferToText()\n this.logger.info(\n 'resolving \"%s\" response type as text',\n this.request.responseType,\n responseText\n )\n\n return responseText\n }\n }\n }\n\n get responseText(): string {\n /**\n * Throw when trying to read the response body as text when the\n * \"responseType\" doesn't expect text. This just respects the spec better.\n * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute\n */\n invariant(\n this.request.responseType === '' || this.request.responseType === 'text',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (\n this.request.readyState !== this.request.LOADING &&\n this.request.readyState !== this.request.DONE\n ) {\n return ''\n }\n\n const responseText = this.responseBufferToText()\n this.logger.info('getResponseText: \"%s\"', responseText)\n\n return responseText\n }\n\n get responseXML(): Document | null {\n invariant(\n this.request.responseType === '' ||\n this.request.responseType === 'document',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n const contentType = this.request.getResponseHeader('Content-Type') || ''\n\n if (typeof DOMParser === 'undefined') {\n console.warn(\n 'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'\n )\n return null\n }\n\n if (isDomParserSupportedType(contentType)) {\n return new DOMParser().parseFromString(\n this.responseBufferToText(),\n contentType\n )\n }\n\n return null\n }\n\n public errorWith(error?: Error): void {\n /**\n * @note Mark this request as handled even if it received a mock error.\n * This prevents the controller from trying to perform this request as-is.\n */\n this[kIsRequestHandled] = true\n this.logger.info('responding with an error')\n\n this.setReadyState(this.request.DONE)\n this.trigger('error', this.request)\n this.trigger('loadend', this.request)\n }\n\n /**\n * Transitions this request's `readyState` to the given one.\n */\n private setReadyState(nextReadyState: number): void {\n this.logger.info(\n 'setReadyState: %d -> %d',\n this.request.readyState,\n nextReadyState\n )\n\n if (this.request.readyState === nextReadyState) {\n this.logger.info('ready state identical, skipping transition...')\n return\n }\n\n define(this.request, 'readyState', nextReadyState)\n\n this.logger.info('set readyState to: %d', nextReadyState)\n\n if (nextReadyState !== this.request.UNSENT) {\n this.logger.info('triggering \"readystatechange\" event...')\n\n this.trigger('readystatechange', this.request)\n }\n }\n\n /**\n * Triggers given event on the `XMLHttpRequest` instance.\n */\n private trigger<\n EventName extends keyof (XMLHttpRequestEventTargetEventMap & {\n readystatechange: ProgressEvent<XMLHttpRequestEventTarget>\n }),\n >(\n eventName: EventName,\n target: XMLHttpRequest | XMLHttpRequestUpload,\n options?: ProgressEventInit\n ): void {\n const callback = (target as XMLHttpRequest)[`on${eventName}`]\n const event = createEvent(target, eventName, options)\n\n this.logger.info('trigger \"%s\"', eventName, options || '')\n\n // Invoke direct callbacks.\n if (typeof callback === 'function') {\n this.logger.info('found a direct \"%s\" callback, calling...', eventName)\n callback.call(target as XMLHttpRequest, event)\n }\n\n // Invoke event listeners.\n const events =\n target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events\n\n for (const [registeredEventName, listeners] of events) {\n if (registeredEventName === eventName) {\n this.logger.info(\n 'found %d listener(s) for \"%s\" event, calling...',\n listeners.length,\n eventName\n )\n\n listeners.forEach((listener) => listener.call(target, event))\n }\n }\n }\n\n /**\n * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.\n */\n private toFetchApiRequest(\n body: XMLHttpRequestBodyInit | Document | null | undefined\n ): Request {\n this.logger.info('converting request to a Fetch API Request...')\n\n // If the `Document` is used as the body of this XMLHttpRequest,\n // set its inner text as the Fetch API Request body.\n const resolvedBody =\n body instanceof Document ? body.documentElement.innerText : body\n\n const fetchRequest = new FetchRequest(this.url.href, {\n method: this.method,\n headers: this.requestHeaders,\n /**\n * @see https://xhr.spec.whatwg.org/#cross-origin-credentials\n */\n credentials: this.request.withCredentials ? 'include' : 'same-origin',\n body: ['GET', 'HEAD'].includes(this.method.toUpperCase())\n ? null\n : resolvedBody,\n })\n\n const proxyHeaders = createProxy(fetchRequest.headers, {\n methodCall: ([methodName, args], invoke) => {\n // Forward the latest state of the internal request headers\n // because the interceptor might have modified them\n // without responding to the request.\n switch (methodName) {\n case 'append':\n case 'set': {\n const [headerName, headerValue] = args as [string, string]\n this.request.setRequestHeader(headerName, headerValue)\n break\n }\n\n case 'delete': {\n const [headerName] = args as [string]\n console.warn(\n `XMLHttpRequest: Cannot remove a \"${headerName}\" header from the Fetch API representation of the \"${fetchRequest.method} ${fetchRequest.url}\" request. XMLHttpRequest headers cannot be removed.`\n )\n break\n }\n }\n\n return invoke()\n },\n })\n define(fetchRequest, 'headers', proxyHeaders)\n setRawRequest(fetchRequest, this.request)\n\n this.logger.info('converted request to a Fetch API Request!', fetchRequest)\n\n return fetchRequest\n }\n}\n\nfunction toAbsoluteUrl(url: string | URL): URL {\n /**\n * @note XMLHttpRequest interceptor may run in environments\n * that implement XMLHttpRequest but don't implement \"location\"\n * (for example, React Native). If that's the case, return the\n * input URL as-is (nothing to be relative to).\n * @see https://github.com/mswjs/msw/issues/1777\n */\n if (typeof location === 'undefined') {\n return new URL(url)\n }\n\n return new URL(url.toString(), location.href)\n}\n\nfunction define(\n target: object,\n property: string | symbol,\n value: unknown\n): void {\n Reflect.defineProperty(target, property, {\n // Ensure writable properties to allow redefining readonly properties.\n writable: true,\n enumerable: true,\n value,\n })\n}\n","import type { Logger } from '@open-draft/logger'\nimport { XMLHttpRequestEmitter } from '.'\nimport { RequestController } from '../../RequestController'\nimport { XMLHttpRequestController } from './XMLHttpRequestController'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { isResponseError } from '../../utils/responseUtils'\n\nexport interface XMLHttpRequestProxyOptions {\n emitter: XMLHttpRequestEmitter\n logger: Logger\n}\n\n/**\n * Create a proxied `XMLHttpRequest` class.\n * The proxied class establishes spies on certain methods,\n * allowing us to intercept requests and respond to them.\n */\nexport function createXMLHttpRequestProxy({\n emitter,\n logger,\n}: XMLHttpRequestProxyOptions) {\n const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {\n construct(target, args, newTarget) {\n logger.info('constructed new XMLHttpRequest')\n\n const originalRequest = Reflect.construct(\n target,\n args,\n newTarget\n ) as XMLHttpRequest\n\n /**\n * @note Forward prototype descriptors onto the proxied object.\n * XMLHttpRequest is implemented in JSDOM in a way that assigns\n * a bunch of descriptors, like \"set responseType()\" on the prototype.\n * With this propagation, we make sure that those descriptors trigger\n * when the user operates with the proxied request instance.\n */\n const prototypeDescriptors = Object.getOwnPropertyDescriptors(\n target.prototype\n )\n for (const propertyName in prototypeDescriptors) {\n Reflect.defineProperty(\n originalRequest,\n propertyName,\n prototypeDescriptors[propertyName]\n )\n }\n\n const xhrRequestController = new XMLHttpRequestController(\n originalRequest,\n logger\n )\n\n xhrRequestController.onRequest = async function ({ request, requestId }) {\n const controller = new RequestController(request, {\n passthrough: () => {\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n },\n respondWith: async (response) => {\n if (isResponseError(response)) {\n this.errorWith(new TypeError('Network error'))\n return\n }\n\n await this.respondWith(response)\n },\n errorWith: (reason) => {\n this.logger.info('request errored!', { error: reason })\n\n if (reason instanceof Error) {\n this.errorWith(reason)\n }\n },\n })\n\n this.logger.info('awaiting mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n controller,\n emitter,\n })\n }\n\n xhrRequestController.onResponse = async function ({\n response,\n isMockedResponse,\n request,\n requestId,\n }) {\n this.logger.info(\n 'emitting the \"response\" event for %s listener(s)...',\n emitter.listenerCount('response')\n )\n\n emitter.emit('response', {\n response,\n isMockedResponse,\n request,\n requestId,\n })\n }\n\n // Return the proxied request from the controller\n // so that the controller can react to the consumer's interactions\n // with this request (opening/sending/etc).\n return xhrRequestController.request\n },\n })\n\n return XMLHttpRequestProxy\n}\n","import { Emitter } from 'strict-event-emitter'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>\n\nexport class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {\n static interceptorSymbol = Symbol('xhr')\n\n constructor() {\n super(XMLHttpRequestInterceptor.interceptorSymbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('XMLHttpRequest')\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('patching global XMLHttpRequest...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'XMLHttpRequest', () => {\n return createXMLHttpRequestProxy({\n emitter: this.emitter,\n logger: this.logger,\n })\n })\n )\n\n logger.info(\n 'global XMLHttpRequest patched!',\n globalThis.XMLHttpRequest.name\n )\n }\n}\n"],"mappings":"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"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-umV5xXBy.cjs'); | ||
| const require_bufferUtils = require('./bufferUtils-S5_-2eN4.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-B6znU6il.cjs'); | ||
| const require_handleRequest = require('./handleRequest-DVOthWJo.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-BtHi5OlL.cjs'); | ||
| let outvariant = require("outvariant"); | ||
| let is_node_process = require("is-node-process"); | ||
| //#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts | ||
| /** | ||
| * Concatenate two `Uint8Array` buffers. | ||
| */ | ||
| function concatArrayBuffer(left, right) { | ||
| const result = new Uint8Array(left.byteLength + right.byteLength); | ||
| result.set(left, 0); | ||
| result.set(right, left.byteLength); | ||
| return result; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts | ||
| var EventPolyfill = class { | ||
| constructor(type, options) { | ||
| this.NONE = 0; | ||
| this.CAPTURING_PHASE = 1; | ||
| this.AT_TARGET = 2; | ||
| this.BUBBLING_PHASE = 3; | ||
| this.type = ""; | ||
| this.srcElement = null; | ||
| this.currentTarget = null; | ||
| this.eventPhase = 0; | ||
| this.isTrusted = true; | ||
| this.composed = false; | ||
| this.cancelable = true; | ||
| this.defaultPrevented = false; | ||
| this.bubbles = true; | ||
| this.lengthComputable = true; | ||
| this.loaded = 0; | ||
| this.total = 0; | ||
| this.cancelBubble = false; | ||
| this.returnValue = true; | ||
| this.type = type; | ||
| this.target = options?.target || null; | ||
| this.currentTarget = options?.currentTarget || null; | ||
| this.timeStamp = Date.now(); | ||
| } | ||
| composedPath() { | ||
| return []; | ||
| } | ||
| initEvent(type, bubbles, cancelable) { | ||
| this.type = type; | ||
| this.bubbles = !!bubbles; | ||
| this.cancelable = !!cancelable; | ||
| } | ||
| preventDefault() { | ||
| this.defaultPrevented = true; | ||
| } | ||
| stopPropagation() {} | ||
| stopImmediatePropagation() {} | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts | ||
| var ProgressEventPolyfill = class extends EventPolyfill { | ||
| constructor(type, init) { | ||
| super(type); | ||
| this.lengthComputable = init?.lengthComputable || false; | ||
| this.composed = init?.composed || false; | ||
| this.loaded = init?.loaded || 0; | ||
| this.total = init?.total || 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createEvent.ts | ||
| const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined"; | ||
| function createEvent(target, type, init) { | ||
| const progressEvents = [ | ||
| "error", | ||
| "progress", | ||
| "loadstart", | ||
| "loadend", | ||
| "load", | ||
| "timeout", | ||
| "abort" | ||
| ]; | ||
| /** | ||
| * `ProgressEvent` is not supported in React Native. | ||
| * @see https://github.com/mswjs/interceptors/issues/40 | ||
| */ | ||
| const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill; | ||
| return progressEvents.includes(type) ? new ProgressEventClass(type, { | ||
| lengthComputable: true, | ||
| loaded: init?.loaded || 0, | ||
| total: init?.total || 0 | ||
| }) : new EventPolyfill(type, { | ||
| target, | ||
| currentTarget: target | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/findPropertySource.ts | ||
| /** | ||
| * Returns the source object of the given property on the target object | ||
| * (the target itself, any parent in its prototype, or null). | ||
| */ | ||
| function findPropertySource(target, propertyName) { | ||
| if (!(propertyName in target)) return null; | ||
| if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target; | ||
| const prototype = Reflect.getPrototypeOf(target); | ||
| return prototype ? findPropertySource(prototype, propertyName) : null; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/createProxy.ts | ||
| function createProxy(target, options) { | ||
| return new Proxy(target, optionsToProxyHandler(options)); | ||
| } | ||
| function optionsToProxyHandler(options) { | ||
| const { constructorCall, methodCall, getProperty, setProperty } = options; | ||
| const handler = {}; | ||
| if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) { | ||
| const next = Reflect.construct.bind(null, target, args, newTarget); | ||
| return constructorCall.call(newTarget, args, next); | ||
| }; | ||
| handler.set = function(target, propertyName, nextValue) { | ||
| const next = () => { | ||
| const propertySource = findPropertySource(target, propertyName) || target; | ||
| const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName); | ||
| if (typeof ownDescriptors?.set !== "undefined") { | ||
| ownDescriptors.set.apply(target, [nextValue]); | ||
| return true; | ||
| } | ||
| return Reflect.defineProperty(propertySource, propertyName, { | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| value: nextValue | ||
| }); | ||
| }; | ||
| if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next); | ||
| return next(); | ||
| }; | ||
| handler.get = function(target, propertyName, receiver) { | ||
| /** | ||
| * @note Using `Reflect.get()` here causes "TypeError: Illegal invocation". | ||
| */ | ||
| const next = () => target[propertyName]; | ||
| const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next(); | ||
| if (typeof value === "function") return (...args) => { | ||
| const next$1 = value.bind(target, ...args); | ||
| if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1); | ||
| return next$1(); | ||
| }; | ||
| return value; | ||
| }; | ||
| return handler; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts | ||
| function isDomParserSupportedType(type) { | ||
| return [ | ||
| "application/xhtml+xml", | ||
| "application/xml", | ||
| "image/svg+xml", | ||
| "text/html", | ||
| "text/xml" | ||
| ].some((supportedType) => { | ||
| return type.startsWith(supportedType); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/parseJson.ts | ||
| /** | ||
| * Parses a given string into JSON. | ||
| * Gracefully handles invalid JSON by returning `null`. | ||
| */ | ||
| function parseJson(data) { | ||
| try { | ||
| return JSON.parse(data); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createResponse.ts | ||
| /** | ||
| * Creates a Fetch API `Response` instance from the given | ||
| * `XMLHttpRequest` instance and a response body. | ||
| */ | ||
| function createResponse(request, body) { | ||
| return new require_fetchUtils.FetchResponse(require_fetchUtils.FetchResponse.isResponseWithBody(request.status) ? body : null, { | ||
| url: request.responseURL, | ||
| status: request.status, | ||
| statusText: request.statusText, | ||
| headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders()) | ||
| }); | ||
| } | ||
| function createHeadersFromXMLHttpRequestHeaders(headersString) { | ||
| const headers = new Headers(); | ||
| const lines = headersString.split(/[\r\n]+/); | ||
| for (const line of lines) { | ||
| if (line.trim() === "") continue; | ||
| const [name, ...parts] = line.split(": "); | ||
| const value = parts.join(": "); | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts | ||
| /** | ||
| * Return a total byte length of the given request/response body. | ||
| * If the `Content-Length` header is present, it will be used as the byte length. | ||
| */ | ||
| async function getBodyByteLength(input) { | ||
| const explicitContentLength = input.headers.get("content-length"); | ||
| if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength); | ||
| return (await input.arrayBuffer()).byteLength; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts | ||
| const kIsRequestHandled = Symbol("kIsRequestHandled"); | ||
| const IS_NODE = (0, is_node_process.isNodeProcess)(); | ||
| const kFetchRequest = Symbol("kFetchRequest"); | ||
| /** | ||
| * An `XMLHttpRequest` instance controller that allows us | ||
| * to handle any given request instance (e.g. responding to it). | ||
| */ | ||
| var XMLHttpRequestController = class { | ||
| constructor(initialRequest, logger) { | ||
| this.initialRequest = initialRequest; | ||
| this.logger = logger; | ||
| this.method = "GET"; | ||
| this.url = null; | ||
| this[kIsRequestHandled] = false; | ||
| this.events = /* @__PURE__ */ new Map(); | ||
| this.uploadEvents = /* @__PURE__ */ new Map(); | ||
| this.requestId = require_fetchUtils.createRequestId(); | ||
| this.requestHeaders = new Headers(); | ||
| this.responseBuffer = new Uint8Array(); | ||
| this.request = createProxy(initialRequest, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "ontimeout": { | ||
| const eventName = propertyName.slice(2); | ||
| /** | ||
| * @note Proxy callbacks to event listeners because JSDOM has trouble | ||
| * translating these properties to callbacks. It seemed to be operating | ||
| * on events exclusively. | ||
| */ | ||
| this.request.addEventListener(eventName, nextValue); | ||
| return invoke(); | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "open": { | ||
| const [method, url] = args; | ||
| if (typeof url === "undefined") { | ||
| this.method = "GET"; | ||
| this.url = toAbsoluteUrl(method); | ||
| } else { | ||
| this.method = method; | ||
| this.url = toAbsoluteUrl(url); | ||
| } | ||
| this.logger = this.logger.extend(`${this.method} ${this.url.href}`); | ||
| this.logger.info("open", this.method, this.url.href); | ||
| return invoke(); | ||
| } | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerEvent(eventName, listener); | ||
| this.logger.info("addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| case "setRequestHeader": { | ||
| const [name, value] = args; | ||
| this.requestHeaders.set(name, value); | ||
| this.logger.info("setRequestHeader", name, value); | ||
| return invoke(); | ||
| } | ||
| case "send": { | ||
| const [body] = args; | ||
| this.request.addEventListener("load", () => { | ||
| if (typeof this.onResponse !== "undefined") { | ||
| const fetchResponse = createResponse( | ||
| this.request, | ||
| /** | ||
| * The `response` property is the right way to read | ||
| * the ambiguous response body, as the request's "responseType" may differ. | ||
| * @see https://xhr.spec.whatwg.org/#the-response-attribute | ||
| */ | ||
| this.request.response | ||
| ); | ||
| this.onResponse.call(this, { | ||
| response: fetchResponse, | ||
| isMockedResponse: this[kIsRequestHandled], | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }); | ||
| } | ||
| }); | ||
| const requestBody = typeof body === "string" ? require_bufferUtils.encodeBuffer(body) : body; | ||
| const fetchRequest = this.toFetchApiRequest(requestBody); | ||
| this[kFetchRequest] = fetchRequest.clone(); | ||
| /** | ||
| * @note Start request handling on the next tick so that the user | ||
| * could add event listeners for "loadend" before the interceptor fires it. | ||
| */ | ||
| queueMicrotask(() => { | ||
| (this.onRequest?.call(this, { | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }) || Promise.resolve()).finally(() => { | ||
| if (!this[kIsRequestHandled]) { | ||
| this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState); | ||
| /** | ||
| * @note Set the intercepted request ID on the original request in Node.js | ||
| * so that if it triggers any other interceptors, they don't attempt | ||
| * to process it once again. | ||
| * | ||
| * For instance, XMLHttpRequest is often implemented via "http.ClientRequest" | ||
| * and we don't want for both XHR and ClientRequest interceptors to | ||
| * handle the same request at the same time (e.g. emit the "response" event twice). | ||
| */ | ||
| if (IS_NODE) this.request.setRequestHeader(require_fetchUtils.INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId); | ||
| return invoke(); | ||
| } | ||
| }); | ||
| }); | ||
| break; | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| } | ||
| }); | ||
| /** | ||
| * Proxy the `.upload` property to gather the event listeners/callbacks. | ||
| */ | ||
| define(this.request, "upload", createProxy(this.request.upload, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "onloadstart": | ||
| case "onprogress": | ||
| case "onaboart": | ||
| case "onerror": | ||
| case "onload": | ||
| case "ontimeout": | ||
| case "onloadend": { | ||
| const eventName = propertyName.slice(2); | ||
| this.registerUploadEvent(eventName, nextValue); | ||
| } | ||
| } | ||
| return invoke(); | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerUploadEvent(eventName, listener); | ||
| this.logger.info("upload.addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| } | ||
| } | ||
| })); | ||
| } | ||
| registerEvent(eventName, listener) { | ||
| const nextEvents = (this.events.get(eventName) || []).concat(listener); | ||
| this.events.set(eventName, nextEvents); | ||
| this.logger.info("registered event \"%s\"", eventName, listener); | ||
| } | ||
| registerUploadEvent(eventName, listener) { | ||
| const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener); | ||
| this.uploadEvents.set(eventName, nextEvents); | ||
| this.logger.info("registered upload event \"%s\"", eventName, listener); | ||
| } | ||
| /** | ||
| * Responds to the current request with the given | ||
| * Fetch API `Response` instance. | ||
| */ | ||
| async respondWith(response) { | ||
| /** | ||
| * @note Since `XMLHttpRequestController` delegates the handling of the responses | ||
| * to the "load" event listener that doesn't distinguish between the mocked and original | ||
| * responses, mark the request that had a mocked response with a corresponding symbol. | ||
| * | ||
| * Mark this request as having a mocked response immediately since | ||
| * calculating request/response total body length is asynchronous. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| /** | ||
| * Dispatch request upload events for requests with a body. | ||
| * @see https://github.com/mswjs/interceptors/issues/573 | ||
| */ | ||
| if (this[kFetchRequest]) { | ||
| const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]); | ||
| this.trigger("loadstart", this.request.upload, { | ||
| loaded: 0, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("progress", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("load", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| } | ||
| this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText); | ||
| define(this.request, "status", response.status); | ||
| define(this.request, "statusText", response.statusText); | ||
| define(this.request, "responseURL", this.url.href); | ||
| this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => { | ||
| this.logger.info("getResponseHeader", args[0]); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning null"); | ||
| return null; | ||
| } | ||
| const headerValue = response.headers.get(args[0]); | ||
| this.logger.info("resolved response header \"%s\" to", args[0], headerValue); | ||
| return headerValue; | ||
| } }); | ||
| this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => { | ||
| this.logger.info("getAllResponseHeaders"); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning empty string"); | ||
| return ""; | ||
| } | ||
| const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => { | ||
| return `${headerName}: ${headerValue}`; | ||
| }).join("\r\n"); | ||
| this.logger.info("resolved all response headers to", allHeaders); | ||
| return allHeaders; | ||
| } }); | ||
| Object.defineProperties(this.request, { | ||
| response: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.response | ||
| }, | ||
| responseText: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseText | ||
| }, | ||
| responseXML: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseXML | ||
| } | ||
| }); | ||
| const totalResponseBodyLength = await getBodyByteLength(response.clone()); | ||
| this.logger.info("calculated response body length", totalResponseBodyLength); | ||
| this.trigger("loadstart", this.request, { | ||
| loaded: 0, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.setReadyState(this.request.HEADERS_RECEIVED); | ||
| this.setReadyState(this.request.LOADING); | ||
| const finalizeResponse = () => { | ||
| this.logger.info("finalizing the mocked response..."); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("load", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| }; | ||
| if (response.body) { | ||
| this.logger.info("mocked response has body, streaming..."); | ||
| const reader = response.body.getReader(); | ||
| const readNextResponseBodyChunk = async () => { | ||
| const { value, done } = await reader.read(); | ||
| if (done) { | ||
| this.logger.info("response body stream done!"); | ||
| finalizeResponse(); | ||
| return; | ||
| } | ||
| if (value) { | ||
| this.logger.info("read response body chunk:", value); | ||
| this.responseBuffer = concatArrayBuffer(this.responseBuffer, value); | ||
| this.trigger("progress", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| } | ||
| readNextResponseBodyChunk(); | ||
| }; | ||
| readNextResponseBodyChunk(); | ||
| } else finalizeResponse(); | ||
| } | ||
| responseBufferToText() { | ||
| return require_bufferUtils.decodeBuffer(this.responseBuffer); | ||
| } | ||
| get response() { | ||
| this.logger.info("getResponse (responseType: %s)", this.request.responseType); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| switch (this.request.responseType) { | ||
| case "json": { | ||
| const responseJson = parseJson(this.responseBufferToText()); | ||
| this.logger.info("resolved response JSON", responseJson); | ||
| return responseJson; | ||
| } | ||
| case "arraybuffer": { | ||
| const arrayBuffer = require_bufferUtils.toArrayBuffer(this.responseBuffer); | ||
| this.logger.info("resolved response ArrayBuffer", arrayBuffer); | ||
| return arrayBuffer; | ||
| } | ||
| case "blob": { | ||
| const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain"; | ||
| const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType }); | ||
| this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType); | ||
| return responseBlob; | ||
| } | ||
| default: { | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText); | ||
| return responseText; | ||
| } | ||
| } | ||
| } | ||
| get responseText() { | ||
| /** | ||
| * Throw when trying to read the response body as text when the | ||
| * "responseType" doesn't expect text. This just respects the spec better. | ||
| * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute | ||
| */ | ||
| (0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return ""; | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("getResponseText: \"%s\"", responseText); | ||
| return responseText; | ||
| } | ||
| get responseXML() { | ||
| (0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| const contentType = this.request.getResponseHeader("Content-Type") || ""; | ||
| if (typeof DOMParser === "undefined") { | ||
| console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly."); | ||
| return null; | ||
| } | ||
| if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType); | ||
| return null; | ||
| } | ||
| errorWith(error) { | ||
| /** | ||
| * @note Mark this request as handled even if it received a mock error. | ||
| * This prevents the controller from trying to perform this request as-is. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| this.logger.info("responding with an error"); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("error", this.request); | ||
| this.trigger("loadend", this.request); | ||
| } | ||
| /** | ||
| * Transitions this request's `readyState` to the given one. | ||
| */ | ||
| setReadyState(nextReadyState) { | ||
| this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState); | ||
| if (this.request.readyState === nextReadyState) { | ||
| this.logger.info("ready state identical, skipping transition..."); | ||
| return; | ||
| } | ||
| define(this.request, "readyState", nextReadyState); | ||
| this.logger.info("set readyState to: %d", nextReadyState); | ||
| if (nextReadyState !== this.request.UNSENT) { | ||
| this.logger.info("triggering \"readystatechange\" event..."); | ||
| this.trigger("readystatechange", this.request); | ||
| } | ||
| } | ||
| /** | ||
| * Triggers given event on the `XMLHttpRequest` instance. | ||
| */ | ||
| trigger(eventName, target, options) { | ||
| const callback = target[`on${eventName}`]; | ||
| const event = createEvent(target, eventName, options); | ||
| this.logger.info("trigger \"%s\"", eventName, options || ""); | ||
| if (typeof callback === "function") { | ||
| this.logger.info("found a direct \"%s\" callback, calling...", eventName); | ||
| callback.call(target, event); | ||
| } | ||
| const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events; | ||
| for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) { | ||
| this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName); | ||
| listeners.forEach((listener) => listener.call(target, event)); | ||
| } | ||
| } | ||
| /** | ||
| * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance. | ||
| */ | ||
| toFetchApiRequest(body) { | ||
| this.logger.info("converting request to a Fetch API Request..."); | ||
| const resolvedBody = body instanceof Document ? body.documentElement.innerText : body; | ||
| const fetchRequest = new require_fetchUtils.FetchRequest(this.url.href, { | ||
| method: this.method, | ||
| headers: this.requestHeaders, | ||
| credentials: this.request.withCredentials ? "include" : "same-origin", | ||
| body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody | ||
| }); | ||
| define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "append": | ||
| case "set": { | ||
| const [headerName, headerValue] = args; | ||
| this.request.setRequestHeader(headerName, headerValue); | ||
| break; | ||
| } | ||
| case "delete": { | ||
| const [headerName] = args; | ||
| console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`); | ||
| break; | ||
| } | ||
| } | ||
| return invoke(); | ||
| } })); | ||
| require_getRawRequest.setRawRequest(fetchRequest, this.request); | ||
| this.logger.info("converted request to a Fetch API Request!", fetchRequest); | ||
| return fetchRequest; | ||
| } | ||
| }; | ||
| function toAbsoluteUrl(url) { | ||
| /** | ||
| * @note XMLHttpRequest interceptor may run in environments | ||
| * that implement XMLHttpRequest but don't implement "location" | ||
| * (for example, React Native). If that's the case, return the | ||
| * input URL as-is (nothing to be relative to). | ||
| * @see https://github.com/mswjs/msw/issues/1777 | ||
| */ | ||
| if (typeof location === "undefined") return new URL(url); | ||
| return new URL(url.toString(), location.href); | ||
| } | ||
| function define(target, property, value) { | ||
| Reflect.defineProperty(target, property, { | ||
| writable: true, | ||
| enumerable: true, | ||
| value | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts | ||
| /** | ||
| * Create a proxied `XMLHttpRequest` class. | ||
| * The proxied class establishes spies on certain methods, | ||
| * allowing us to intercept requests and respond to them. | ||
| */ | ||
| function createXMLHttpRequestProxy({ emitter, logger }) { | ||
| return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) { | ||
| logger.info("constructed new XMLHttpRequest"); | ||
| const originalRequest = Reflect.construct(target, args, newTarget); | ||
| /** | ||
| * @note Forward prototype descriptors onto the proxied object. | ||
| * XMLHttpRequest is implemented in JSDOM in a way that assigns | ||
| * a bunch of descriptors, like "set responseType()" on the prototype. | ||
| * With this propagation, we make sure that those descriptors trigger | ||
| * when the user operates with the proxied request instance. | ||
| */ | ||
| const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype); | ||
| for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]); | ||
| const xhrRequestController = new XMLHttpRequestController(originalRequest, logger); | ||
| xhrRequestController.onRequest = async function({ request, requestId }) { | ||
| const controller = new require_fetchUtils.RequestController(request, { | ||
| passthrough: () => { | ||
| this.logger.info("no mocked response received, performing request as-is..."); | ||
| }, | ||
| respondWith: async (response) => { | ||
| if (require_handleRequest.isResponseError(response)) { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| await this.respondWith(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request errored!", { error: reason }); | ||
| if (reason instanceof Error) this.errorWith(reason); | ||
| } | ||
| }); | ||
| this.logger.info("awaiting mocked response..."); | ||
| this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request")); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId, | ||
| controller, | ||
| emitter | ||
| }); | ||
| }; | ||
| xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) { | ||
| this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response")); | ||
| emitter.emit("response", { | ||
| response, | ||
| isMockedResponse, | ||
| request, | ||
| requestId | ||
| }); | ||
| }; | ||
| return xhrRequestController.request; | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/index.ts | ||
| var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends require_fetchUtils.Interceptor { | ||
| static { | ||
| this.interceptorSymbol = Symbol("xhr"); | ||
| } | ||
| constructor() { | ||
| super(XMLHttpRequestInterceptor.interceptorSymbol); | ||
| } | ||
| checkEnvironment() { | ||
| return require_hasConfigurableGlobal.hasConfigurableGlobal("XMLHttpRequest"); | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("patching global XMLHttpRequest..."); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.patchesRegistry.applyPatch(globalThis, "XMLHttpRequest", () => { | ||
| return createXMLHttpRequestProxy({ | ||
| emitter: this.emitter, | ||
| logger: this.logger | ||
| }); | ||
| })); | ||
| logger.info("global XMLHttpRequest patched!", globalThis.XMLHttpRequest.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'XMLHttpRequestInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return XMLHttpRequestInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=XMLHttpRequest-CUbLHiq6.cjs.map |
| {"version":3,"file":"XMLHttpRequest-CUbLHiq6.cjs","names":["handler: ProxyHandler<T>","next","FetchResponse","initialRequest: XMLHttpRequest","logger: Logger","createRequestId","encodeBuffer","INTERNAL_REQUEST_ID_HEADER_NAME","decodeBuffer","toArrayBuffer","FetchRequest","RequestController","isResponseError","handleRequest","Interceptor","hasConfigurableGlobal","patchesRegistry"],"sources":["../../src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts","../../src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts","../../src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts","../../src/interceptors/XMLHttpRequest/utils/createEvent.ts","../../src/utils/findPropertySource.ts","../../src/utils/createProxy.ts","../../src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts","../../src/utils/parseJson.ts","../../src/interceptors/XMLHttpRequest/utils/createResponse.ts","../../src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts","../../src/interceptors/XMLHttpRequest/index.ts"],"sourcesContent":["/**\n * Concatenate two `Uint8Array` buffers.\n */\nexport function concatArrayBuffer(\n left: Uint8Array,\n right: Uint8Array\n): Uint8Array {\n const result = new Uint8Array(left.byteLength + right.byteLength)\n result.set(left, 0)\n result.set(right, left.byteLength)\n return result\n}\n","export class EventPolyfill implements Event {\n readonly NONE = 0\n readonly CAPTURING_PHASE = 1\n readonly AT_TARGET = 2\n readonly BUBBLING_PHASE = 3\n\n public type: string = ''\n public srcElement: EventTarget | null = null\n public target: EventTarget | null\n public currentTarget: EventTarget | null = null\n public eventPhase: number = 0\n public timeStamp: number\n public isTrusted: boolean = true\n public composed: boolean = false\n public cancelable: boolean = true\n public defaultPrevented: boolean = false\n public bubbles: boolean = true\n public lengthComputable: boolean = true\n public loaded: number = 0\n public total: number = 0\n\n cancelBubble: boolean = false\n returnValue: boolean = true\n\n constructor(\n type: string,\n options?: { target: EventTarget; currentTarget: EventTarget }\n ) {\n this.type = type\n this.target = options?.target || null\n this.currentTarget = options?.currentTarget || null\n this.timeStamp = Date.now()\n }\n\n public composedPath(): EventTarget[] {\n return []\n }\n\n public initEvent(type: string, bubbles?: boolean, cancelable?: boolean) {\n this.type = type\n this.bubbles = !!bubbles\n this.cancelable = !!cancelable\n }\n\n public preventDefault() {\n this.defaultPrevented = true\n }\n\n public stopPropagation() {}\n public stopImmediatePropagation() {}\n}\n","import { EventPolyfill } from './EventPolyfill'\n\nexport class ProgressEventPolyfill extends EventPolyfill {\n readonly lengthComputable: boolean\n readonly composed: boolean\n readonly loaded: number\n readonly total: number\n\n constructor(type: string, init?: ProgressEventInit) {\n super(type)\n\n this.lengthComputable = init?.lengthComputable || false\n this.composed = init?.composed || false\n this.loaded = init?.loaded || 0\n this.total = init?.total || 0\n }\n}\n","import { EventPolyfill } from '../polyfills/EventPolyfill'\nimport { ProgressEventPolyfill } from '../polyfills/ProgressEventPolyfill'\n\nconst SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined'\n\nexport function createEvent(\n target: XMLHttpRequest | XMLHttpRequestUpload,\n type: string,\n init?: ProgressEventInit\n): EventPolyfill | ProgressEvent {\n const progressEvents = [\n 'error',\n 'progress',\n 'loadstart',\n 'loadend',\n 'load',\n 'timeout',\n 'abort',\n ]\n\n /**\n * `ProgressEvent` is not supported in React Native.\n * @see https://github.com/mswjs/interceptors/issues/40\n */\n const ProgressEventClass = SUPPORTS_PROGRESS_EVENT\n ? ProgressEvent\n : ProgressEventPolyfill\n\n const event = progressEvents.includes(type)\n ? new ProgressEventClass(type, {\n lengthComputable: true,\n loaded: init?.loaded || 0,\n total: init?.total || 0,\n })\n : new EventPolyfill(type, {\n target,\n currentTarget: target,\n })\n\n return event\n}\n","/**\n * Returns the source object of the given property on the target object\n * (the target itself, any parent in its prototype, or null).\n */\nexport function findPropertySource(\n target: object,\n propertyName: string | symbol\n): object | null {\n if (!(propertyName in target)) {\n return null\n }\n\n const hasProperty = Object.prototype.hasOwnProperty.call(target, propertyName)\n if (hasProperty) {\n return target\n }\n\n const prototype = Reflect.getPrototypeOf(target)\n return prototype ? findPropertySource(prototype, propertyName) : null\n}\n","import { findPropertySource } from './findPropertySource'\n\nexport interface ProxyOptions<Target extends Record<string, any>> {\n constructorCall?(args: Array<unknown>, next: NextFunction<Target>): Target\n\n methodCall?<F extends keyof Target>(\n this: Target,\n data: [methodName: F, args: Array<unknown>],\n next: NextFunction<void>\n ): void\n\n setProperty?(\n data: [propertyName: string | symbol, nextValue: unknown],\n next: NextFunction<boolean>\n ): boolean\n\n getProperty?(\n data: [propertyName: string | symbol, receiver: Target],\n next: NextFunction<void>\n ): void\n}\n\nexport type NextFunction<ReturnType> = () => ReturnType\n\nexport function createProxy<Target extends object>(\n target: Target,\n options: ProxyOptions<Target>\n): Target {\n const proxy = new Proxy(target, optionsToProxyHandler(options))\n\n return proxy\n}\n\nfunction optionsToProxyHandler<T extends Record<string, any>>(\n options: ProxyOptions<T>\n): ProxyHandler<T> {\n const { constructorCall, methodCall, getProperty, setProperty } = options\n const handler: ProxyHandler<T> = {}\n\n if (typeof constructorCall !== 'undefined') {\n handler.construct = function (target, args, newTarget) {\n const next = Reflect.construct.bind(null, target as any, args, newTarget)\n return constructorCall.call(newTarget, args, next)\n }\n }\n\n handler.set = function (target, propertyName, nextValue) {\n const next = () => {\n const propertySource = findPropertySource(target, propertyName) || target\n const ownDescriptors = Reflect.getOwnPropertyDescriptor(\n propertySource,\n propertyName\n )\n\n // Respect any custom setters present for this property.\n if (typeof ownDescriptors?.set !== 'undefined') {\n ownDescriptors.set.apply(target, [nextValue])\n return true\n }\n\n // Otherwise, set the property on the source.\n return Reflect.defineProperty(propertySource, propertyName, {\n writable: true,\n enumerable: true,\n configurable: true,\n value: nextValue,\n })\n }\n\n if (typeof setProperty !== 'undefined') {\n return setProperty.call(target, [propertyName, nextValue], next)\n }\n\n return next()\n }\n\n handler.get = function (target, propertyName, receiver) {\n /**\n * @note Using `Reflect.get()` here causes \"TypeError: Illegal invocation\".\n */\n const next = () => target[propertyName as any]\n\n const value =\n typeof getProperty !== 'undefined'\n ? getProperty.call(target, [propertyName, receiver], next)\n : next()\n\n if (typeof value === 'function') {\n return (...args: Array<any>) => {\n const next = value.bind(target, ...args)\n\n if (typeof methodCall !== 'undefined') {\n return methodCall.call(target, [propertyName as any, args], next)\n }\n\n return next()\n }\n }\n\n return value\n }\n\n return handler\n}\n","export function isDomParserSupportedType(\n type: string\n): type is DOMParserSupportedType {\n const supportedTypes: Array<DOMParserSupportedType> = [\n 'application/xhtml+xml',\n 'application/xml',\n 'image/svg+xml',\n 'text/html',\n 'text/xml',\n ]\n return supportedTypes.some((supportedType) => {\n return type.startsWith(supportedType)\n })\n}\n","/**\n * Parses a given string into JSON.\n * Gracefully handles invalid JSON by returning `null`.\n */\nexport function parseJson(data: string): Record<string, unknown> | null {\n try {\n const json = JSON.parse(data)\n return json\n } catch (_) {\n return null\n }\n}\n","import { FetchResponse } from '../../../utils/fetchUtils'\n\n/**\n * Creates a Fetch API `Response` instance from the given\n * `XMLHttpRequest` instance and a response body.\n */\nexport function createResponse(\n request: XMLHttpRequest,\n body: BodyInit | null\n): Response {\n /**\n * Handle XMLHttpRequest responses that must have null as the\n * response body when represented using Fetch API Response.\n * XMLHttpRequest response will always have an empty string\n * as the \"request.response\" in those cases, resulting in an error\n * when constructing a Response instance.\n * @see https://github.com/mswjs/interceptors/issues/379\n */\n const responseBodyOrNull = FetchResponse.isResponseWithBody(request.status)\n ? body\n : null\n\n return new FetchResponse(responseBodyOrNull, {\n url: request.responseURL,\n status: request.status,\n statusText: request.statusText,\n headers: createHeadersFromXMLHttpRequestHeaders(\n request.getAllResponseHeaders()\n ),\n })\n}\n\nfunction createHeadersFromXMLHttpRequestHeaders(headersString: string): Headers {\n const headers = new Headers()\n\n const lines = headersString.split(/[\\r\\n]+/)\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n const [name, ...parts] = line.split(': ')\n const value = parts.join(': ')\n\n headers.append(name, value)\n }\n\n return headers\n}\n","/**\n * Return a total byte length of the given request/response body.\n * If the `Content-Length` header is present, it will be used as the byte length.\n */\nexport async function getBodyByteLength(\n input: Request | Response\n): Promise<number> {\n const explicitContentLength = input.headers.get('content-length')\n\n if (explicitContentLength != null && explicitContentLength !== '') {\n return Number(explicitContentLength)\n }\n\n const buffer = await input.arrayBuffer()\n return buffer.byteLength\n}\n","import { invariant } from 'outvariant'\nimport { isNodeProcess } from 'is-node-process'\nimport type { Logger } from '@open-draft/logger'\nimport { concatArrayBuffer } from './utils/concatArrayBuffer'\nimport { createEvent } from './utils/createEvent'\nimport {\n decodeBuffer,\n encodeBuffer,\n toArrayBuffer,\n} from '../../utils/bufferUtils'\nimport { createProxy } from '../../utils/createProxy'\nimport { isDomParserSupportedType } from './utils/isDomParserSupportedType'\nimport { parseJson } from '../../utils/parseJson'\nimport { createResponse } from './utils/createResponse'\nimport { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'\nimport { createRequestId } from '../../createRequestId'\nimport { getBodyByteLength } from './utils/getBodyByteLength'\nimport { setRawRequest } from '../../getRawRequest'\nimport { FetchRequest } from '../../utils/fetchUtils'\n\nconst kIsRequestHandled = Symbol('kIsRequestHandled')\nconst IS_NODE = isNodeProcess()\nconst kFetchRequest = Symbol('kFetchRequest')\n\n/**\n * An `XMLHttpRequest` instance controller that allows us\n * to handle any given request instance (e.g. responding to it).\n */\nexport class XMLHttpRequestController {\n public request: XMLHttpRequest\n public requestId: string\n public onRequest?: (\n this: XMLHttpRequestController,\n args: {\n request: Request\n requestId: string\n }\n ) => Promise<void>\n public onResponse?: (\n this: XMLHttpRequestController,\n args: {\n response: Response\n isMockedResponse: boolean\n request: Request\n requestId: string\n }\n ) => void;\n\n [kIsRequestHandled]: boolean;\n [kFetchRequest]?: Request\n private method: string = 'GET'\n private url: URL = null as any\n private requestHeaders: Headers\n private responseBuffer: Uint8Array\n private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>\n private uploadEvents: Map<\n keyof XMLHttpRequestEventTargetEventMap,\n Array<Function>\n >\n\n constructor(\n readonly initialRequest: XMLHttpRequest,\n public logger: Logger\n ) {\n this[kIsRequestHandled] = false\n\n this.events = new Map()\n this.uploadEvents = new Map()\n this.requestId = createRequestId()\n this.requestHeaders = new Headers()\n this.responseBuffer = new Uint8Array()\n\n this.request = createProxy(initialRequest, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'ontimeout': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n /**\n * @note Proxy callbacks to event listeners because JSDOM has trouble\n * translating these properties to callbacks. It seemed to be operating\n * on events exclusively.\n */\n this.request.addEventListener(eventName, nextValue as any)\n\n return invoke()\n }\n\n default: {\n return invoke()\n }\n }\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'open': {\n const [method, url] = args as [string, string | undefined]\n\n if (typeof url === 'undefined') {\n this.method = 'GET'\n this.url = toAbsoluteUrl(method)\n } else {\n this.method = method\n this.url = toAbsoluteUrl(url)\n }\n\n this.logger = this.logger.extend(`${this.method} ${this.url.href}`)\n this.logger.info('open', this.method, this.url.href)\n\n return invoke()\n }\n\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n\n this.registerEvent(eventName, listener)\n this.logger.info('addEventListener', eventName, listener)\n\n return invoke()\n }\n\n case 'setRequestHeader': {\n const [name, value] = args as [string, string]\n this.requestHeaders.set(name, value)\n\n this.logger.info('setRequestHeader', name, value)\n\n return invoke()\n }\n\n case 'send': {\n const [body] = args as [\n body?: XMLHttpRequestBodyInit | Document | null,\n ]\n\n this.request.addEventListener('load', () => {\n if (typeof this.onResponse !== 'undefined') {\n // Create a Fetch API Response representation of whichever\n // response this XMLHttpRequest received. Note those may\n // be either a mocked and the original response.\n const fetchResponse = createResponse(\n this.request,\n /**\n * The `response` property is the right way to read\n * the ambiguous response body, as the request's \"responseType\" may differ.\n * @see https://xhr.spec.whatwg.org/#the-response-attribute\n */\n this.request.response\n )\n\n // Notify the consumer about the response.\n this.onResponse.call(this, {\n response: fetchResponse,\n isMockedResponse: this[kIsRequestHandled],\n request: fetchRequest,\n requestId: this.requestId!,\n })\n }\n })\n\n const requestBody =\n typeof body === 'string' ? encodeBuffer(body) : body\n\n // Delegate request handling to the consumer.\n const fetchRequest = this.toFetchApiRequest(requestBody)\n this[kFetchRequest] = fetchRequest.clone()\n\n /**\n * @note Start request handling on the next tick so that the user\n * could add event listeners for \"loadend\" before the interceptor fires it.\n */\n queueMicrotask(() => {\n const onceRequestSettled =\n this.onRequest?.call(this, {\n request: fetchRequest,\n requestId: this.requestId!,\n }) || Promise.resolve()\n\n onceRequestSettled.finally(() => {\n // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.\n if (!this[kIsRequestHandled]) {\n this.logger.info(\n 'request callback settled but request has not been handled (readystate %d), performing as-is...',\n this.request.readyState\n )\n\n /**\n * @note Set the intercepted request ID on the original request in Node.js\n * so that if it triggers any other interceptors, they don't attempt\n * to process it once again.\n *\n * For instance, XMLHttpRequest is often implemented via \"http.ClientRequest\"\n * and we don't want for both XHR and ClientRequest interceptors to\n * handle the same request at the same time (e.g. emit the \"response\" event twice).\n */\n if (IS_NODE) {\n this.request.setRequestHeader(\n INTERNAL_REQUEST_ID_HEADER_NAME,\n this.requestId!\n )\n }\n\n return invoke()\n }\n })\n })\n\n break\n }\n\n default: {\n return invoke()\n }\n }\n },\n })\n\n /**\n * Proxy the `.upload` property to gather the event listeners/callbacks.\n */\n define(\n this.request,\n 'upload',\n createProxy(this.request.upload, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'onloadstart':\n case 'onprogress':\n case 'onaboart':\n case 'onerror':\n case 'onload':\n case 'ontimeout':\n case 'onloadend': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n this.registerUploadEvent(eventName, nextValue as Function)\n }\n }\n\n return invoke()\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n this.registerUploadEvent(eventName, listener)\n this.logger.info('upload.addEventListener', eventName, listener)\n\n return invoke()\n }\n }\n },\n })\n )\n }\n\n private registerEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.events.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.events.set(eventName, nextEvents)\n\n this.logger.info('registered event \"%s\"', eventName, listener)\n }\n\n private registerUploadEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.uploadEvents.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.uploadEvents.set(eventName, nextEvents)\n\n this.logger.info('registered upload event \"%s\"', eventName, listener)\n }\n\n /**\n * Responds to the current request with the given\n * Fetch API `Response` instance.\n */\n public async respondWith(response: Response): Promise<void> {\n /**\n * @note Since `XMLHttpRequestController` delegates the handling of the responses\n * to the \"load\" event listener that doesn't distinguish between the mocked and original\n * responses, mark the request that had a mocked response with a corresponding symbol.\n *\n * Mark this request as having a mocked response immediately since\n * calculating request/response total body length is asynchronous.\n */\n this[kIsRequestHandled] = true\n\n /**\n * Dispatch request upload events for requests with a body.\n * @see https://github.com/mswjs/interceptors/issues/573\n */\n if (this[kFetchRequest]) {\n const totalRequestBodyLength = await getBodyByteLength(\n this[kFetchRequest]\n )\n\n this.trigger('loadstart', this.request.upload, {\n loaded: 0,\n total: totalRequestBodyLength,\n })\n this.trigger('progress', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n this.trigger('load', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n\n this.trigger('loadend', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n }\n\n this.logger.info(\n 'responding with a mocked response: %d %s',\n response.status,\n response.statusText\n )\n\n define(this.request, 'status', response.status)\n define(this.request, 'statusText', response.statusText)\n define(this.request, 'responseURL', this.url.href)\n\n this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {\n apply: (_, __, args: [name: string]) => {\n this.logger.info('getResponseHeader', args[0])\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning null')\n\n // Headers not received yet, nothing to return.\n return null\n }\n\n const headerValue = response.headers.get(args[0])\n this.logger.info(\n 'resolved response header \"%s\" to',\n args[0],\n headerValue\n )\n\n return headerValue\n },\n })\n\n this.request.getAllResponseHeaders = new Proxy(\n this.request.getAllResponseHeaders,\n {\n apply: () => {\n this.logger.info('getAllResponseHeaders')\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning empty string')\n\n // Headers not received yet, nothing to return.\n return ''\n }\n\n const headersList = Array.from(response.headers.entries())\n const allHeaders = headersList\n .map(([headerName, headerValue]) => {\n return `${headerName}: ${headerValue}`\n })\n .join('\\r\\n')\n\n this.logger.info('resolved all response headers to', allHeaders)\n\n return allHeaders\n },\n }\n )\n\n // Update the response getters to resolve against the mocked response.\n Object.defineProperties(this.request, {\n response: {\n enumerable: true,\n configurable: false,\n get: () => this.response,\n },\n responseText: {\n enumerable: true,\n configurable: false,\n get: () => this.responseText,\n },\n responseXML: {\n enumerable: true,\n configurable: false,\n get: () => this.responseXML,\n },\n })\n\n const totalResponseBodyLength = await getBodyByteLength(response.clone())\n\n this.logger.info('calculated response body length', totalResponseBodyLength)\n\n this.trigger('loadstart', this.request, {\n loaded: 0,\n total: totalResponseBodyLength,\n })\n\n this.setReadyState(this.request.HEADERS_RECEIVED)\n this.setReadyState(this.request.LOADING)\n\n const finalizeResponse = () => {\n this.logger.info('finalizing the mocked response...')\n\n this.setReadyState(this.request.DONE)\n\n this.trigger('load', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n\n this.trigger('loadend', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n if (response.body) {\n this.logger.info('mocked response has body, streaming...')\n\n const reader = response.body.getReader()\n\n const readNextResponseBodyChunk = async () => {\n const { value, done } = await reader.read()\n\n if (done) {\n this.logger.info('response body stream done!')\n finalizeResponse()\n return\n }\n\n if (value) {\n this.logger.info('read response body chunk:', value)\n this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)\n\n this.trigger('progress', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n readNextResponseBodyChunk()\n }\n\n readNextResponseBodyChunk()\n } else {\n finalizeResponse()\n }\n }\n\n private responseBufferToText(): string {\n return decodeBuffer(this.responseBuffer)\n }\n\n get response(): unknown {\n this.logger.info(\n 'getResponse (responseType: %s)',\n this.request.responseType\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n switch (this.request.responseType) {\n case 'json': {\n const responseJson = parseJson(this.responseBufferToText())\n this.logger.info('resolved response JSON', responseJson)\n\n return responseJson\n }\n\n case 'arraybuffer': {\n const arrayBuffer = toArrayBuffer(this.responseBuffer)\n this.logger.info('resolved response ArrayBuffer', arrayBuffer)\n\n return arrayBuffer\n }\n\n case 'blob': {\n const mimeType =\n this.request.getResponseHeader('Content-Type') || 'text/plain'\n const responseBlob = new Blob([this.responseBufferToText()], {\n type: mimeType,\n })\n\n this.logger.info(\n 'resolved response Blob (mime type: %s)',\n responseBlob,\n mimeType\n )\n\n return responseBlob\n }\n\n default: {\n const responseText = this.responseBufferToText()\n this.logger.info(\n 'resolving \"%s\" response type as text',\n this.request.responseType,\n responseText\n )\n\n return responseText\n }\n }\n }\n\n get responseText(): string {\n /**\n * Throw when trying to read the response body as text when the\n * \"responseType\" doesn't expect text. This just respects the spec better.\n * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute\n */\n invariant(\n this.request.responseType === '' || this.request.responseType === 'text',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (\n this.request.readyState !== this.request.LOADING &&\n this.request.readyState !== this.request.DONE\n ) {\n return ''\n }\n\n const responseText = this.responseBufferToText()\n this.logger.info('getResponseText: \"%s\"', responseText)\n\n return responseText\n }\n\n get responseXML(): Document | null {\n invariant(\n this.request.responseType === '' ||\n this.request.responseType === 'document',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n const contentType = this.request.getResponseHeader('Content-Type') || ''\n\n if (typeof DOMParser === 'undefined') {\n console.warn(\n 'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'\n )\n return null\n }\n\n if (isDomParserSupportedType(contentType)) {\n return new DOMParser().parseFromString(\n this.responseBufferToText(),\n contentType\n )\n }\n\n return null\n }\n\n public errorWith(error?: Error): void {\n /**\n * @note Mark this request as handled even if it received a mock error.\n * This prevents the controller from trying to perform this request as-is.\n */\n this[kIsRequestHandled] = true\n this.logger.info('responding with an error')\n\n this.setReadyState(this.request.DONE)\n this.trigger('error', this.request)\n this.trigger('loadend', this.request)\n }\n\n /**\n * Transitions this request's `readyState` to the given one.\n */\n private setReadyState(nextReadyState: number): void {\n this.logger.info(\n 'setReadyState: %d -> %d',\n this.request.readyState,\n nextReadyState\n )\n\n if (this.request.readyState === nextReadyState) {\n this.logger.info('ready state identical, skipping transition...')\n return\n }\n\n define(this.request, 'readyState', nextReadyState)\n\n this.logger.info('set readyState to: %d', nextReadyState)\n\n if (nextReadyState !== this.request.UNSENT) {\n this.logger.info('triggering \"readystatechange\" event...')\n\n this.trigger('readystatechange', this.request)\n }\n }\n\n /**\n * Triggers given event on the `XMLHttpRequest` instance.\n */\n private trigger<\n EventName extends keyof (XMLHttpRequestEventTargetEventMap & {\n readystatechange: ProgressEvent<XMLHttpRequestEventTarget>\n }),\n >(\n eventName: EventName,\n target: XMLHttpRequest | XMLHttpRequestUpload,\n options?: ProgressEventInit\n ): void {\n const callback = (target as XMLHttpRequest)[`on${eventName}`]\n const event = createEvent(target, eventName, options)\n\n this.logger.info('trigger \"%s\"', eventName, options || '')\n\n // Invoke direct callbacks.\n if (typeof callback === 'function') {\n this.logger.info('found a direct \"%s\" callback, calling...', eventName)\n callback.call(target as XMLHttpRequest, event)\n }\n\n // Invoke event listeners.\n const events =\n target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events\n\n for (const [registeredEventName, listeners] of events) {\n if (registeredEventName === eventName) {\n this.logger.info(\n 'found %d listener(s) for \"%s\" event, calling...',\n listeners.length,\n eventName\n )\n\n listeners.forEach((listener) => listener.call(target, event))\n }\n }\n }\n\n /**\n * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.\n */\n private toFetchApiRequest(\n body: XMLHttpRequestBodyInit | Document | null | undefined\n ): Request {\n this.logger.info('converting request to a Fetch API Request...')\n\n // If the `Document` is used as the body of this XMLHttpRequest,\n // set its inner text as the Fetch API Request body.\n const resolvedBody =\n body instanceof Document ? body.documentElement.innerText : body\n\n const fetchRequest = new FetchRequest(this.url.href, {\n method: this.method,\n headers: this.requestHeaders,\n /**\n * @see https://xhr.spec.whatwg.org/#cross-origin-credentials\n */\n credentials: this.request.withCredentials ? 'include' : 'same-origin',\n body: ['GET', 'HEAD'].includes(this.method.toUpperCase())\n ? null\n : resolvedBody,\n })\n\n const proxyHeaders = createProxy(fetchRequest.headers, {\n methodCall: ([methodName, args], invoke) => {\n // Forward the latest state of the internal request headers\n // because the interceptor might have modified them\n // without responding to the request.\n switch (methodName) {\n case 'append':\n case 'set': {\n const [headerName, headerValue] = args as [string, string]\n this.request.setRequestHeader(headerName, headerValue)\n break\n }\n\n case 'delete': {\n const [headerName] = args as [string]\n console.warn(\n `XMLHttpRequest: Cannot remove a \"${headerName}\" header from the Fetch API representation of the \"${fetchRequest.method} ${fetchRequest.url}\" request. XMLHttpRequest headers cannot be removed.`\n )\n break\n }\n }\n\n return invoke()\n },\n })\n define(fetchRequest, 'headers', proxyHeaders)\n setRawRequest(fetchRequest, this.request)\n\n this.logger.info('converted request to a Fetch API Request!', fetchRequest)\n\n return fetchRequest\n }\n}\n\nfunction toAbsoluteUrl(url: string | URL): URL {\n /**\n * @note XMLHttpRequest interceptor may run in environments\n * that implement XMLHttpRequest but don't implement \"location\"\n * (for example, React Native). If that's the case, return the\n * input URL as-is (nothing to be relative to).\n * @see https://github.com/mswjs/msw/issues/1777\n */\n if (typeof location === 'undefined') {\n return new URL(url)\n }\n\n return new URL(url.toString(), location.href)\n}\n\nfunction define(\n target: object,\n property: string | symbol,\n value: unknown\n): void {\n Reflect.defineProperty(target, property, {\n // Ensure writable properties to allow redefining readonly properties.\n writable: true,\n enumerable: true,\n value,\n })\n}\n","import type { Logger } from '@open-draft/logger'\nimport { XMLHttpRequestEmitter } from '.'\nimport { RequestController } from '../../RequestController'\nimport { XMLHttpRequestController } from './XMLHttpRequestController'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { isResponseError } from '../../utils/responseUtils'\n\nexport interface XMLHttpRequestProxyOptions {\n emitter: XMLHttpRequestEmitter\n logger: Logger\n}\n\n/**\n * Create a proxied `XMLHttpRequest` class.\n * The proxied class establishes spies on certain methods,\n * allowing us to intercept requests and respond to them.\n */\nexport function createXMLHttpRequestProxy({\n emitter,\n logger,\n}: XMLHttpRequestProxyOptions) {\n const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {\n construct(target, args, newTarget) {\n logger.info('constructed new XMLHttpRequest')\n\n const originalRequest = Reflect.construct(\n target,\n args,\n newTarget\n ) as XMLHttpRequest\n\n /**\n * @note Forward prototype descriptors onto the proxied object.\n * XMLHttpRequest is implemented in JSDOM in a way that assigns\n * a bunch of descriptors, like \"set responseType()\" on the prototype.\n * With this propagation, we make sure that those descriptors trigger\n * when the user operates with the proxied request instance.\n */\n const prototypeDescriptors = Object.getOwnPropertyDescriptors(\n target.prototype\n )\n for (const propertyName in prototypeDescriptors) {\n Reflect.defineProperty(\n originalRequest,\n propertyName,\n prototypeDescriptors[propertyName]\n )\n }\n\n const xhrRequestController = new XMLHttpRequestController(\n originalRequest,\n logger\n )\n\n xhrRequestController.onRequest = async function ({ request, requestId }) {\n const controller = new RequestController(request, {\n passthrough: () => {\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n },\n respondWith: async (response) => {\n if (isResponseError(response)) {\n this.errorWith(new TypeError('Network error'))\n return\n }\n\n await this.respondWith(response)\n },\n errorWith: (reason) => {\n this.logger.info('request errored!', { error: reason })\n\n if (reason instanceof Error) {\n this.errorWith(reason)\n }\n },\n })\n\n this.logger.info('awaiting mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n controller,\n emitter,\n })\n }\n\n xhrRequestController.onResponse = async function ({\n response,\n isMockedResponse,\n request,\n requestId,\n }) {\n this.logger.info(\n 'emitting the \"response\" event for %s listener(s)...',\n emitter.listenerCount('response')\n )\n\n emitter.emit('response', {\n response,\n isMockedResponse,\n request,\n requestId,\n })\n }\n\n // Return the proxied request from the controller\n // so that the controller can react to the consumer's interactions\n // with this request (opening/sending/etc).\n return xhrRequestController.request\n },\n })\n\n return XMLHttpRequestProxy\n}\n","import { Emitter } from 'strict-event-emitter'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>\n\nexport class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {\n static interceptorSymbol = Symbol('xhr')\n\n constructor() {\n super(XMLHttpRequestInterceptor.interceptorSymbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('XMLHttpRequest')\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('patching global XMLHttpRequest...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'XMLHttpRequest', () => {\n return createXMLHttpRequestProxy({\n emitter: this.emitter,\n logger: this.logger,\n })\n })\n )\n\n logger.info(\n 'global XMLHttpRequest patched!',\n globalThis.XMLHttpRequest.name\n )\n }\n}\n"],"mappings":"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"} |
| import { it, beforeEach, afterEach, expect, vi } from 'vitest' | ||
| import { patchesRegistry } from './patchesRegistry' | ||
| declare global { | ||
| var foo: { original: boolean } | ||
| } | ||
| const realGlobalPrototype = Object.getPrototypeOf(global) | ||
| beforeEach(() => { | ||
| global.foo = { original: true } | ||
| }) | ||
| afterEach(() => { | ||
| Object.setPrototypeOf(global, realGlobalPrototype) | ||
| patchesRegistry.restoreAllPatches() | ||
| }) | ||
| it('replaces the global', () => { | ||
| patchesRegistry.applyPatch(global, 'foo', () => ({ original: false })) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| }) | ||
| it('exposes the real value to the replacement callback', () => { | ||
| patchesRegistry.applyPatch(global, 'foo', (realValue) => { | ||
| expect(realValue).toEqual({ original: true }) | ||
| return { original: false } | ||
| }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| }) | ||
| it('replaces the global set on the prototype', () => { | ||
| function FakeGlobalScope() {} | ||
| FakeGlobalScope.prototype.foo = { prototype: true } | ||
| Object.setPrototypeOf(global, FakeGlobalScope.prototype) | ||
| Reflect.deleteProperty(global, 'foo') | ||
| expect(global.foo).toEqual({ prototype: true }) | ||
| expect(FakeGlobalScope.prototype.foo).toEqual({ prototype: true }) | ||
| patchesRegistry.applyPatch(global, 'foo', () => ({ original: false })) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| expect(FakeGlobalScope.prototype.foo, 'Preserves prototype value').toEqual({ | ||
| prototype: true, | ||
| }) | ||
| }) | ||
| it('replaces the global after it was restored', () => { | ||
| const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({ | ||
| original: false, | ||
| })) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| patchesRegistry.applyPatch(global, 'foo', () => ({ original: false })) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| }) | ||
| it('replaces a property on a custom owner', () => { | ||
| const owner = { bar: { original: true } } | ||
| const restoreGlobal = patchesRegistry.applyPatch(owner, 'bar', () => ({ | ||
| original: false, | ||
| })) | ||
| expect(owner.bar).toEqual({ original: false }) | ||
| expect(global.foo).toEqual({ original: true }) | ||
| restoreGlobal() | ||
| expect(owner.bar).toEqual({ original: true }) | ||
| }) | ||
| it('tracks replacements per owner independently', () => { | ||
| const ownerA = { shared: 'a-original' } | ||
| const ownerB = { shared: 'b-original' } | ||
| const restoreA = patchesRegistry.applyPatch(ownerA, 'shared', () => 'a-next') | ||
| const restoreB = patchesRegistry.applyPatch(ownerB, 'shared', () => 'b-next') | ||
| expect(ownerA.shared).toBe('a-next') | ||
| expect(ownerB.shared).toBe('b-next') | ||
| restoreA() | ||
| expect(ownerA.shared).toBe('a-original') | ||
| expect(ownerB.shared).toBe('b-next') | ||
| restoreB() | ||
| expect(ownerB.shared).toBe('b-original') | ||
| }) | ||
| it('warns on replacing a non-existing global', () => { | ||
| vi.spyOn(console, 'warn').mockImplementation(() => {}) | ||
| patchesRegistry.applyPatch( | ||
| global, | ||
| // @ts-expect-error Intentionally invalid value. | ||
| 'NON-EXISTING', | ||
| () => ({ original: false }) | ||
| ) | ||
| expect(console.warn).toHaveBeenCalledExactlyOnceWith( | ||
| 'Failed to replace a global value at "NON-EXISTING": not a global value.' | ||
| ) | ||
| }) | ||
| it('throws if replacing an already replaced global', () => { | ||
| patchesRegistry.applyPatch(global, 'foo', () => ({ original: false })) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| expect(() => | ||
| patchesRegistry.applyPatch(global, 'foo', () => ({ original: false })) | ||
| ).toThrow('Failed to replace a global value at "foo": already replaced.') | ||
| }) | ||
| it('does nothing if restoring an already restored global', () => { | ||
| const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({ | ||
| original: false, | ||
| })) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| }) | ||
| it('restores the global', () => { | ||
| const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({ | ||
| original: false, | ||
| })) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| }) | ||
| it('restores the global set on the prototype', () => { | ||
| function FakeGlobalScope() {} | ||
| FakeGlobalScope.prototype.foo = { prototype: true } | ||
| Object.setPrototypeOf(global, FakeGlobalScope.prototype) | ||
| Reflect.deleteProperty(global, 'foo') | ||
| expect(global.foo).toEqual({ prototype: true }) | ||
| const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({ | ||
| original: false, | ||
| })) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ prototype: true }) | ||
| }) | ||
| it('restores global to the original property descriptor', () => { | ||
| const descriptor: PropertyDescriptor = { | ||
| value: { original: true }, | ||
| enumerable: false, | ||
| configurable: true, | ||
| writable: false, | ||
| } | ||
| Object.defineProperty(global, 'foo', descriptor) | ||
| expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual(descriptor) | ||
| const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({ | ||
| original: false, | ||
| })) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual({ | ||
| value: { original: false }, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false, | ||
| }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual(descriptor) | ||
| }) |
| import { invariant } from 'outvariant' | ||
| class PatchesRegistry { | ||
| #replacements = new Map<object, Map<PropertyKey, () => void>>() | ||
| public applyPatch<Owner extends object, K extends keyof Owner>( | ||
| owner: Owner, | ||
| key: K, | ||
| getNextValue: (realValue: Owner[K]) => Owner[K] | ||
| ): () => void { | ||
| const ownerReplacements = this.#replacements.get(owner) | ||
| invariant( | ||
| !ownerReplacements?.has(key), | ||
| `Failed to replace a global value at "${String(key)}": already replaced.` | ||
| ) | ||
| const match = getDeepPropertyDescriptor(owner, key) | ||
| if (typeof match === 'undefined') { | ||
| console.warn( | ||
| `Failed to replace a global value at "${String(key)}": not a global value.` | ||
| ) | ||
| return () => {} | ||
| } | ||
| Object.defineProperty(owner, key, { | ||
| value: getNextValue(owner[key]), | ||
| enumerable: true, | ||
| configurable: true, | ||
| }) | ||
| const restorePatch = () => { | ||
| const currentReplacements = this.#replacements.get(owner) | ||
| if (!currentReplacements?.has(key)) { | ||
| return | ||
| } | ||
| if (match.owner === owner) { | ||
| Object.defineProperty(match.owner, key, match.descriptor) | ||
| } else { | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the match's owner isn't the original owner, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(owner, key) | ||
| } | ||
| currentReplacements.delete(key) | ||
| if (currentReplacements.size === 0) { | ||
| this.#replacements.delete(owner) | ||
| } | ||
| } | ||
| if (ownerReplacements) { | ||
| ownerReplacements.set(key, restorePatch) | ||
| } else { | ||
| this.#replacements.set(owner, new Map([[key, restorePatch]])) | ||
| } | ||
| return restorePatch | ||
| } | ||
| public restoreAllPatches(): void { | ||
| const errors: Array<Error> = [] | ||
| for (const [, ownerReplacements] of this.#replacements) { | ||
| for (const [, restorePatch] of ownerReplacements) { | ||
| try { | ||
| restorePatch() | ||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| errors.push(error) | ||
| } else { | ||
| throw error | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (errors.length > 0) { | ||
| throw new AggregateError(errors, 'FOO!') | ||
| } | ||
| } | ||
| } | ||
| export const patchesRegistry = new PatchesRegistry() | ||
| interface DeepDescriptorMatch { | ||
| owner: object | ||
| descriptor: PropertyDescriptor | ||
| } | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| export function getDeepPropertyDescriptor<Owner extends object>( | ||
| owner: Owner, | ||
| key: keyof Owner | ||
| ): DeepDescriptorMatch | undefined { | ||
| let currentOwner: Owner | null = owner | ||
| let descriptor: PropertyDescriptor | undefined | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key) | ||
| if (descriptor) { | ||
| return { | ||
| owner: currentOwner, | ||
| descriptor, | ||
| } | ||
| } | ||
| currentOwner = Object.getPrototypeOf(currentOwner) | ||
| } | ||
| } |
| const require_createRequestId = require('./createRequestId-DOf8Ktjs.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-B6GjFKAr.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-DdfaiPVH.cjs'); | ||
| const require_bufferUtils = require('./bufferUtils-Uc0eRItL.cjs'); | ||
@@ -4,0 +4,0 @@ const require_resolveWebSocketUrl = require('./resolveWebSocketUrl-6K6EgqsA.cjs'); |
@@ -82,2 +82,3 @@ import { i as RequestControllerSource, n as RequestCredentials, r as RequestController, t as HttpRequestEventMap } from "./glossary-1lhZx6Aw.cjs"; | ||
| static isResponseWithBody(status: number): boolean; | ||
| static setStatus(status: number, response: Response): void; | ||
| static setUrl(url: string | undefined, response: Response): void; | ||
@@ -97,2 +98,3 @@ /** | ||
| constructor(body?: BodyInit | null, init?: FetchResponseInit); | ||
| clone(): Response; | ||
| } | ||
@@ -99,0 +101,0 @@ //#endregion |
@@ -82,2 +82,3 @@ import { i as RequestControllerSource, n as RequestCredentials, r as RequestController, t as HttpRequestEventMap } from "./glossary-nkHWcwvY.mjs"; | ||
| static isResponseWithBody(status: number): boolean; | ||
| static setStatus(status: number, response: Response): void; | ||
| static setUrl(url: string | undefined, response: Response): void; | ||
@@ -97,2 +98,3 @@ /** | ||
| constructor(body?: BodyInit | null, init?: FetchResponseInit); | ||
| clone(): Response; | ||
| } | ||
@@ -99,0 +101,0 @@ //#endregion |
| import { a as deleteGlobalSymbol, i as InterceptorReadyState, n as INTERNAL_REQUEST_ID_HEADER_NAME, o as getGlobalSymbol, r as Interceptor, t as createRequestId } from "./createRequestId-DYCsFHOi.mjs"; | ||
| import { i as FetchResponse, o as RequestController, r as FetchRequest, t as getRawRequest } from "./getRawRequest-BP--nUwn.mjs"; | ||
| import { i as FetchResponse, o as RequestController, r as FetchRequest, t as getRawRequest } from "./getRawRequest-B1BqgWG6.mjs"; | ||
| import { n as encodeBuffer, t as decodeBuffer } from "./bufferUtils-BiiO6HZv.mjs"; | ||
@@ -4,0 +4,0 @@ import { t as resolveWebSocketUrl } from "./resolveWebSocketUrl-C83-x9iE.mjs"; |
| require('../../createRequestId-DOf8Ktjs.cjs'); | ||
| require('../../getRawRequest-B6GjFKAr.cjs'); | ||
| require('../../hasConfigurableGlobal-B5o6PHq3.cjs'); | ||
| require('../../handleRequest-BIte-8l8.cjs'); | ||
| const require_fetch = require('../../fetch-BmrYPMQ0.cjs'); | ||
| require('../../getRawRequest-DdfaiPVH.cjs'); | ||
| require('../../hasConfigurableGlobal-BS75Oulv.cjs'); | ||
| require('../../handleRequest-tUNU616J.cjs'); | ||
| const require_fetch = require('../../fetch-CtkfZi4Z.cjs'); | ||
| exports.FetchInterceptor = require_fetch.FetchInterceptor; |
| import "../../createRequestId-DYCsFHOi.mjs"; | ||
| import "../../getRawRequest-BP--nUwn.mjs"; | ||
| import "../../hasConfigurableGlobal-B39W0thh.mjs"; | ||
| import "../../handleRequest-CnDA8MVO.mjs"; | ||
| import { t as FetchInterceptor } from "../../fetch-BS6cxWzo.mjs"; | ||
| import "../../getRawRequest-B1BqgWG6.mjs"; | ||
| import "../../hasConfigurableGlobal-FTYwno1G.mjs"; | ||
| import "../../handleRequest-BHrC8Flw.mjs"; | ||
| import { t as FetchInterceptor } from "../../fetch-Ddj5TV04.mjs"; | ||
| export { FetchInterceptor }; |
| const require_createRequestId = require('../../createRequestId-DOf8Ktjs.cjs'); | ||
| const require_resolveWebSocketUrl = require('../../resolveWebSocketUrl-6K6EgqsA.cjs'); | ||
| const require_hasConfigurableGlobal = require('../../hasConfigurableGlobal-B5o6PHq3.cjs'); | ||
| const require_hasConfigurableGlobal = require('../../hasConfigurableGlobal-BS75Oulv.cjs'); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
@@ -604,3 +604,3 @@ let outvariant = require("outvariant"); | ||
| logger.info("patching global WebSocket..."); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.globalsRegistry.replaceGlobal("WebSocket", WebSocketProxy)); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.patchesRegistry.applyPatch(globalThis, "WebSocket", () => WebSocketProxy)); | ||
| logger.info("global WebSocket patched!", globalThis.WebSocket.name); | ||
@@ -607,0 +607,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","names":["kEmitter","kBoundListener","socket: WebSocket","transport: WebSocketTransport","createRequestId","resolveWebSocketUrl","DeferredPromise","client: WebSocketOverride","transport: WebSocketClassTransport","createConnection: () => WebSocket","socket: WebSocketOverride","Interceptor","hasConfigurableGlobal","emitAsync","globalsRegistry"],"sources":["../../../../src/interceptors/WebSocket/utils/bindEvent.ts","../../../../src/interceptors/WebSocket/utils/events.ts","../../../../src/interceptors/WebSocket/WebSocketClientConnection.ts","../../../../src/interceptors/WebSocket/WebSocketOverride.ts","../../../../src/interceptors/WebSocket/WebSocketServerConnection.ts","../../../../src/interceptors/WebSocket/WebSocketClassTransport.ts","../../../../src/interceptors/WebSocket/index.ts"],"sourcesContent":["type EventWithTarget<E extends Event, T> = E & { target: T }\n\nexport function bindEvent<E extends Event, T>(\n target: T,\n event: E\n): EventWithTarget<E, T> {\n Object.defineProperties(event, {\n target: {\n value: target,\n enumerable: true,\n writable: true,\n },\n currentTarget: {\n value: target,\n enumerable: true,\n writable: true,\n },\n })\n\n return event as EventWithTarget<E, T>\n}\n","const kCancelable = Symbol('kCancelable')\nconst kDefaultPrevented = Symbol('kDefaultPrevented')\n\n/**\n * A `MessageEvent` superset that supports event cancellation\n * in Node.js. It's rather non-intrusive so it can be safely\n * used in the browser as well.\n *\n * @see https://github.com/nodejs/node/issues/51767\n */\nexport class CancelableMessageEvent<T = any> extends MessageEvent<T> {\n [kCancelable]: boolean;\n [kDefaultPrevented]: boolean\n\n constructor(type: string, init: MessageEventInit<T>) {\n super(type, init)\n this[kCancelable] = !!init.cancelable\n this[kDefaultPrevented] = false\n }\n\n get cancelable() {\n return this[kCancelable]\n }\n\n set cancelable(nextCancelable) {\n this[kCancelable] = nextCancelable\n }\n\n get defaultPrevented() {\n return this[kDefaultPrevented]\n }\n\n set defaultPrevented(nextDefaultPrevented) {\n this[kDefaultPrevented] = nextDefaultPrevented\n }\n\n public preventDefault(): void {\n if (this.cancelable && !this[kDefaultPrevented]) {\n this[kDefaultPrevented] = true\n }\n }\n}\n\ninterface CloseEventInit extends EventInit {\n code?: number\n reason?: string\n wasClean?: boolean\n}\n\nexport class CloseEvent extends Event {\n public code: number\n public reason: string\n public wasClean: boolean\n\n constructor(type: string, init: CloseEventInit = {}) {\n super(type, init)\n this.code = init.code === undefined ? 0 : init.code\n this.reason = init.reason === undefined ? '' : init.reason\n this.wasClean = init.wasClean === undefined ? false : init.wasClean\n }\n}\n\nexport class CancelableCloseEvent extends CloseEvent {\n [kCancelable]: boolean;\n [kDefaultPrevented]: boolean\n\n constructor(type: string, init: CloseEventInit = {}) {\n super(type, init)\n this[kCancelable] = !!init.cancelable\n this[kDefaultPrevented] = false\n }\n\n get cancelable() {\n return this[kCancelable]\n }\n\n set cancelable(nextCancelable) {\n this[kCancelable] = nextCancelable\n }\n\n get defaultPrevented() {\n return this[kDefaultPrevented]\n }\n\n set defaultPrevented(nextDefaultPrevented) {\n this[kDefaultPrevented] = nextDefaultPrevented\n }\n\n public preventDefault(): void {\n if (this.cancelable && !this[kDefaultPrevented]) {\n this[kDefaultPrevented] = true\n }\n }\n}\n","import type { WebSocketData, WebSocketTransport } from './WebSocketTransport'\nimport type { WebSocketEventListener } from './WebSocketOverride'\nimport { bindEvent } from './utils/bindEvent'\nimport { CancelableMessageEvent, CloseEvent } from './utils/events'\nimport { createRequestId } from '../../createRequestId'\n\nconst kEmitter = Symbol('kEmitter')\nconst kBoundListener = Symbol('kBoundListener')\n\nexport interface WebSocketClientEventMap {\n message: MessageEvent<WebSocketData>\n close: CloseEvent\n}\n\nexport abstract class WebSocketClientConnectionProtocol {\n abstract id: string\n abstract url: URL\n public abstract send(data: WebSocketData): void\n public abstract close(code?: number, reason?: string): void\n\n public abstract addEventListener<\n EventType extends keyof WebSocketClientEventMap,\n >(\n type: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void\n\n public abstract removeEventListener<\n EventType extends keyof WebSocketClientEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void\n}\n\n/**\n * The WebSocket client instance represents an incoming\n * client connection. The user can control the connection,\n * send and receive events.\n */\nexport class WebSocketClientConnection implements WebSocketClientConnectionProtocol {\n public readonly id: string\n public readonly url: URL\n\n private [kEmitter]: EventTarget\n\n constructor(\n public readonly socket: WebSocket,\n private readonly transport: WebSocketTransport\n ) {\n this.id = createRequestId()\n this.url = new URL(socket.url)\n this[kEmitter] = new EventTarget()\n\n // Emit outgoing client data (\"ws.send()\") as \"message\"\n // events on the \"client\" connection.\n this.transport.addEventListener('outgoing', (event) => {\n const message = bindEvent(\n this.socket,\n new CancelableMessageEvent('message', {\n data: event.data,\n origin: event.origin,\n cancelable: true,\n })\n )\n\n this[kEmitter].dispatchEvent(message)\n\n // This is a bit silly but forward the cancellation state\n // of the \"client\" message event to the \"outgoing\" transport event.\n // This way, other agens (like \"server\" connection) can know\n // whether the client listener has pervented the default.\n if (message.defaultPrevented) {\n event.preventDefault()\n }\n })\n\n /**\n * Emit the \"close\" event on the \"client\" connection\n * whenever the underlying transport is closed.\n * @note \"client.close()\" does NOT dispatch the \"close\"\n * event on the WebSocket because it uses non-configurable\n * close status code. Thus, we listen to the transport\n * instead of the WebSocket's \"close\" event.\n */\n this.transport.addEventListener('close', (event) => {\n this[kEmitter].dispatchEvent(\n bindEvent(this.socket, new CloseEvent('close', event))\n )\n })\n }\n\n /**\n * Listen for the outgoing events from the connected WebSocket client.\n */\n public addEventListener<EventType extends keyof WebSocketClientEventMap>(\n type: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void {\n if (!Reflect.has(listener, kBoundListener)) {\n const boundListener = listener.bind(this.socket)\n\n // Store the bound listener on the original listener\n // so the exact bound function can be accessed in \"removeEventListener()\".\n Object.defineProperty(listener, kBoundListener, {\n value: boundListener,\n enumerable: false,\n configurable: false,\n })\n }\n\n this[kEmitter].addEventListener(\n type,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Removes the listener for the given event.\n */\n public removeEventListener<EventType extends keyof WebSocketClientEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void {\n this[kEmitter].removeEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Send data to the connected client.\n */\n public send(data: WebSocketData): void {\n this.transport.send(data)\n }\n\n /**\n * Close the WebSocket connection.\n * @param {number} code A status code (see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).\n * @param {string} reason A custom connection close reason.\n */\n public close(code?: number, reason?: string): void {\n this.transport.close(code, reason)\n }\n}\n","import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport type { WebSocketData } from './WebSocketTransport'\nimport { bindEvent } from './utils/bindEvent'\nimport { CloseEvent } from './utils/events'\nimport { resolveWebSocketUrl } from '../../utils/resolveWebSocketUrl'\n\nexport type WebSocketEventListener<\n EventType extends WebSocketEventMap[keyof WebSocketEventMap] = Event,\n> = (this: WebSocket, event: EventType) => void\n\nconst WEBSOCKET_CLOSE_CODE_RANGE_ERROR =\n 'InvalidAccessError: close code out of user configurable range'\n\nexport const kPassthroughPromise = Symbol('kPassthroughPromise')\nexport const kOnSend = Symbol('kOnSend')\nexport const kClose = Symbol('kClose')\n\nexport class WebSocketOverride extends EventTarget implements WebSocket {\n static readonly CONNECTING = 0\n static readonly OPEN = 1\n static readonly CLOSING = 2\n static readonly CLOSED = 3\n readonly CONNECTING = 0\n readonly OPEN = 1\n readonly CLOSING = 2\n readonly CLOSED = 3\n\n public url: string\n public protocol: string\n public extensions: string\n public binaryType: BinaryType\n public readyState: WebSocket['readyState']\n public bufferedAmount: number\n\n private _onopen: WebSocketEventListener | null = null\n private _onmessage: WebSocketEventListener<\n MessageEvent<WebSocketData>\n > | null = null\n private _onerror: WebSocketEventListener | null = null\n private _onclose: WebSocketEventListener<CloseEvent> | null = null\n\n private [kPassthroughPromise]: DeferredPromise<boolean>\n private [kOnSend]?: (data: WebSocketData) => void\n\n constructor(url: string | URL, protocols?: string | Array<string>) {\n super()\n this.url = resolveWebSocketUrl(url)\n this.protocol = ''\n this.extensions = ''\n this.binaryType = 'blob'\n this.readyState = this.CONNECTING\n this.bufferedAmount = 0\n\n this[kPassthroughPromise] = new DeferredPromise<boolean>()\n\n queueMicrotask(async () => {\n if (await this[kPassthroughPromise]) {\n return\n }\n\n this.protocol =\n typeof protocols === 'string'\n ? protocols\n : Array.isArray(protocols) && protocols.length > 0\n ? protocols[0]\n : ''\n\n /**\n * @note Check that nothing has prevented this connection\n * (e.g. called `client.close()` in the connection listener).\n * If the connection has been prevented, never dispatch the open event,.\n */\n if (this.readyState === this.CONNECTING) {\n this.readyState = this.OPEN\n this.dispatchEvent(bindEvent(this, new Event('open')))\n }\n })\n }\n\n set onopen(listener: WebSocketEventListener | null) {\n this.removeEventListener('open', this._onopen)\n this._onopen = listener\n if (listener !== null) {\n this.addEventListener('open', listener)\n }\n }\n get onopen(): WebSocketEventListener | null {\n return this._onopen\n }\n\n set onmessage(\n listener: WebSocketEventListener<MessageEvent<WebSocketData>> | null\n ) {\n this.removeEventListener(\n 'message',\n this._onmessage as WebSocketEventListener\n )\n this._onmessage = listener\n if (listener !== null) {\n this.addEventListener('message', listener)\n }\n }\n get onmessage(): WebSocketEventListener<MessageEvent<WebSocketData>> | null {\n return this._onmessage\n }\n\n set onerror(listener: WebSocketEventListener | null) {\n this.removeEventListener('error', this._onerror)\n this._onerror = listener\n if (listener !== null) {\n this.addEventListener('error', listener)\n }\n }\n get onerror(): WebSocketEventListener | null {\n return this._onerror\n }\n\n set onclose(listener: WebSocketEventListener<CloseEvent> | null) {\n this.removeEventListener('close', this._onclose as WebSocketEventListener)\n this._onclose = listener\n if (listener !== null) {\n this.addEventListener('close', listener)\n }\n }\n get onclose(): WebSocketEventListener<CloseEvent> | null {\n return this._onclose\n }\n\n /**\n * @see https://websockets.spec.whatwg.org/#ref-for-dom-websocket-send%E2%91%A0\n */\n public send(data: WebSocketData): void {\n if (this.readyState === this.CONNECTING) {\n this.close()\n throw new DOMException('InvalidStateError')\n }\n\n // Sending when the socket is about to close\n // discards the sent data.\n if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {\n return\n }\n\n // Buffer the data to send in this even loop\n // but send it in the next.\n this.bufferedAmount += getDataSize(data)\n\n queueMicrotask(() => {\n // This is a bit optimistic but since no actual data transfer\n // is involved, all the data will be \"sent\" on the next tick.\n this.bufferedAmount = 0\n\n /**\n * @note Notify the parent about outgoing data.\n * This notifies the transport and the connection\n * listens to the outgoing data to emit the \"message\" event.\n */\n this[kOnSend]?.(data)\n })\n }\n\n public close(code: number = 1000, reason?: string): void {\n invariant(code, WEBSOCKET_CLOSE_CODE_RANGE_ERROR)\n invariant(\n code === 1000 || (code >= 3000 && code <= 4999),\n WEBSOCKET_CLOSE_CODE_RANGE_ERROR\n )\n\n this[kClose](code, reason)\n }\n\n private [kClose](\n code: number = 1000,\n reason?: string,\n wasClean = true\n ): void {\n /**\n * @note Move this check here so that even internal closures,\n * like those triggered by the `server` connection, are not\n * performed twice.\n */\n if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {\n return\n }\n\n this.readyState = this.CLOSING\n\n queueMicrotask(() => {\n this.readyState = this.CLOSED\n\n this.dispatchEvent(\n bindEvent(\n this,\n new CloseEvent('close', {\n code,\n reason,\n wasClean,\n })\n )\n )\n\n // Remove all event listeners once the socket is closed.\n this._onopen = null\n this._onmessage = null\n this._onerror = null\n this._onclose = null\n })\n }\n\n public addEventListener<K extends keyof WebSocketEventMap>(\n type: K,\n listener: (this: WebSocket, event: WebSocketEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions\n ): void\n public addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ): void\n public addEventListener(\n type: unknown,\n listener: unknown,\n options?: unknown\n ): void {\n return super.addEventListener(\n type as string,\n listener as EventListener,\n options as AddEventListenerOptions\n )\n }\n\n removeEventListener<K extends keyof WebSocketEventMap>(\n type: K,\n callback: EventListenerOrEventListenerObject | null,\n options?: boolean | EventListenerOptions\n ): void {\n return super.removeEventListener(type, callback, options)\n }\n}\n\nfunction getDataSize(data: WebSocketData): number {\n if (typeof data === 'string') {\n return data.length\n }\n\n if (data instanceof Blob) {\n return data.size\n }\n\n return data.byteLength\n}\n","import { invariant } from 'outvariant'\nimport {\n kClose,\n WebSocketEventListener,\n WebSocketOverride,\n} from './WebSocketOverride'\nimport type { WebSocketData } from './WebSocketTransport'\nimport type { WebSocketClassTransport } from './WebSocketClassTransport'\nimport { bindEvent } from './utils/bindEvent'\nimport {\n CancelableMessageEvent,\n CancelableCloseEvent,\n CloseEvent,\n} from './utils/events'\n\nconst kEmitter = Symbol('kEmitter')\nconst kBoundListener = Symbol('kBoundListener')\nconst kSend = Symbol('kSend')\n\nexport interface WebSocketServerEventMap {\n open: Event\n message: MessageEvent<WebSocketData>\n error: Event\n close: CloseEvent\n}\n\nexport abstract class WebSocketServerConnectionProtocol {\n public abstract connect(): void\n public abstract send(data: WebSocketData): void\n public abstract close(): void\n\n public abstract addEventListener<\n EventType extends keyof WebSocketServerEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void\n\n public abstract removeEventListener<\n EventType extends keyof WebSocketServerEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void\n}\n\n/**\n * The WebSocket server instance represents the actual production\n * WebSocket server connection. It's idle by default but you can\n * establish it by calling `server.connect()`.\n */\nexport class WebSocketServerConnection implements WebSocketServerConnectionProtocol {\n /**\n * A WebSocket instance connected to the original server.\n */\n private realWebSocket?: WebSocket\n private mockCloseController: AbortController\n private realCloseController: AbortController\n private [kEmitter]: EventTarget\n\n constructor(\n private readonly client: WebSocketOverride,\n private readonly transport: WebSocketClassTransport,\n private readonly createConnection: () => WebSocket\n ) {\n this[kEmitter] = new EventTarget()\n this.mockCloseController = new AbortController()\n this.realCloseController = new AbortController()\n\n // Automatically forward outgoing client events\n // to the actual server unless the outgoing message event\n // has been prevented. The \"outgoing\" transport event it\n // dispatched by the \"client\" connection.\n this.transport.addEventListener('outgoing', (event) => {\n // Ignore client messages if the server connection\n // hasn't been established yet. Nowhere to forward.\n if (typeof this.realWebSocket === 'undefined') {\n return\n }\n\n // Every outgoing client message can prevent this forwarding\n // by preventing the default of the outgoing message event.\n // This listener will be added before user-defined listeners,\n // so execute the logic on the next tick.\n queueMicrotask(() => {\n if (!event.defaultPrevented) {\n /**\n * @note Use the internal send mechanism so consumers can tell\n * apart direct user calls to `server.send()` and internal calls.\n * E.g. MSW has to ignore this internal call to log out messages correctly.\n */\n this[kSend](event.data)\n }\n })\n })\n\n this.transport.addEventListener(\n 'incoming',\n this.handleIncomingMessage.bind(this)\n )\n }\n\n /**\n * The `WebSocket` instance connected to the original server.\n * Accessing this before calling `server.connect()` will throw.\n */\n public get socket(): WebSocket {\n invariant(\n this.realWebSocket,\n 'Cannot access \"socket\" on the original WebSocket server object: the connection is not open. Did you forget to call `server.connect()`?'\n )\n\n return this.realWebSocket\n }\n\n /**\n * Open connection to the original WebSocket server.\n */\n public connect(): void {\n invariant(\n !this.realWebSocket || this.realWebSocket.readyState !== WebSocket.OPEN,\n 'Failed to call \"connect()\" on the original WebSocket instance: the connection already open'\n )\n\n const realWebSocket = this.createConnection()\n\n // Inherit the binary type from the mock WebSocket client.\n realWebSocket.binaryType = this.client.binaryType\n\n // Allow the interceptor to listen to when the server connection\n // has been established. This isn't necessary to operate with the connection\n // but may be beneficial in some cases (like conditionally adding logging).\n realWebSocket.addEventListener(\n 'open',\n (event) => {\n this[kEmitter].dispatchEvent(\n bindEvent(this.realWebSocket!, new Event('open', event))\n )\n },\n { once: true }\n )\n\n realWebSocket.addEventListener('message', (event) => {\n // Dispatch the \"incoming\" transport event instead of\n // invoking the internal handler directly. This way,\n // anyone can listen to the \"incoming\" event but this\n // class is the one resulting in it.\n this.transport.dispatchEvent(\n bindEvent(\n this.realWebSocket!,\n new MessageEvent('incoming', {\n data: event.data,\n origin: event.origin,\n })\n )\n )\n })\n\n // Close the original connection when the mock client closes.\n // E.g. \"client.close()\" was called. This is never forwarded anywhere.\n this.client.addEventListener(\n 'close',\n (event) => {\n this.handleMockClose(event)\n },\n {\n signal: this.mockCloseController.signal,\n }\n )\n\n // Forward the \"close\" event to let the interceptor handle\n // closures initiated by the original server.\n realWebSocket.addEventListener(\n 'close',\n (event) => {\n this.handleRealClose(event)\n },\n {\n signal: this.realCloseController.signal,\n }\n )\n\n realWebSocket.addEventListener('error', () => {\n const errorEvent = bindEvent(\n realWebSocket,\n new Event('error', { cancelable: true })\n )\n\n // Emit the \"error\" event on the `server` connection\n // to let the interceptor react to original server errors.\n this[kEmitter].dispatchEvent(errorEvent)\n\n // If the error event from the original server hasn't been prevented,\n // forward it to the underlying client.\n if (!errorEvent.defaultPrevented) {\n this.client.dispatchEvent(bindEvent(this.client, new Event('error')))\n }\n })\n\n this.realWebSocket = realWebSocket\n }\n\n /**\n * Listen for the incoming events from the original WebSocket server.\n */\n public addEventListener<EventType extends keyof WebSocketServerEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void {\n if (!Reflect.has(listener, kBoundListener)) {\n const boundListener = listener.bind(this.client)\n\n // Store the bound listener on the original listener\n // so the exact bound function can be accessed in \"removeEventListener()\".\n Object.defineProperty(listener, kBoundListener, {\n value: boundListener,\n enumerable: false,\n })\n }\n\n this[kEmitter].addEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Remove the listener for the given event.\n */\n public removeEventListener<EventType extends keyof WebSocketServerEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void {\n this[kEmitter].removeEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Send data to the original WebSocket server.\n * @example\n * server.send('hello')\n * server.send(new Blob(['hello']))\n * server.send(new TextEncoder().encode('hello'))\n */\n public send(data: WebSocketData): void {\n this[kSend](data)\n }\n\n private [kSend](data: WebSocketData): void {\n const { realWebSocket } = this\n\n invariant(\n realWebSocket,\n 'Failed to call \"server.send()\" for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?',\n this.client.url\n )\n\n // Silently ignore writes on the closed original WebSocket.\n if (\n realWebSocket.readyState === WebSocket.CLOSING ||\n realWebSocket.readyState === WebSocket.CLOSED\n ) {\n return\n }\n\n // Delegate the send to when the original connection is open.\n // Unlike the mock, connecting to the original server may take time\n // so we cannot call this on the next tick.\n if (realWebSocket.readyState === WebSocket.CONNECTING) {\n realWebSocket.addEventListener(\n 'open',\n () => {\n realWebSocket.send(data)\n },\n { once: true }\n )\n return\n }\n\n // Send the data to the original WebSocket server.\n realWebSocket.send(data)\n }\n\n /**\n * Close the actual server connection.\n */\n public close(): void {\n const { realWebSocket } = this\n\n invariant(\n realWebSocket,\n 'Failed to close server connection for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?',\n this.client.url\n )\n\n // Remove the \"close\" event listener from the server\n // so it doesn't close the underlying WebSocket client\n // when you call \"server.close()\". This also prevents the\n // `close` event on the `server` connection from being dispatched twice.\n this.realCloseController.abort()\n\n if (\n realWebSocket.readyState === WebSocket.CLOSING ||\n realWebSocket.readyState === WebSocket.CLOSED\n ) {\n return\n }\n\n // Close the actual client connection.\n realWebSocket.close()\n\n // Dispatch the \"close\" event on the `server` connection.\n queueMicrotask(() => {\n this[kEmitter].dispatchEvent(\n bindEvent(\n this.realWebSocket,\n new CancelableCloseEvent('close', {\n /**\n * @note `server.close()` in the interceptor\n * always results in clean closures.\n */\n code: 1000,\n cancelable: true,\n })\n )\n )\n })\n }\n\n private handleIncomingMessage(event: MessageEvent<WebSocketData>): void {\n // Clone the event to dispatch it on this class\n // once again and prevent the \"already being dispatched\"\n // exception. Clone it here so we can observe this event\n // being prevented in the \"server.on()\" listeners.\n const messageEvent = bindEvent(\n event.target,\n new CancelableMessageEvent('message', {\n data: event.data,\n origin: event.origin,\n cancelable: true,\n })\n )\n\n /**\n * @note Emit \"message\" event on the server connection\n * instance to let the interceptor know about these\n * incoming events from the original server. In that listener,\n * the interceptor can modify or skip the event forwarding\n * to the mock WebSocket instance.\n */\n this[kEmitter].dispatchEvent(messageEvent)\n\n /**\n * @note Forward the incoming server events to the client.\n * Preventing the default on the message event stops this.\n */\n if (!messageEvent.defaultPrevented) {\n this.client.dispatchEvent(\n bindEvent(\n /**\n * @note Bind the forwarded original server events\n * to the mock WebSocket instance so it would\n * dispatch them straight away.\n */\n this.client,\n // Clone the message event again to prevent\n // the \"already being dispatched\" exception.\n new MessageEvent('message', {\n data: event.data,\n origin: event.origin,\n })\n )\n )\n }\n }\n\n private handleMockClose(_event: Event): void {\n // Close the original connection if the mock client closes.\n if (this.realWebSocket) {\n this.realWebSocket.close()\n }\n }\n\n private handleRealClose(event: CloseEvent): void {\n // For closures originating from the original server,\n // remove the \"close\" listener from the mock client.\n // original close -> (?) client[kClose]() --X--> \"close\" (again).\n this.mockCloseController.abort()\n\n const closeEvent = bindEvent(\n this.realWebSocket,\n new CancelableCloseEvent('close', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n cancelable: true,\n })\n )\n\n this[kEmitter].dispatchEvent(closeEvent)\n\n // If the close event from the server hasn't been prevented,\n // forward the closure to the mock client.\n if (!closeEvent.defaultPrevented) {\n // Close the intercepted client forcefully to\n // allow non-configurable status codes from the server.\n // If the socket has been closed by now, no harm calling\n // this again—it will have no effect.\n this.client[kClose](event.code, event.reason)\n }\n }\n}\n","import { bindEvent } from './utils/bindEvent'\nimport {\n StrictEventListenerOrEventListenerObject,\n WebSocketData,\n WebSocketTransport,\n WebSocketTransportEventMap,\n} from './WebSocketTransport'\nimport { kOnSend, kClose, WebSocketOverride } from './WebSocketOverride'\nimport { CancelableMessageEvent, CloseEvent } from './utils/events'\n\n/**\n * Abstraction over the given mock `WebSocket` instance that allows\n * for controlling that instance (e.g. sending and receiving messages).\n */\nexport class WebSocketClassTransport\n extends EventTarget\n implements WebSocketTransport\n{\n constructor(protected readonly socket: WebSocketOverride) {\n super()\n\n // Emit the \"close\" event on the transport if the close\n // originates from the WebSocket client. E.g. the application\n // calls \"ws.close()\", not the interceptor.\n this.socket.addEventListener('close', (event) => {\n this.dispatchEvent(bindEvent(this.socket, new CloseEvent('close', event)))\n })\n\n /**\n * Emit the \"outgoing\" event on the transport\n * whenever the WebSocket client sends data (\"ws.send()\").\n */\n this.socket[kOnSend] = (data) => {\n this.dispatchEvent(\n bindEvent(\n this.socket,\n // Dispatch this as cancelable because \"client\" connection\n // re-creates this message event (cannot dispatch the same event).\n new CancelableMessageEvent('outgoing', {\n data,\n origin: this.socket.url,\n cancelable: true,\n })\n )\n )\n }\n }\n\n public addEventListener<EventType extends keyof WebSocketTransportEventMap>(\n type: EventType,\n callback: StrictEventListenerOrEventListenerObject<\n WebSocketTransportEventMap[EventType]\n > | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n return super.addEventListener(type, callback as EventListener, options)\n }\n\n public dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(\n event: WebSocketTransportEventMap[EventType]\n ): boolean {\n return super.dispatchEvent(event)\n }\n\n public send(data: WebSocketData): void {\n queueMicrotask(() => {\n if (\n this.socket.readyState === this.socket.CLOSING ||\n this.socket.readyState === this.socket.CLOSED\n ) {\n return\n }\n\n const dispatchEvent = () => {\n this.socket.dispatchEvent(\n bindEvent(\n /**\n * @note Setting this event's \"target\" to the\n * WebSocket override instance is important.\n * This way it can tell apart original incoming events\n * (must be forwarded to the transport) from the\n * mocked message events like the one below\n * (must be dispatched on the client instance).\n */\n this.socket,\n new MessageEvent('message', {\n data,\n origin: this.socket.url,\n })\n )\n )\n }\n\n if (this.socket.readyState === this.socket.CONNECTING) {\n this.socket.addEventListener(\n 'open',\n () => {\n dispatchEvent()\n },\n { once: true }\n )\n } else {\n dispatchEvent()\n }\n })\n }\n\n public close(code: number, reason?: string): void {\n /**\n * @note Call the internal close method directly\n * to allow closing the connection with the status codes\n * that are non-configurable by the user (> 1000 <= 1015).\n */\n this.socket[kClose](code, reason)\n }\n}\n","import { Interceptor } from '../../Interceptor'\nimport {\n WebSocketClientConnectionProtocol,\n WebSocketClientConnection,\n type WebSocketClientEventMap,\n} from './WebSocketClientConnection'\nimport {\n WebSocketServerConnectionProtocol,\n WebSocketServerConnection,\n type WebSocketServerEventMap,\n} from './WebSocketServerConnection'\nimport { WebSocketClassTransport } from './WebSocketClassTransport'\nimport {\n kClose,\n kPassthroughPromise,\n WebSocketOverride,\n} from './WebSocketOverride'\nimport { bindEvent } from './utils/bindEvent'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport {\n type WebSocketData,\n type WebSocketTransport,\n} from './WebSocketTransport'\nexport {\n WebSocketClientEventMap,\n WebSocketClientConnectionProtocol,\n WebSocketClientConnection,\n WebSocketServerEventMap,\n WebSocketServerConnectionProtocol,\n WebSocketServerConnection,\n}\n\nexport {\n CloseEvent,\n CancelableCloseEvent,\n CancelableMessageEvent,\n} from './utils/events'\n\nexport type WebSocketEventMap = {\n connection: [args: WebSocketConnectionData]\n}\n\nexport type WebSocketConnectionData = {\n /**\n * The incoming WebSocket client connection.\n */\n client: WebSocketClientConnection\n\n /**\n * The original WebSocket server connection.\n */\n server: WebSocketServerConnection\n\n /**\n * The connection information.\n */\n info: {\n /**\n * The protocols supported by the WebSocket client.\n */\n protocols: string | Array<string> | undefined\n }\n}\n\n/**\n * Intercept the outgoing WebSocket connections created using\n * the global `WebSocket` class.\n */\nexport class WebSocketInterceptor extends Interceptor<WebSocketEventMap> {\n static symbol = Symbol('websocket')\n\n constructor() {\n super(WebSocketInterceptor.symbol)\n }\n\n protected checkEnvironment(): boolean {\n return hasConfigurableGlobal('WebSocket')\n }\n\n protected setup(): void {\n const logger = this.logger.extend('setup')\n\n const WebSocketProxy = new Proxy(globalThis.WebSocket, {\n construct: (\n target,\n args: ConstructorParameters<typeof globalThis.WebSocket>,\n newTarget\n ) => {\n const [url, protocols] = args\n\n const createConnection = (): WebSocket => {\n return Reflect.construct(target, args, newTarget)\n }\n\n // All WebSocket instances are mocked and don't forward\n // any events to the original server (no connection established).\n // To forward the events, the user must use the \"server.send()\" API.\n const socket = new WebSocketOverride(url, protocols)\n const transport = new WebSocketClassTransport(socket)\n\n // Emit the \"connection\" event to the interceptor on the next tick\n // so the client can modify WebSocket options, like \"binaryType\"\n // while the connection is already pending.\n queueMicrotask(async () => {\n try {\n const server = new WebSocketServerConnection(\n socket,\n transport,\n createConnection\n )\n\n const hasConnectionListeners =\n this.emitter.listenerCount('connection') > 0\n\n // The \"globalThis.WebSocket\" class stands for\n // the client-side connection. Assume it's established\n // as soon as the WebSocket instance is constructed.\n await emitAsync(this.emitter, 'connection', {\n client: new WebSocketClientConnection(socket, transport),\n server,\n info: {\n protocols,\n },\n })\n\n if (hasConnectionListeners) {\n socket[kPassthroughPromise].resolve(false)\n } else {\n socket[kPassthroughPromise].resolve(true)\n\n server.connect()\n\n // Forward the \"open\" event from the original server\n // to the mock WebSocket client in the case of a passthrough connection.\n server.addEventListener('open', () => {\n socket.dispatchEvent(bindEvent(socket, new Event('open')))\n\n // Forward the original connection protocol to the\n // mock WebSocket client.\n if (server['realWebSocket']) {\n socket.protocol = server['realWebSocket'].protocol\n }\n })\n }\n } catch (error) {\n /**\n * @note Translate unhandled exceptions during the connection\n * handling (i.e. interceptor exceptions) as WebSocket connection\n * closures with error. This prevents from the exceptions occurring\n * in `queueMicrotask` from being process-wide and uncatchable.\n */\n if (error instanceof Error) {\n socket.dispatchEvent(new Event('error'))\n\n // No need to close the connection if it's already being closed.\n // E.g. the interceptor called `client.close()` and then threw an error.\n if (\n socket.readyState !== WebSocket.CLOSING &&\n socket.readyState !== WebSocket.CLOSED\n ) {\n socket[kClose](1011, error.message, false)\n }\n\n console.error(error)\n }\n }\n })\n\n return socket\n },\n })\n\n logger.info('patching global WebSocket...')\n\n this.subscriptions.push(\n globalsRegistry.replaceGlobal('WebSocket', WebSocketProxy)\n )\n\n logger.info('global WebSocket patched!', globalThis.WebSocket.name)\n }\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.cjs","names":["kEmitter","kBoundListener","socket: WebSocket","transport: WebSocketTransport","createRequestId","resolveWebSocketUrl","DeferredPromise","client: WebSocketOverride","transport: WebSocketClassTransport","createConnection: () => WebSocket","socket: WebSocketOverride","Interceptor","hasConfigurableGlobal","emitAsync","patchesRegistry"],"sources":["../../../../src/interceptors/WebSocket/utils/bindEvent.ts","../../../../src/interceptors/WebSocket/utils/events.ts","../../../../src/interceptors/WebSocket/WebSocketClientConnection.ts","../../../../src/interceptors/WebSocket/WebSocketOverride.ts","../../../../src/interceptors/WebSocket/WebSocketServerConnection.ts","../../../../src/interceptors/WebSocket/WebSocketClassTransport.ts","../../../../src/interceptors/WebSocket/index.ts"],"sourcesContent":["type EventWithTarget<E extends Event, T> = E & { target: T }\n\nexport function bindEvent<E extends Event, T>(\n target: T,\n event: E\n): EventWithTarget<E, T> {\n Object.defineProperties(event, {\n target: {\n value: target,\n enumerable: true,\n writable: true,\n },\n currentTarget: {\n value: target,\n enumerable: true,\n writable: true,\n },\n })\n\n return event as EventWithTarget<E, T>\n}\n","const kCancelable = Symbol('kCancelable')\nconst kDefaultPrevented = Symbol('kDefaultPrevented')\n\n/**\n * A `MessageEvent` superset that supports event cancellation\n * in Node.js. It's rather non-intrusive so it can be safely\n * used in the browser as well.\n *\n * @see https://github.com/nodejs/node/issues/51767\n */\nexport class CancelableMessageEvent<T = any> extends MessageEvent<T> {\n [kCancelable]: boolean;\n [kDefaultPrevented]: boolean\n\n constructor(type: string, init: MessageEventInit<T>) {\n super(type, init)\n this[kCancelable] = !!init.cancelable\n this[kDefaultPrevented] = false\n }\n\n get cancelable() {\n return this[kCancelable]\n }\n\n set cancelable(nextCancelable) {\n this[kCancelable] = nextCancelable\n }\n\n get defaultPrevented() {\n return this[kDefaultPrevented]\n }\n\n set defaultPrevented(nextDefaultPrevented) {\n this[kDefaultPrevented] = nextDefaultPrevented\n }\n\n public preventDefault(): void {\n if (this.cancelable && !this[kDefaultPrevented]) {\n this[kDefaultPrevented] = true\n }\n }\n}\n\ninterface CloseEventInit extends EventInit {\n code?: number\n reason?: string\n wasClean?: boolean\n}\n\nexport class CloseEvent extends Event {\n public code: number\n public reason: string\n public wasClean: boolean\n\n constructor(type: string, init: CloseEventInit = {}) {\n super(type, init)\n this.code = init.code === undefined ? 0 : init.code\n this.reason = init.reason === undefined ? '' : init.reason\n this.wasClean = init.wasClean === undefined ? false : init.wasClean\n }\n}\n\nexport class CancelableCloseEvent extends CloseEvent {\n [kCancelable]: boolean;\n [kDefaultPrevented]: boolean\n\n constructor(type: string, init: CloseEventInit = {}) {\n super(type, init)\n this[kCancelable] = !!init.cancelable\n this[kDefaultPrevented] = false\n }\n\n get cancelable() {\n return this[kCancelable]\n }\n\n set cancelable(nextCancelable) {\n this[kCancelable] = nextCancelable\n }\n\n get defaultPrevented() {\n return this[kDefaultPrevented]\n }\n\n set defaultPrevented(nextDefaultPrevented) {\n this[kDefaultPrevented] = nextDefaultPrevented\n }\n\n public preventDefault(): void {\n if (this.cancelable && !this[kDefaultPrevented]) {\n this[kDefaultPrevented] = true\n }\n }\n}\n","import type { WebSocketData, WebSocketTransport } from './WebSocketTransport'\nimport type { WebSocketEventListener } from './WebSocketOverride'\nimport { bindEvent } from './utils/bindEvent'\nimport { CancelableMessageEvent, CloseEvent } from './utils/events'\nimport { createRequestId } from '../../createRequestId'\n\nconst kEmitter = Symbol('kEmitter')\nconst kBoundListener = Symbol('kBoundListener')\n\nexport interface WebSocketClientEventMap {\n message: MessageEvent<WebSocketData>\n close: CloseEvent\n}\n\nexport abstract class WebSocketClientConnectionProtocol {\n abstract id: string\n abstract url: URL\n public abstract send(data: WebSocketData): void\n public abstract close(code?: number, reason?: string): void\n\n public abstract addEventListener<\n EventType extends keyof WebSocketClientEventMap,\n >(\n type: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void\n\n public abstract removeEventListener<\n EventType extends keyof WebSocketClientEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void\n}\n\n/**\n * The WebSocket client instance represents an incoming\n * client connection. The user can control the connection,\n * send and receive events.\n */\nexport class WebSocketClientConnection implements WebSocketClientConnectionProtocol {\n public readonly id: string\n public readonly url: URL\n\n private [kEmitter]: EventTarget\n\n constructor(\n public readonly socket: WebSocket,\n private readonly transport: WebSocketTransport\n ) {\n this.id = createRequestId()\n this.url = new URL(socket.url)\n this[kEmitter] = new EventTarget()\n\n // Emit outgoing client data (\"ws.send()\") as \"message\"\n // events on the \"client\" connection.\n this.transport.addEventListener('outgoing', (event) => {\n const message = bindEvent(\n this.socket,\n new CancelableMessageEvent('message', {\n data: event.data,\n origin: event.origin,\n cancelable: true,\n })\n )\n\n this[kEmitter].dispatchEvent(message)\n\n // This is a bit silly but forward the cancellation state\n // of the \"client\" message event to the \"outgoing\" transport event.\n // This way, other agens (like \"server\" connection) can know\n // whether the client listener has pervented the default.\n if (message.defaultPrevented) {\n event.preventDefault()\n }\n })\n\n /**\n * Emit the \"close\" event on the \"client\" connection\n * whenever the underlying transport is closed.\n * @note \"client.close()\" does NOT dispatch the \"close\"\n * event on the WebSocket because it uses non-configurable\n * close status code. Thus, we listen to the transport\n * instead of the WebSocket's \"close\" event.\n */\n this.transport.addEventListener('close', (event) => {\n this[kEmitter].dispatchEvent(\n bindEvent(this.socket, new CloseEvent('close', event))\n )\n })\n }\n\n /**\n * Listen for the outgoing events from the connected WebSocket client.\n */\n public addEventListener<EventType extends keyof WebSocketClientEventMap>(\n type: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void {\n if (!Reflect.has(listener, kBoundListener)) {\n const boundListener = listener.bind(this.socket)\n\n // Store the bound listener on the original listener\n // so the exact bound function can be accessed in \"removeEventListener()\".\n Object.defineProperty(listener, kBoundListener, {\n value: boundListener,\n enumerable: false,\n configurable: false,\n })\n }\n\n this[kEmitter].addEventListener(\n type,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Removes the listener for the given event.\n */\n public removeEventListener<EventType extends keyof WebSocketClientEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void {\n this[kEmitter].removeEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Send data to the connected client.\n */\n public send(data: WebSocketData): void {\n this.transport.send(data)\n }\n\n /**\n * Close the WebSocket connection.\n * @param {number} code A status code (see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).\n * @param {string} reason A custom connection close reason.\n */\n public close(code?: number, reason?: string): void {\n this.transport.close(code, reason)\n }\n}\n","import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport type { WebSocketData } from './WebSocketTransport'\nimport { bindEvent } from './utils/bindEvent'\nimport { CloseEvent } from './utils/events'\nimport { resolveWebSocketUrl } from '../../utils/resolveWebSocketUrl'\n\nexport type WebSocketEventListener<\n EventType extends WebSocketEventMap[keyof WebSocketEventMap] = Event,\n> = (this: WebSocket, event: EventType) => void\n\nconst WEBSOCKET_CLOSE_CODE_RANGE_ERROR =\n 'InvalidAccessError: close code out of user configurable range'\n\nexport const kPassthroughPromise = Symbol('kPassthroughPromise')\nexport const kOnSend = Symbol('kOnSend')\nexport const kClose = Symbol('kClose')\n\nexport class WebSocketOverride extends EventTarget implements WebSocket {\n static readonly CONNECTING = 0\n static readonly OPEN = 1\n static readonly CLOSING = 2\n static readonly CLOSED = 3\n readonly CONNECTING = 0\n readonly OPEN = 1\n readonly CLOSING = 2\n readonly CLOSED = 3\n\n public url: string\n public protocol: string\n public extensions: string\n public binaryType: BinaryType\n public readyState: WebSocket['readyState']\n public bufferedAmount: number\n\n private _onopen: WebSocketEventListener | null = null\n private _onmessage: WebSocketEventListener<\n MessageEvent<WebSocketData>\n > | null = null\n private _onerror: WebSocketEventListener | null = null\n private _onclose: WebSocketEventListener<CloseEvent> | null = null\n\n private [kPassthroughPromise]: DeferredPromise<boolean>\n private [kOnSend]?: (data: WebSocketData) => void\n\n constructor(url: string | URL, protocols?: string | Array<string>) {\n super()\n this.url = resolveWebSocketUrl(url)\n this.protocol = ''\n this.extensions = ''\n this.binaryType = 'blob'\n this.readyState = this.CONNECTING\n this.bufferedAmount = 0\n\n this[kPassthroughPromise] = new DeferredPromise<boolean>()\n\n queueMicrotask(async () => {\n if (await this[kPassthroughPromise]) {\n return\n }\n\n this.protocol =\n typeof protocols === 'string'\n ? protocols\n : Array.isArray(protocols) && protocols.length > 0\n ? protocols[0]\n : ''\n\n /**\n * @note Check that nothing has prevented this connection\n * (e.g. called `client.close()` in the connection listener).\n * If the connection has been prevented, never dispatch the open event,.\n */\n if (this.readyState === this.CONNECTING) {\n this.readyState = this.OPEN\n this.dispatchEvent(bindEvent(this, new Event('open')))\n }\n })\n }\n\n set onopen(listener: WebSocketEventListener | null) {\n this.removeEventListener('open', this._onopen)\n this._onopen = listener\n if (listener !== null) {\n this.addEventListener('open', listener)\n }\n }\n get onopen(): WebSocketEventListener | null {\n return this._onopen\n }\n\n set onmessage(\n listener: WebSocketEventListener<MessageEvent<WebSocketData>> | null\n ) {\n this.removeEventListener(\n 'message',\n this._onmessage as WebSocketEventListener\n )\n this._onmessage = listener\n if (listener !== null) {\n this.addEventListener('message', listener)\n }\n }\n get onmessage(): WebSocketEventListener<MessageEvent<WebSocketData>> | null {\n return this._onmessage\n }\n\n set onerror(listener: WebSocketEventListener | null) {\n this.removeEventListener('error', this._onerror)\n this._onerror = listener\n if (listener !== null) {\n this.addEventListener('error', listener)\n }\n }\n get onerror(): WebSocketEventListener | null {\n return this._onerror\n }\n\n set onclose(listener: WebSocketEventListener<CloseEvent> | null) {\n this.removeEventListener('close', this._onclose as WebSocketEventListener)\n this._onclose = listener\n if (listener !== null) {\n this.addEventListener('close', listener)\n }\n }\n get onclose(): WebSocketEventListener<CloseEvent> | null {\n return this._onclose\n }\n\n /**\n * @see https://websockets.spec.whatwg.org/#ref-for-dom-websocket-send%E2%91%A0\n */\n public send(data: WebSocketData): void {\n if (this.readyState === this.CONNECTING) {\n this.close()\n throw new DOMException('InvalidStateError')\n }\n\n // Sending when the socket is about to close\n // discards the sent data.\n if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {\n return\n }\n\n // Buffer the data to send in this even loop\n // but send it in the next.\n this.bufferedAmount += getDataSize(data)\n\n queueMicrotask(() => {\n // This is a bit optimistic but since no actual data transfer\n // is involved, all the data will be \"sent\" on the next tick.\n this.bufferedAmount = 0\n\n /**\n * @note Notify the parent about outgoing data.\n * This notifies the transport and the connection\n * listens to the outgoing data to emit the \"message\" event.\n */\n this[kOnSend]?.(data)\n })\n }\n\n public close(code: number = 1000, reason?: string): void {\n invariant(code, WEBSOCKET_CLOSE_CODE_RANGE_ERROR)\n invariant(\n code === 1000 || (code >= 3000 && code <= 4999),\n WEBSOCKET_CLOSE_CODE_RANGE_ERROR\n )\n\n this[kClose](code, reason)\n }\n\n private [kClose](\n code: number = 1000,\n reason?: string,\n wasClean = true\n ): void {\n /**\n * @note Move this check here so that even internal closures,\n * like those triggered by the `server` connection, are not\n * performed twice.\n */\n if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {\n return\n }\n\n this.readyState = this.CLOSING\n\n queueMicrotask(() => {\n this.readyState = this.CLOSED\n\n this.dispatchEvent(\n bindEvent(\n this,\n new CloseEvent('close', {\n code,\n reason,\n wasClean,\n })\n )\n )\n\n // Remove all event listeners once the socket is closed.\n this._onopen = null\n this._onmessage = null\n this._onerror = null\n this._onclose = null\n })\n }\n\n public addEventListener<K extends keyof WebSocketEventMap>(\n type: K,\n listener: (this: WebSocket, event: WebSocketEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions\n ): void\n public addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ): void\n public addEventListener(\n type: unknown,\n listener: unknown,\n options?: unknown\n ): void {\n return super.addEventListener(\n type as string,\n listener as EventListener,\n options as AddEventListenerOptions\n )\n }\n\n removeEventListener<K extends keyof WebSocketEventMap>(\n type: K,\n callback: EventListenerOrEventListenerObject | null,\n options?: boolean | EventListenerOptions\n ): void {\n return super.removeEventListener(type, callback, options)\n }\n}\n\nfunction getDataSize(data: WebSocketData): number {\n if (typeof data === 'string') {\n return data.length\n }\n\n if (data instanceof Blob) {\n return data.size\n }\n\n return data.byteLength\n}\n","import { invariant } from 'outvariant'\nimport {\n kClose,\n WebSocketEventListener,\n WebSocketOverride,\n} from './WebSocketOverride'\nimport type { WebSocketData } from './WebSocketTransport'\nimport type { WebSocketClassTransport } from './WebSocketClassTransport'\nimport { bindEvent } from './utils/bindEvent'\nimport {\n CancelableMessageEvent,\n CancelableCloseEvent,\n CloseEvent,\n} from './utils/events'\n\nconst kEmitter = Symbol('kEmitter')\nconst kBoundListener = Symbol('kBoundListener')\nconst kSend = Symbol('kSend')\n\nexport interface WebSocketServerEventMap {\n open: Event\n message: MessageEvent<WebSocketData>\n error: Event\n close: CloseEvent\n}\n\nexport abstract class WebSocketServerConnectionProtocol {\n public abstract connect(): void\n public abstract send(data: WebSocketData): void\n public abstract close(): void\n\n public abstract addEventListener<\n EventType extends keyof WebSocketServerEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void\n\n public abstract removeEventListener<\n EventType extends keyof WebSocketServerEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void\n}\n\n/**\n * The WebSocket server instance represents the actual production\n * WebSocket server connection. It's idle by default but you can\n * establish it by calling `server.connect()`.\n */\nexport class WebSocketServerConnection implements WebSocketServerConnectionProtocol {\n /**\n * A WebSocket instance connected to the original server.\n */\n private realWebSocket?: WebSocket\n private mockCloseController: AbortController\n private realCloseController: AbortController\n private [kEmitter]: EventTarget\n\n constructor(\n private readonly client: WebSocketOverride,\n private readonly transport: WebSocketClassTransport,\n private readonly createConnection: () => WebSocket\n ) {\n this[kEmitter] = new EventTarget()\n this.mockCloseController = new AbortController()\n this.realCloseController = new AbortController()\n\n // Automatically forward outgoing client events\n // to the actual server unless the outgoing message event\n // has been prevented. The \"outgoing\" transport event it\n // dispatched by the \"client\" connection.\n this.transport.addEventListener('outgoing', (event) => {\n // Ignore client messages if the server connection\n // hasn't been established yet. Nowhere to forward.\n if (typeof this.realWebSocket === 'undefined') {\n return\n }\n\n // Every outgoing client message can prevent this forwarding\n // by preventing the default of the outgoing message event.\n // This listener will be added before user-defined listeners,\n // so execute the logic on the next tick.\n queueMicrotask(() => {\n if (!event.defaultPrevented) {\n /**\n * @note Use the internal send mechanism so consumers can tell\n * apart direct user calls to `server.send()` and internal calls.\n * E.g. MSW has to ignore this internal call to log out messages correctly.\n */\n this[kSend](event.data)\n }\n })\n })\n\n this.transport.addEventListener(\n 'incoming',\n this.handleIncomingMessage.bind(this)\n )\n }\n\n /**\n * The `WebSocket` instance connected to the original server.\n * Accessing this before calling `server.connect()` will throw.\n */\n public get socket(): WebSocket {\n invariant(\n this.realWebSocket,\n 'Cannot access \"socket\" on the original WebSocket server object: the connection is not open. Did you forget to call `server.connect()`?'\n )\n\n return this.realWebSocket\n }\n\n /**\n * Open connection to the original WebSocket server.\n */\n public connect(): void {\n invariant(\n !this.realWebSocket || this.realWebSocket.readyState !== WebSocket.OPEN,\n 'Failed to call \"connect()\" on the original WebSocket instance: the connection already open'\n )\n\n const realWebSocket = this.createConnection()\n\n // Inherit the binary type from the mock WebSocket client.\n realWebSocket.binaryType = this.client.binaryType\n\n // Allow the interceptor to listen to when the server connection\n // has been established. This isn't necessary to operate with the connection\n // but may be beneficial in some cases (like conditionally adding logging).\n realWebSocket.addEventListener(\n 'open',\n (event) => {\n this[kEmitter].dispatchEvent(\n bindEvent(this.realWebSocket!, new Event('open', event))\n )\n },\n { once: true }\n )\n\n realWebSocket.addEventListener('message', (event) => {\n // Dispatch the \"incoming\" transport event instead of\n // invoking the internal handler directly. This way,\n // anyone can listen to the \"incoming\" event but this\n // class is the one resulting in it.\n this.transport.dispatchEvent(\n bindEvent(\n this.realWebSocket!,\n new MessageEvent('incoming', {\n data: event.data,\n origin: event.origin,\n })\n )\n )\n })\n\n // Close the original connection when the mock client closes.\n // E.g. \"client.close()\" was called. This is never forwarded anywhere.\n this.client.addEventListener(\n 'close',\n (event) => {\n this.handleMockClose(event)\n },\n {\n signal: this.mockCloseController.signal,\n }\n )\n\n // Forward the \"close\" event to let the interceptor handle\n // closures initiated by the original server.\n realWebSocket.addEventListener(\n 'close',\n (event) => {\n this.handleRealClose(event)\n },\n {\n signal: this.realCloseController.signal,\n }\n )\n\n realWebSocket.addEventListener('error', () => {\n const errorEvent = bindEvent(\n realWebSocket,\n new Event('error', { cancelable: true })\n )\n\n // Emit the \"error\" event on the `server` connection\n // to let the interceptor react to original server errors.\n this[kEmitter].dispatchEvent(errorEvent)\n\n // If the error event from the original server hasn't been prevented,\n // forward it to the underlying client.\n if (!errorEvent.defaultPrevented) {\n this.client.dispatchEvent(bindEvent(this.client, new Event('error')))\n }\n })\n\n this.realWebSocket = realWebSocket\n }\n\n /**\n * Listen for the incoming events from the original WebSocket server.\n */\n public addEventListener<EventType extends keyof WebSocketServerEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void {\n if (!Reflect.has(listener, kBoundListener)) {\n const boundListener = listener.bind(this.client)\n\n // Store the bound listener on the original listener\n // so the exact bound function can be accessed in \"removeEventListener()\".\n Object.defineProperty(listener, kBoundListener, {\n value: boundListener,\n enumerable: false,\n })\n }\n\n this[kEmitter].addEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Remove the listener for the given event.\n */\n public removeEventListener<EventType extends keyof WebSocketServerEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void {\n this[kEmitter].removeEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Send data to the original WebSocket server.\n * @example\n * server.send('hello')\n * server.send(new Blob(['hello']))\n * server.send(new TextEncoder().encode('hello'))\n */\n public send(data: WebSocketData): void {\n this[kSend](data)\n }\n\n private [kSend](data: WebSocketData): void {\n const { realWebSocket } = this\n\n invariant(\n realWebSocket,\n 'Failed to call \"server.send()\" for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?',\n this.client.url\n )\n\n // Silently ignore writes on the closed original WebSocket.\n if (\n realWebSocket.readyState === WebSocket.CLOSING ||\n realWebSocket.readyState === WebSocket.CLOSED\n ) {\n return\n }\n\n // Delegate the send to when the original connection is open.\n // Unlike the mock, connecting to the original server may take time\n // so we cannot call this on the next tick.\n if (realWebSocket.readyState === WebSocket.CONNECTING) {\n realWebSocket.addEventListener(\n 'open',\n () => {\n realWebSocket.send(data)\n },\n { once: true }\n )\n return\n }\n\n // Send the data to the original WebSocket server.\n realWebSocket.send(data)\n }\n\n /**\n * Close the actual server connection.\n */\n public close(): void {\n const { realWebSocket } = this\n\n invariant(\n realWebSocket,\n 'Failed to close server connection for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?',\n this.client.url\n )\n\n // Remove the \"close\" event listener from the server\n // so it doesn't close the underlying WebSocket client\n // when you call \"server.close()\". This also prevents the\n // `close` event on the `server` connection from being dispatched twice.\n this.realCloseController.abort()\n\n if (\n realWebSocket.readyState === WebSocket.CLOSING ||\n realWebSocket.readyState === WebSocket.CLOSED\n ) {\n return\n }\n\n // Close the actual client connection.\n realWebSocket.close()\n\n // Dispatch the \"close\" event on the `server` connection.\n queueMicrotask(() => {\n this[kEmitter].dispatchEvent(\n bindEvent(\n this.realWebSocket,\n new CancelableCloseEvent('close', {\n /**\n * @note `server.close()` in the interceptor\n * always results in clean closures.\n */\n code: 1000,\n cancelable: true,\n })\n )\n )\n })\n }\n\n private handleIncomingMessage(event: MessageEvent<WebSocketData>): void {\n // Clone the event to dispatch it on this class\n // once again and prevent the \"already being dispatched\"\n // exception. Clone it here so we can observe this event\n // being prevented in the \"server.on()\" listeners.\n const messageEvent = bindEvent(\n event.target,\n new CancelableMessageEvent('message', {\n data: event.data,\n origin: event.origin,\n cancelable: true,\n })\n )\n\n /**\n * @note Emit \"message\" event on the server connection\n * instance to let the interceptor know about these\n * incoming events from the original server. In that listener,\n * the interceptor can modify or skip the event forwarding\n * to the mock WebSocket instance.\n */\n this[kEmitter].dispatchEvent(messageEvent)\n\n /**\n * @note Forward the incoming server events to the client.\n * Preventing the default on the message event stops this.\n */\n if (!messageEvent.defaultPrevented) {\n this.client.dispatchEvent(\n bindEvent(\n /**\n * @note Bind the forwarded original server events\n * to the mock WebSocket instance so it would\n * dispatch them straight away.\n */\n this.client,\n // Clone the message event again to prevent\n // the \"already being dispatched\" exception.\n new MessageEvent('message', {\n data: event.data,\n origin: event.origin,\n })\n )\n )\n }\n }\n\n private handleMockClose(_event: Event): void {\n // Close the original connection if the mock client closes.\n if (this.realWebSocket) {\n this.realWebSocket.close()\n }\n }\n\n private handleRealClose(event: CloseEvent): void {\n // For closures originating from the original server,\n // remove the \"close\" listener from the mock client.\n // original close -> (?) client[kClose]() --X--> \"close\" (again).\n this.mockCloseController.abort()\n\n const closeEvent = bindEvent(\n this.realWebSocket,\n new CancelableCloseEvent('close', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n cancelable: true,\n })\n )\n\n this[kEmitter].dispatchEvent(closeEvent)\n\n // If the close event from the server hasn't been prevented,\n // forward the closure to the mock client.\n if (!closeEvent.defaultPrevented) {\n // Close the intercepted client forcefully to\n // allow non-configurable status codes from the server.\n // If the socket has been closed by now, no harm calling\n // this again—it will have no effect.\n this.client[kClose](event.code, event.reason)\n }\n }\n}\n","import { bindEvent } from './utils/bindEvent'\nimport {\n StrictEventListenerOrEventListenerObject,\n WebSocketData,\n WebSocketTransport,\n WebSocketTransportEventMap,\n} from './WebSocketTransport'\nimport { kOnSend, kClose, WebSocketOverride } from './WebSocketOverride'\nimport { CancelableMessageEvent, CloseEvent } from './utils/events'\n\n/**\n * Abstraction over the given mock `WebSocket` instance that allows\n * for controlling that instance (e.g. sending and receiving messages).\n */\nexport class WebSocketClassTransport\n extends EventTarget\n implements WebSocketTransport\n{\n constructor(protected readonly socket: WebSocketOverride) {\n super()\n\n // Emit the \"close\" event on the transport if the close\n // originates from the WebSocket client. E.g. the application\n // calls \"ws.close()\", not the interceptor.\n this.socket.addEventListener('close', (event) => {\n this.dispatchEvent(bindEvent(this.socket, new CloseEvent('close', event)))\n })\n\n /**\n * Emit the \"outgoing\" event on the transport\n * whenever the WebSocket client sends data (\"ws.send()\").\n */\n this.socket[kOnSend] = (data) => {\n this.dispatchEvent(\n bindEvent(\n this.socket,\n // Dispatch this as cancelable because \"client\" connection\n // re-creates this message event (cannot dispatch the same event).\n new CancelableMessageEvent('outgoing', {\n data,\n origin: this.socket.url,\n cancelable: true,\n })\n )\n )\n }\n }\n\n public addEventListener<EventType extends keyof WebSocketTransportEventMap>(\n type: EventType,\n callback: StrictEventListenerOrEventListenerObject<\n WebSocketTransportEventMap[EventType]\n > | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n return super.addEventListener(type, callback as EventListener, options)\n }\n\n public dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(\n event: WebSocketTransportEventMap[EventType]\n ): boolean {\n return super.dispatchEvent(event)\n }\n\n public send(data: WebSocketData): void {\n queueMicrotask(() => {\n if (\n this.socket.readyState === this.socket.CLOSING ||\n this.socket.readyState === this.socket.CLOSED\n ) {\n return\n }\n\n const dispatchEvent = () => {\n this.socket.dispatchEvent(\n bindEvent(\n /**\n * @note Setting this event's \"target\" to the\n * WebSocket override instance is important.\n * This way it can tell apart original incoming events\n * (must be forwarded to the transport) from the\n * mocked message events like the one below\n * (must be dispatched on the client instance).\n */\n this.socket,\n new MessageEvent('message', {\n data,\n origin: this.socket.url,\n })\n )\n )\n }\n\n if (this.socket.readyState === this.socket.CONNECTING) {\n this.socket.addEventListener(\n 'open',\n () => {\n dispatchEvent()\n },\n { once: true }\n )\n } else {\n dispatchEvent()\n }\n })\n }\n\n public close(code: number, reason?: string): void {\n /**\n * @note Call the internal close method directly\n * to allow closing the connection with the status codes\n * that are non-configurable by the user (> 1000 <= 1015).\n */\n this.socket[kClose](code, reason)\n }\n}\n","import { Interceptor } from '../../Interceptor'\nimport {\n WebSocketClientConnectionProtocol,\n WebSocketClientConnection,\n type WebSocketClientEventMap,\n} from './WebSocketClientConnection'\nimport {\n WebSocketServerConnectionProtocol,\n WebSocketServerConnection,\n type WebSocketServerEventMap,\n} from './WebSocketServerConnection'\nimport { WebSocketClassTransport } from './WebSocketClassTransport'\nimport {\n kClose,\n kPassthroughPromise,\n WebSocketOverride,\n} from './WebSocketOverride'\nimport { bindEvent } from './utils/bindEvent'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport {\n type WebSocketData,\n type WebSocketTransport,\n} from './WebSocketTransport'\nexport {\n WebSocketClientEventMap,\n WebSocketClientConnectionProtocol,\n WebSocketClientConnection,\n WebSocketServerEventMap,\n WebSocketServerConnectionProtocol,\n WebSocketServerConnection,\n}\n\nexport {\n CloseEvent,\n CancelableCloseEvent,\n CancelableMessageEvent,\n} from './utils/events'\n\nexport type WebSocketEventMap = {\n connection: [args: WebSocketConnectionData]\n}\n\nexport type WebSocketConnectionData = {\n /**\n * The incoming WebSocket client connection.\n */\n client: WebSocketClientConnection\n\n /**\n * The original WebSocket server connection.\n */\n server: WebSocketServerConnection\n\n /**\n * The connection information.\n */\n info: {\n /**\n * The protocols supported by the WebSocket client.\n */\n protocols: string | Array<string> | undefined\n }\n}\n\n/**\n * Intercept the outgoing WebSocket connections created using\n * the global `WebSocket` class.\n */\nexport class WebSocketInterceptor extends Interceptor<WebSocketEventMap> {\n static symbol = Symbol('websocket')\n\n constructor() {\n super(WebSocketInterceptor.symbol)\n }\n\n protected checkEnvironment(): boolean {\n return hasConfigurableGlobal('WebSocket')\n }\n\n protected setup(): void {\n const logger = this.logger.extend('setup')\n\n const WebSocketProxy = new Proxy(globalThis.WebSocket, {\n construct: (\n target,\n args: ConstructorParameters<typeof globalThis.WebSocket>,\n newTarget\n ) => {\n const [url, protocols] = args\n\n const createConnection = (): WebSocket => {\n return Reflect.construct(target, args, newTarget)\n }\n\n // All WebSocket instances are mocked and don't forward\n // any events to the original server (no connection established).\n // To forward the events, the user must use the \"server.send()\" API.\n const socket = new WebSocketOverride(url, protocols)\n const transport = new WebSocketClassTransport(socket)\n\n // Emit the \"connection\" event to the interceptor on the next tick\n // so the client can modify WebSocket options, like \"binaryType\"\n // while the connection is already pending.\n queueMicrotask(async () => {\n try {\n const server = new WebSocketServerConnection(\n socket,\n transport,\n createConnection\n )\n\n const hasConnectionListeners =\n this.emitter.listenerCount('connection') > 0\n\n // The \"globalThis.WebSocket\" class stands for\n // the client-side connection. Assume it's established\n // as soon as the WebSocket instance is constructed.\n await emitAsync(this.emitter, 'connection', {\n client: new WebSocketClientConnection(socket, transport),\n server,\n info: {\n protocols,\n },\n })\n\n if (hasConnectionListeners) {\n socket[kPassthroughPromise].resolve(false)\n } else {\n socket[kPassthroughPromise].resolve(true)\n\n server.connect()\n\n // Forward the \"open\" event from the original server\n // to the mock WebSocket client in the case of a passthrough connection.\n server.addEventListener('open', () => {\n socket.dispatchEvent(bindEvent(socket, new Event('open')))\n\n // Forward the original connection protocol to the\n // mock WebSocket client.\n if (server['realWebSocket']) {\n socket.protocol = server['realWebSocket'].protocol\n }\n })\n }\n } catch (error) {\n /**\n * @note Translate unhandled exceptions during the connection\n * handling (i.e. interceptor exceptions) as WebSocket connection\n * closures with error. This prevents from the exceptions occurring\n * in `queueMicrotask` from being process-wide and uncatchable.\n */\n if (error instanceof Error) {\n socket.dispatchEvent(new Event('error'))\n\n // No need to close the connection if it's already being closed.\n // E.g. the interceptor called `client.close()` and then threw an error.\n if (\n socket.readyState !== WebSocket.CLOSING &&\n socket.readyState !== WebSocket.CLOSED\n ) {\n socket[kClose](1011, error.message, false)\n }\n\n console.error(error)\n }\n }\n })\n\n return socket\n },\n })\n\n logger.info('patching global WebSocket...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'WebSocket', () => WebSocketProxy)\n )\n\n logger.info('global WebSocket patched!', globalThis.WebSocket.name)\n }\n}\n"],"mappings":"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"} |
| import { r as Interceptor, t as createRequestId } from "../../createRequestId-DYCsFHOi.mjs"; | ||
| import { t as resolveWebSocketUrl } from "../../resolveWebSocketUrl-C83-x9iE.mjs"; | ||
| import { n as globalsRegistry, r as emitAsync, t as hasConfigurableGlobal } from "../../hasConfigurableGlobal-B39W0thh.mjs"; | ||
| import { n as patchesRegistry, r as emitAsync, t as hasConfigurableGlobal } from "../../hasConfigurableGlobal-FTYwno1G.mjs"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
@@ -604,3 +604,3 @@ import { invariant } from "outvariant"; | ||
| logger.info("patching global WebSocket..."); | ||
| this.subscriptions.push(globalsRegistry.replaceGlobal("WebSocket", WebSocketProxy)); | ||
| this.subscriptions.push(patchesRegistry.applyPatch(globalThis, "WebSocket", () => WebSocketProxy)); | ||
| logger.info("global WebSocket patched!", globalThis.WebSocket.name); | ||
@@ -607,0 +607,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","names":["kEmitter","kBoundListener","socket: WebSocket","transport: WebSocketTransport","client: WebSocketOverride","transport: WebSocketClassTransport","createConnection: () => WebSocket","socket: WebSocketOverride"],"sources":["../../../../src/interceptors/WebSocket/utils/bindEvent.ts","../../../../src/interceptors/WebSocket/utils/events.ts","../../../../src/interceptors/WebSocket/WebSocketClientConnection.ts","../../../../src/interceptors/WebSocket/WebSocketOverride.ts","../../../../src/interceptors/WebSocket/WebSocketServerConnection.ts","../../../../src/interceptors/WebSocket/WebSocketClassTransport.ts","../../../../src/interceptors/WebSocket/index.ts"],"sourcesContent":["type EventWithTarget<E extends Event, T> = E & { target: T }\n\nexport function bindEvent<E extends Event, T>(\n target: T,\n event: E\n): EventWithTarget<E, T> {\n Object.defineProperties(event, {\n target: {\n value: target,\n enumerable: true,\n writable: true,\n },\n currentTarget: {\n value: target,\n enumerable: true,\n writable: true,\n },\n })\n\n return event as EventWithTarget<E, T>\n}\n","const kCancelable = Symbol('kCancelable')\nconst kDefaultPrevented = Symbol('kDefaultPrevented')\n\n/**\n * A `MessageEvent` superset that supports event cancellation\n * in Node.js. It's rather non-intrusive so it can be safely\n * used in the browser as well.\n *\n * @see https://github.com/nodejs/node/issues/51767\n */\nexport class CancelableMessageEvent<T = any> extends MessageEvent<T> {\n [kCancelable]: boolean;\n [kDefaultPrevented]: boolean\n\n constructor(type: string, init: MessageEventInit<T>) {\n super(type, init)\n this[kCancelable] = !!init.cancelable\n this[kDefaultPrevented] = false\n }\n\n get cancelable() {\n return this[kCancelable]\n }\n\n set cancelable(nextCancelable) {\n this[kCancelable] = nextCancelable\n }\n\n get defaultPrevented() {\n return this[kDefaultPrevented]\n }\n\n set defaultPrevented(nextDefaultPrevented) {\n this[kDefaultPrevented] = nextDefaultPrevented\n }\n\n public preventDefault(): void {\n if (this.cancelable && !this[kDefaultPrevented]) {\n this[kDefaultPrevented] = true\n }\n }\n}\n\ninterface CloseEventInit extends EventInit {\n code?: number\n reason?: string\n wasClean?: boolean\n}\n\nexport class CloseEvent extends Event {\n public code: number\n public reason: string\n public wasClean: boolean\n\n constructor(type: string, init: CloseEventInit = {}) {\n super(type, init)\n this.code = init.code === undefined ? 0 : init.code\n this.reason = init.reason === undefined ? '' : init.reason\n this.wasClean = init.wasClean === undefined ? false : init.wasClean\n }\n}\n\nexport class CancelableCloseEvent extends CloseEvent {\n [kCancelable]: boolean;\n [kDefaultPrevented]: boolean\n\n constructor(type: string, init: CloseEventInit = {}) {\n super(type, init)\n this[kCancelable] = !!init.cancelable\n this[kDefaultPrevented] = false\n }\n\n get cancelable() {\n return this[kCancelable]\n }\n\n set cancelable(nextCancelable) {\n this[kCancelable] = nextCancelable\n }\n\n get defaultPrevented() {\n return this[kDefaultPrevented]\n }\n\n set defaultPrevented(nextDefaultPrevented) {\n this[kDefaultPrevented] = nextDefaultPrevented\n }\n\n public preventDefault(): void {\n if (this.cancelable && !this[kDefaultPrevented]) {\n this[kDefaultPrevented] = true\n }\n }\n}\n","import type { WebSocketData, WebSocketTransport } from './WebSocketTransport'\nimport type { WebSocketEventListener } from './WebSocketOverride'\nimport { bindEvent } from './utils/bindEvent'\nimport { CancelableMessageEvent, CloseEvent } from './utils/events'\nimport { createRequestId } from '../../createRequestId'\n\nconst kEmitter = Symbol('kEmitter')\nconst kBoundListener = Symbol('kBoundListener')\n\nexport interface WebSocketClientEventMap {\n message: MessageEvent<WebSocketData>\n close: CloseEvent\n}\n\nexport abstract class WebSocketClientConnectionProtocol {\n abstract id: string\n abstract url: URL\n public abstract send(data: WebSocketData): void\n public abstract close(code?: number, reason?: string): void\n\n public abstract addEventListener<\n EventType extends keyof WebSocketClientEventMap,\n >(\n type: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void\n\n public abstract removeEventListener<\n EventType extends keyof WebSocketClientEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void\n}\n\n/**\n * The WebSocket client instance represents an incoming\n * client connection. The user can control the connection,\n * send and receive events.\n */\nexport class WebSocketClientConnection implements WebSocketClientConnectionProtocol {\n public readonly id: string\n public readonly url: URL\n\n private [kEmitter]: EventTarget\n\n constructor(\n public readonly socket: WebSocket,\n private readonly transport: WebSocketTransport\n ) {\n this.id = createRequestId()\n this.url = new URL(socket.url)\n this[kEmitter] = new EventTarget()\n\n // Emit outgoing client data (\"ws.send()\") as \"message\"\n // events on the \"client\" connection.\n this.transport.addEventListener('outgoing', (event) => {\n const message = bindEvent(\n this.socket,\n new CancelableMessageEvent('message', {\n data: event.data,\n origin: event.origin,\n cancelable: true,\n })\n )\n\n this[kEmitter].dispatchEvent(message)\n\n // This is a bit silly but forward the cancellation state\n // of the \"client\" message event to the \"outgoing\" transport event.\n // This way, other agens (like \"server\" connection) can know\n // whether the client listener has pervented the default.\n if (message.defaultPrevented) {\n event.preventDefault()\n }\n })\n\n /**\n * Emit the \"close\" event on the \"client\" connection\n * whenever the underlying transport is closed.\n * @note \"client.close()\" does NOT dispatch the \"close\"\n * event on the WebSocket because it uses non-configurable\n * close status code. Thus, we listen to the transport\n * instead of the WebSocket's \"close\" event.\n */\n this.transport.addEventListener('close', (event) => {\n this[kEmitter].dispatchEvent(\n bindEvent(this.socket, new CloseEvent('close', event))\n )\n })\n }\n\n /**\n * Listen for the outgoing events from the connected WebSocket client.\n */\n public addEventListener<EventType extends keyof WebSocketClientEventMap>(\n type: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void {\n if (!Reflect.has(listener, kBoundListener)) {\n const boundListener = listener.bind(this.socket)\n\n // Store the bound listener on the original listener\n // so the exact bound function can be accessed in \"removeEventListener()\".\n Object.defineProperty(listener, kBoundListener, {\n value: boundListener,\n enumerable: false,\n configurable: false,\n })\n }\n\n this[kEmitter].addEventListener(\n type,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Removes the listener for the given event.\n */\n public removeEventListener<EventType extends keyof WebSocketClientEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void {\n this[kEmitter].removeEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Send data to the connected client.\n */\n public send(data: WebSocketData): void {\n this.transport.send(data)\n }\n\n /**\n * Close the WebSocket connection.\n * @param {number} code A status code (see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).\n * @param {string} reason A custom connection close reason.\n */\n public close(code?: number, reason?: string): void {\n this.transport.close(code, reason)\n }\n}\n","import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport type { WebSocketData } from './WebSocketTransport'\nimport { bindEvent } from './utils/bindEvent'\nimport { CloseEvent } from './utils/events'\nimport { resolveWebSocketUrl } from '../../utils/resolveWebSocketUrl'\n\nexport type WebSocketEventListener<\n EventType extends WebSocketEventMap[keyof WebSocketEventMap] = Event,\n> = (this: WebSocket, event: EventType) => void\n\nconst WEBSOCKET_CLOSE_CODE_RANGE_ERROR =\n 'InvalidAccessError: close code out of user configurable range'\n\nexport const kPassthroughPromise = Symbol('kPassthroughPromise')\nexport const kOnSend = Symbol('kOnSend')\nexport const kClose = Symbol('kClose')\n\nexport class WebSocketOverride extends EventTarget implements WebSocket {\n static readonly CONNECTING = 0\n static readonly OPEN = 1\n static readonly CLOSING = 2\n static readonly CLOSED = 3\n readonly CONNECTING = 0\n readonly OPEN = 1\n readonly CLOSING = 2\n readonly CLOSED = 3\n\n public url: string\n public protocol: string\n public extensions: string\n public binaryType: BinaryType\n public readyState: WebSocket['readyState']\n public bufferedAmount: number\n\n private _onopen: WebSocketEventListener | null = null\n private _onmessage: WebSocketEventListener<\n MessageEvent<WebSocketData>\n > | null = null\n private _onerror: WebSocketEventListener | null = null\n private _onclose: WebSocketEventListener<CloseEvent> | null = null\n\n private [kPassthroughPromise]: DeferredPromise<boolean>\n private [kOnSend]?: (data: WebSocketData) => void\n\n constructor(url: string | URL, protocols?: string | Array<string>) {\n super()\n this.url = resolveWebSocketUrl(url)\n this.protocol = ''\n this.extensions = ''\n this.binaryType = 'blob'\n this.readyState = this.CONNECTING\n this.bufferedAmount = 0\n\n this[kPassthroughPromise] = new DeferredPromise<boolean>()\n\n queueMicrotask(async () => {\n if (await this[kPassthroughPromise]) {\n return\n }\n\n this.protocol =\n typeof protocols === 'string'\n ? protocols\n : Array.isArray(protocols) && protocols.length > 0\n ? protocols[0]\n : ''\n\n /**\n * @note Check that nothing has prevented this connection\n * (e.g. called `client.close()` in the connection listener).\n * If the connection has been prevented, never dispatch the open event,.\n */\n if (this.readyState === this.CONNECTING) {\n this.readyState = this.OPEN\n this.dispatchEvent(bindEvent(this, new Event('open')))\n }\n })\n }\n\n set onopen(listener: WebSocketEventListener | null) {\n this.removeEventListener('open', this._onopen)\n this._onopen = listener\n if (listener !== null) {\n this.addEventListener('open', listener)\n }\n }\n get onopen(): WebSocketEventListener | null {\n return this._onopen\n }\n\n set onmessage(\n listener: WebSocketEventListener<MessageEvent<WebSocketData>> | null\n ) {\n this.removeEventListener(\n 'message',\n this._onmessage as WebSocketEventListener\n )\n this._onmessage = listener\n if (listener !== null) {\n this.addEventListener('message', listener)\n }\n }\n get onmessage(): WebSocketEventListener<MessageEvent<WebSocketData>> | null {\n return this._onmessage\n }\n\n set onerror(listener: WebSocketEventListener | null) {\n this.removeEventListener('error', this._onerror)\n this._onerror = listener\n if (listener !== null) {\n this.addEventListener('error', listener)\n }\n }\n get onerror(): WebSocketEventListener | null {\n return this._onerror\n }\n\n set onclose(listener: WebSocketEventListener<CloseEvent> | null) {\n this.removeEventListener('close', this._onclose as WebSocketEventListener)\n this._onclose = listener\n if (listener !== null) {\n this.addEventListener('close', listener)\n }\n }\n get onclose(): WebSocketEventListener<CloseEvent> | null {\n return this._onclose\n }\n\n /**\n * @see https://websockets.spec.whatwg.org/#ref-for-dom-websocket-send%E2%91%A0\n */\n public send(data: WebSocketData): void {\n if (this.readyState === this.CONNECTING) {\n this.close()\n throw new DOMException('InvalidStateError')\n }\n\n // Sending when the socket is about to close\n // discards the sent data.\n if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {\n return\n }\n\n // Buffer the data to send in this even loop\n // but send it in the next.\n this.bufferedAmount += getDataSize(data)\n\n queueMicrotask(() => {\n // This is a bit optimistic but since no actual data transfer\n // is involved, all the data will be \"sent\" on the next tick.\n this.bufferedAmount = 0\n\n /**\n * @note Notify the parent about outgoing data.\n * This notifies the transport and the connection\n * listens to the outgoing data to emit the \"message\" event.\n */\n this[kOnSend]?.(data)\n })\n }\n\n public close(code: number = 1000, reason?: string): void {\n invariant(code, WEBSOCKET_CLOSE_CODE_RANGE_ERROR)\n invariant(\n code === 1000 || (code >= 3000 && code <= 4999),\n WEBSOCKET_CLOSE_CODE_RANGE_ERROR\n )\n\n this[kClose](code, reason)\n }\n\n private [kClose](\n code: number = 1000,\n reason?: string,\n wasClean = true\n ): void {\n /**\n * @note Move this check here so that even internal closures,\n * like those triggered by the `server` connection, are not\n * performed twice.\n */\n if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {\n return\n }\n\n this.readyState = this.CLOSING\n\n queueMicrotask(() => {\n this.readyState = this.CLOSED\n\n this.dispatchEvent(\n bindEvent(\n this,\n new CloseEvent('close', {\n code,\n reason,\n wasClean,\n })\n )\n )\n\n // Remove all event listeners once the socket is closed.\n this._onopen = null\n this._onmessage = null\n this._onerror = null\n this._onclose = null\n })\n }\n\n public addEventListener<K extends keyof WebSocketEventMap>(\n type: K,\n listener: (this: WebSocket, event: WebSocketEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions\n ): void\n public addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ): void\n public addEventListener(\n type: unknown,\n listener: unknown,\n options?: unknown\n ): void {\n return super.addEventListener(\n type as string,\n listener as EventListener,\n options as AddEventListenerOptions\n )\n }\n\n removeEventListener<K extends keyof WebSocketEventMap>(\n type: K,\n callback: EventListenerOrEventListenerObject | null,\n options?: boolean | EventListenerOptions\n ): void {\n return super.removeEventListener(type, callback, options)\n }\n}\n\nfunction getDataSize(data: WebSocketData): number {\n if (typeof data === 'string') {\n return data.length\n }\n\n if (data instanceof Blob) {\n return data.size\n }\n\n return data.byteLength\n}\n","import { invariant } from 'outvariant'\nimport {\n kClose,\n WebSocketEventListener,\n WebSocketOverride,\n} from './WebSocketOverride'\nimport type { WebSocketData } from './WebSocketTransport'\nimport type { WebSocketClassTransport } from './WebSocketClassTransport'\nimport { bindEvent } from './utils/bindEvent'\nimport {\n CancelableMessageEvent,\n CancelableCloseEvent,\n CloseEvent,\n} from './utils/events'\n\nconst kEmitter = Symbol('kEmitter')\nconst kBoundListener = Symbol('kBoundListener')\nconst kSend = Symbol('kSend')\n\nexport interface WebSocketServerEventMap {\n open: Event\n message: MessageEvent<WebSocketData>\n error: Event\n close: CloseEvent\n}\n\nexport abstract class WebSocketServerConnectionProtocol {\n public abstract connect(): void\n public abstract send(data: WebSocketData): void\n public abstract close(): void\n\n public abstract addEventListener<\n EventType extends keyof WebSocketServerEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void\n\n public abstract removeEventListener<\n EventType extends keyof WebSocketServerEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void\n}\n\n/**\n * The WebSocket server instance represents the actual production\n * WebSocket server connection. It's idle by default but you can\n * establish it by calling `server.connect()`.\n */\nexport class WebSocketServerConnection implements WebSocketServerConnectionProtocol {\n /**\n * A WebSocket instance connected to the original server.\n */\n private realWebSocket?: WebSocket\n private mockCloseController: AbortController\n private realCloseController: AbortController\n private [kEmitter]: EventTarget\n\n constructor(\n private readonly client: WebSocketOverride,\n private readonly transport: WebSocketClassTransport,\n private readonly createConnection: () => WebSocket\n ) {\n this[kEmitter] = new EventTarget()\n this.mockCloseController = new AbortController()\n this.realCloseController = new AbortController()\n\n // Automatically forward outgoing client events\n // to the actual server unless the outgoing message event\n // has been prevented. The \"outgoing\" transport event it\n // dispatched by the \"client\" connection.\n this.transport.addEventListener('outgoing', (event) => {\n // Ignore client messages if the server connection\n // hasn't been established yet. Nowhere to forward.\n if (typeof this.realWebSocket === 'undefined') {\n return\n }\n\n // Every outgoing client message can prevent this forwarding\n // by preventing the default of the outgoing message event.\n // This listener will be added before user-defined listeners,\n // so execute the logic on the next tick.\n queueMicrotask(() => {\n if (!event.defaultPrevented) {\n /**\n * @note Use the internal send mechanism so consumers can tell\n * apart direct user calls to `server.send()` and internal calls.\n * E.g. MSW has to ignore this internal call to log out messages correctly.\n */\n this[kSend](event.data)\n }\n })\n })\n\n this.transport.addEventListener(\n 'incoming',\n this.handleIncomingMessage.bind(this)\n )\n }\n\n /**\n * The `WebSocket` instance connected to the original server.\n * Accessing this before calling `server.connect()` will throw.\n */\n public get socket(): WebSocket {\n invariant(\n this.realWebSocket,\n 'Cannot access \"socket\" on the original WebSocket server object: the connection is not open. Did you forget to call `server.connect()`?'\n )\n\n return this.realWebSocket\n }\n\n /**\n * Open connection to the original WebSocket server.\n */\n public connect(): void {\n invariant(\n !this.realWebSocket || this.realWebSocket.readyState !== WebSocket.OPEN,\n 'Failed to call \"connect()\" on the original WebSocket instance: the connection already open'\n )\n\n const realWebSocket = this.createConnection()\n\n // Inherit the binary type from the mock WebSocket client.\n realWebSocket.binaryType = this.client.binaryType\n\n // Allow the interceptor to listen to when the server connection\n // has been established. This isn't necessary to operate with the connection\n // but may be beneficial in some cases (like conditionally adding logging).\n realWebSocket.addEventListener(\n 'open',\n (event) => {\n this[kEmitter].dispatchEvent(\n bindEvent(this.realWebSocket!, new Event('open', event))\n )\n },\n { once: true }\n )\n\n realWebSocket.addEventListener('message', (event) => {\n // Dispatch the \"incoming\" transport event instead of\n // invoking the internal handler directly. This way,\n // anyone can listen to the \"incoming\" event but this\n // class is the one resulting in it.\n this.transport.dispatchEvent(\n bindEvent(\n this.realWebSocket!,\n new MessageEvent('incoming', {\n data: event.data,\n origin: event.origin,\n })\n )\n )\n })\n\n // Close the original connection when the mock client closes.\n // E.g. \"client.close()\" was called. This is never forwarded anywhere.\n this.client.addEventListener(\n 'close',\n (event) => {\n this.handleMockClose(event)\n },\n {\n signal: this.mockCloseController.signal,\n }\n )\n\n // Forward the \"close\" event to let the interceptor handle\n // closures initiated by the original server.\n realWebSocket.addEventListener(\n 'close',\n (event) => {\n this.handleRealClose(event)\n },\n {\n signal: this.realCloseController.signal,\n }\n )\n\n realWebSocket.addEventListener('error', () => {\n const errorEvent = bindEvent(\n realWebSocket,\n new Event('error', { cancelable: true })\n )\n\n // Emit the \"error\" event on the `server` connection\n // to let the interceptor react to original server errors.\n this[kEmitter].dispatchEvent(errorEvent)\n\n // If the error event from the original server hasn't been prevented,\n // forward it to the underlying client.\n if (!errorEvent.defaultPrevented) {\n this.client.dispatchEvent(bindEvent(this.client, new Event('error')))\n }\n })\n\n this.realWebSocket = realWebSocket\n }\n\n /**\n * Listen for the incoming events from the original WebSocket server.\n */\n public addEventListener<EventType extends keyof WebSocketServerEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void {\n if (!Reflect.has(listener, kBoundListener)) {\n const boundListener = listener.bind(this.client)\n\n // Store the bound listener on the original listener\n // so the exact bound function can be accessed in \"removeEventListener()\".\n Object.defineProperty(listener, kBoundListener, {\n value: boundListener,\n enumerable: false,\n })\n }\n\n this[kEmitter].addEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Remove the listener for the given event.\n */\n public removeEventListener<EventType extends keyof WebSocketServerEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void {\n this[kEmitter].removeEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Send data to the original WebSocket server.\n * @example\n * server.send('hello')\n * server.send(new Blob(['hello']))\n * server.send(new TextEncoder().encode('hello'))\n */\n public send(data: WebSocketData): void {\n this[kSend](data)\n }\n\n private [kSend](data: WebSocketData): void {\n const { realWebSocket } = this\n\n invariant(\n realWebSocket,\n 'Failed to call \"server.send()\" for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?',\n this.client.url\n )\n\n // Silently ignore writes on the closed original WebSocket.\n if (\n realWebSocket.readyState === WebSocket.CLOSING ||\n realWebSocket.readyState === WebSocket.CLOSED\n ) {\n return\n }\n\n // Delegate the send to when the original connection is open.\n // Unlike the mock, connecting to the original server may take time\n // so we cannot call this on the next tick.\n if (realWebSocket.readyState === WebSocket.CONNECTING) {\n realWebSocket.addEventListener(\n 'open',\n () => {\n realWebSocket.send(data)\n },\n { once: true }\n )\n return\n }\n\n // Send the data to the original WebSocket server.\n realWebSocket.send(data)\n }\n\n /**\n * Close the actual server connection.\n */\n public close(): void {\n const { realWebSocket } = this\n\n invariant(\n realWebSocket,\n 'Failed to close server connection for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?',\n this.client.url\n )\n\n // Remove the \"close\" event listener from the server\n // so it doesn't close the underlying WebSocket client\n // when you call \"server.close()\". This also prevents the\n // `close` event on the `server` connection from being dispatched twice.\n this.realCloseController.abort()\n\n if (\n realWebSocket.readyState === WebSocket.CLOSING ||\n realWebSocket.readyState === WebSocket.CLOSED\n ) {\n return\n }\n\n // Close the actual client connection.\n realWebSocket.close()\n\n // Dispatch the \"close\" event on the `server` connection.\n queueMicrotask(() => {\n this[kEmitter].dispatchEvent(\n bindEvent(\n this.realWebSocket,\n new CancelableCloseEvent('close', {\n /**\n * @note `server.close()` in the interceptor\n * always results in clean closures.\n */\n code: 1000,\n cancelable: true,\n })\n )\n )\n })\n }\n\n private handleIncomingMessage(event: MessageEvent<WebSocketData>): void {\n // Clone the event to dispatch it on this class\n // once again and prevent the \"already being dispatched\"\n // exception. Clone it here so we can observe this event\n // being prevented in the \"server.on()\" listeners.\n const messageEvent = bindEvent(\n event.target,\n new CancelableMessageEvent('message', {\n data: event.data,\n origin: event.origin,\n cancelable: true,\n })\n )\n\n /**\n * @note Emit \"message\" event on the server connection\n * instance to let the interceptor know about these\n * incoming events from the original server. In that listener,\n * the interceptor can modify or skip the event forwarding\n * to the mock WebSocket instance.\n */\n this[kEmitter].dispatchEvent(messageEvent)\n\n /**\n * @note Forward the incoming server events to the client.\n * Preventing the default on the message event stops this.\n */\n if (!messageEvent.defaultPrevented) {\n this.client.dispatchEvent(\n bindEvent(\n /**\n * @note Bind the forwarded original server events\n * to the mock WebSocket instance so it would\n * dispatch them straight away.\n */\n this.client,\n // Clone the message event again to prevent\n // the \"already being dispatched\" exception.\n new MessageEvent('message', {\n data: event.data,\n origin: event.origin,\n })\n )\n )\n }\n }\n\n private handleMockClose(_event: Event): void {\n // Close the original connection if the mock client closes.\n if (this.realWebSocket) {\n this.realWebSocket.close()\n }\n }\n\n private handleRealClose(event: CloseEvent): void {\n // For closures originating from the original server,\n // remove the \"close\" listener from the mock client.\n // original close -> (?) client[kClose]() --X--> \"close\" (again).\n this.mockCloseController.abort()\n\n const closeEvent = bindEvent(\n this.realWebSocket,\n new CancelableCloseEvent('close', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n cancelable: true,\n })\n )\n\n this[kEmitter].dispatchEvent(closeEvent)\n\n // If the close event from the server hasn't been prevented,\n // forward the closure to the mock client.\n if (!closeEvent.defaultPrevented) {\n // Close the intercepted client forcefully to\n // allow non-configurable status codes from the server.\n // If the socket has been closed by now, no harm calling\n // this again—it will have no effect.\n this.client[kClose](event.code, event.reason)\n }\n }\n}\n","import { bindEvent } from './utils/bindEvent'\nimport {\n StrictEventListenerOrEventListenerObject,\n WebSocketData,\n WebSocketTransport,\n WebSocketTransportEventMap,\n} from './WebSocketTransport'\nimport { kOnSend, kClose, WebSocketOverride } from './WebSocketOverride'\nimport { CancelableMessageEvent, CloseEvent } from './utils/events'\n\n/**\n * Abstraction over the given mock `WebSocket` instance that allows\n * for controlling that instance (e.g. sending and receiving messages).\n */\nexport class WebSocketClassTransport\n extends EventTarget\n implements WebSocketTransport\n{\n constructor(protected readonly socket: WebSocketOverride) {\n super()\n\n // Emit the \"close\" event on the transport if the close\n // originates from the WebSocket client. E.g. the application\n // calls \"ws.close()\", not the interceptor.\n this.socket.addEventListener('close', (event) => {\n this.dispatchEvent(bindEvent(this.socket, new CloseEvent('close', event)))\n })\n\n /**\n * Emit the \"outgoing\" event on the transport\n * whenever the WebSocket client sends data (\"ws.send()\").\n */\n this.socket[kOnSend] = (data) => {\n this.dispatchEvent(\n bindEvent(\n this.socket,\n // Dispatch this as cancelable because \"client\" connection\n // re-creates this message event (cannot dispatch the same event).\n new CancelableMessageEvent('outgoing', {\n data,\n origin: this.socket.url,\n cancelable: true,\n })\n )\n )\n }\n }\n\n public addEventListener<EventType extends keyof WebSocketTransportEventMap>(\n type: EventType,\n callback: StrictEventListenerOrEventListenerObject<\n WebSocketTransportEventMap[EventType]\n > | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n return super.addEventListener(type, callback as EventListener, options)\n }\n\n public dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(\n event: WebSocketTransportEventMap[EventType]\n ): boolean {\n return super.dispatchEvent(event)\n }\n\n public send(data: WebSocketData): void {\n queueMicrotask(() => {\n if (\n this.socket.readyState === this.socket.CLOSING ||\n this.socket.readyState === this.socket.CLOSED\n ) {\n return\n }\n\n const dispatchEvent = () => {\n this.socket.dispatchEvent(\n bindEvent(\n /**\n * @note Setting this event's \"target\" to the\n * WebSocket override instance is important.\n * This way it can tell apart original incoming events\n * (must be forwarded to the transport) from the\n * mocked message events like the one below\n * (must be dispatched on the client instance).\n */\n this.socket,\n new MessageEvent('message', {\n data,\n origin: this.socket.url,\n })\n )\n )\n }\n\n if (this.socket.readyState === this.socket.CONNECTING) {\n this.socket.addEventListener(\n 'open',\n () => {\n dispatchEvent()\n },\n { once: true }\n )\n } else {\n dispatchEvent()\n }\n })\n }\n\n public close(code: number, reason?: string): void {\n /**\n * @note Call the internal close method directly\n * to allow closing the connection with the status codes\n * that are non-configurable by the user (> 1000 <= 1015).\n */\n this.socket[kClose](code, reason)\n }\n}\n","import { Interceptor } from '../../Interceptor'\nimport {\n WebSocketClientConnectionProtocol,\n WebSocketClientConnection,\n type WebSocketClientEventMap,\n} from './WebSocketClientConnection'\nimport {\n WebSocketServerConnectionProtocol,\n WebSocketServerConnection,\n type WebSocketServerEventMap,\n} from './WebSocketServerConnection'\nimport { WebSocketClassTransport } from './WebSocketClassTransport'\nimport {\n kClose,\n kPassthroughPromise,\n WebSocketOverride,\n} from './WebSocketOverride'\nimport { bindEvent } from './utils/bindEvent'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport {\n type WebSocketData,\n type WebSocketTransport,\n} from './WebSocketTransport'\nexport {\n WebSocketClientEventMap,\n WebSocketClientConnectionProtocol,\n WebSocketClientConnection,\n WebSocketServerEventMap,\n WebSocketServerConnectionProtocol,\n WebSocketServerConnection,\n}\n\nexport {\n CloseEvent,\n CancelableCloseEvent,\n CancelableMessageEvent,\n} from './utils/events'\n\nexport type WebSocketEventMap = {\n connection: [args: WebSocketConnectionData]\n}\n\nexport type WebSocketConnectionData = {\n /**\n * The incoming WebSocket client connection.\n */\n client: WebSocketClientConnection\n\n /**\n * The original WebSocket server connection.\n */\n server: WebSocketServerConnection\n\n /**\n * The connection information.\n */\n info: {\n /**\n * The protocols supported by the WebSocket client.\n */\n protocols: string | Array<string> | undefined\n }\n}\n\n/**\n * Intercept the outgoing WebSocket connections created using\n * the global `WebSocket` class.\n */\nexport class WebSocketInterceptor extends Interceptor<WebSocketEventMap> {\n static symbol = Symbol('websocket')\n\n constructor() {\n super(WebSocketInterceptor.symbol)\n }\n\n protected checkEnvironment(): boolean {\n return hasConfigurableGlobal('WebSocket')\n }\n\n protected setup(): void {\n const logger = this.logger.extend('setup')\n\n const WebSocketProxy = new Proxy(globalThis.WebSocket, {\n construct: (\n target,\n args: ConstructorParameters<typeof globalThis.WebSocket>,\n newTarget\n ) => {\n const [url, protocols] = args\n\n const createConnection = (): WebSocket => {\n return Reflect.construct(target, args, newTarget)\n }\n\n // All WebSocket instances are mocked and don't forward\n // any events to the original server (no connection established).\n // To forward the events, the user must use the \"server.send()\" API.\n const socket = new WebSocketOverride(url, protocols)\n const transport = new WebSocketClassTransport(socket)\n\n // Emit the \"connection\" event to the interceptor on the next tick\n // so the client can modify WebSocket options, like \"binaryType\"\n // while the connection is already pending.\n queueMicrotask(async () => {\n try {\n const server = new WebSocketServerConnection(\n socket,\n transport,\n createConnection\n )\n\n const hasConnectionListeners =\n this.emitter.listenerCount('connection') > 0\n\n // The \"globalThis.WebSocket\" class stands for\n // the client-side connection. Assume it's established\n // as soon as the WebSocket instance is constructed.\n await emitAsync(this.emitter, 'connection', {\n client: new WebSocketClientConnection(socket, transport),\n server,\n info: {\n protocols,\n },\n })\n\n if (hasConnectionListeners) {\n socket[kPassthroughPromise].resolve(false)\n } else {\n socket[kPassthroughPromise].resolve(true)\n\n server.connect()\n\n // Forward the \"open\" event from the original server\n // to the mock WebSocket client in the case of a passthrough connection.\n server.addEventListener('open', () => {\n socket.dispatchEvent(bindEvent(socket, new Event('open')))\n\n // Forward the original connection protocol to the\n // mock WebSocket client.\n if (server['realWebSocket']) {\n socket.protocol = server['realWebSocket'].protocol\n }\n })\n }\n } catch (error) {\n /**\n * @note Translate unhandled exceptions during the connection\n * handling (i.e. interceptor exceptions) as WebSocket connection\n * closures with error. This prevents from the exceptions occurring\n * in `queueMicrotask` from being process-wide and uncatchable.\n */\n if (error instanceof Error) {\n socket.dispatchEvent(new Event('error'))\n\n // No need to close the connection if it's already being closed.\n // E.g. the interceptor called `client.close()` and then threw an error.\n if (\n socket.readyState !== WebSocket.CLOSING &&\n socket.readyState !== WebSocket.CLOSED\n ) {\n socket[kClose](1011, error.message, false)\n }\n\n console.error(error)\n }\n }\n })\n\n return socket\n },\n })\n\n logger.info('patching global WebSocket...')\n\n this.subscriptions.push(\n globalsRegistry.replaceGlobal('WebSocket', WebSocketProxy)\n )\n\n logger.info('global WebSocket patched!', globalThis.WebSocket.name)\n }\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.mjs","names":["kEmitter","kBoundListener","socket: WebSocket","transport: WebSocketTransport","client: WebSocketOverride","transport: WebSocketClassTransport","createConnection: () => WebSocket","socket: WebSocketOverride"],"sources":["../../../../src/interceptors/WebSocket/utils/bindEvent.ts","../../../../src/interceptors/WebSocket/utils/events.ts","../../../../src/interceptors/WebSocket/WebSocketClientConnection.ts","../../../../src/interceptors/WebSocket/WebSocketOverride.ts","../../../../src/interceptors/WebSocket/WebSocketServerConnection.ts","../../../../src/interceptors/WebSocket/WebSocketClassTransport.ts","../../../../src/interceptors/WebSocket/index.ts"],"sourcesContent":["type EventWithTarget<E extends Event, T> = E & { target: T }\n\nexport function bindEvent<E extends Event, T>(\n target: T,\n event: E\n): EventWithTarget<E, T> {\n Object.defineProperties(event, {\n target: {\n value: target,\n enumerable: true,\n writable: true,\n },\n currentTarget: {\n value: target,\n enumerable: true,\n writable: true,\n },\n })\n\n return event as EventWithTarget<E, T>\n}\n","const kCancelable = Symbol('kCancelable')\nconst kDefaultPrevented = Symbol('kDefaultPrevented')\n\n/**\n * A `MessageEvent` superset that supports event cancellation\n * in Node.js. It's rather non-intrusive so it can be safely\n * used in the browser as well.\n *\n * @see https://github.com/nodejs/node/issues/51767\n */\nexport class CancelableMessageEvent<T = any> extends MessageEvent<T> {\n [kCancelable]: boolean;\n [kDefaultPrevented]: boolean\n\n constructor(type: string, init: MessageEventInit<T>) {\n super(type, init)\n this[kCancelable] = !!init.cancelable\n this[kDefaultPrevented] = false\n }\n\n get cancelable() {\n return this[kCancelable]\n }\n\n set cancelable(nextCancelable) {\n this[kCancelable] = nextCancelable\n }\n\n get defaultPrevented() {\n return this[kDefaultPrevented]\n }\n\n set defaultPrevented(nextDefaultPrevented) {\n this[kDefaultPrevented] = nextDefaultPrevented\n }\n\n public preventDefault(): void {\n if (this.cancelable && !this[kDefaultPrevented]) {\n this[kDefaultPrevented] = true\n }\n }\n}\n\ninterface CloseEventInit extends EventInit {\n code?: number\n reason?: string\n wasClean?: boolean\n}\n\nexport class CloseEvent extends Event {\n public code: number\n public reason: string\n public wasClean: boolean\n\n constructor(type: string, init: CloseEventInit = {}) {\n super(type, init)\n this.code = init.code === undefined ? 0 : init.code\n this.reason = init.reason === undefined ? '' : init.reason\n this.wasClean = init.wasClean === undefined ? false : init.wasClean\n }\n}\n\nexport class CancelableCloseEvent extends CloseEvent {\n [kCancelable]: boolean;\n [kDefaultPrevented]: boolean\n\n constructor(type: string, init: CloseEventInit = {}) {\n super(type, init)\n this[kCancelable] = !!init.cancelable\n this[kDefaultPrevented] = false\n }\n\n get cancelable() {\n return this[kCancelable]\n }\n\n set cancelable(nextCancelable) {\n this[kCancelable] = nextCancelable\n }\n\n get defaultPrevented() {\n return this[kDefaultPrevented]\n }\n\n set defaultPrevented(nextDefaultPrevented) {\n this[kDefaultPrevented] = nextDefaultPrevented\n }\n\n public preventDefault(): void {\n if (this.cancelable && !this[kDefaultPrevented]) {\n this[kDefaultPrevented] = true\n }\n }\n}\n","import type { WebSocketData, WebSocketTransport } from './WebSocketTransport'\nimport type { WebSocketEventListener } from './WebSocketOverride'\nimport { bindEvent } from './utils/bindEvent'\nimport { CancelableMessageEvent, CloseEvent } from './utils/events'\nimport { createRequestId } from '../../createRequestId'\n\nconst kEmitter = Symbol('kEmitter')\nconst kBoundListener = Symbol('kBoundListener')\n\nexport interface WebSocketClientEventMap {\n message: MessageEvent<WebSocketData>\n close: CloseEvent\n}\n\nexport abstract class WebSocketClientConnectionProtocol {\n abstract id: string\n abstract url: URL\n public abstract send(data: WebSocketData): void\n public abstract close(code?: number, reason?: string): void\n\n public abstract addEventListener<\n EventType extends keyof WebSocketClientEventMap,\n >(\n type: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void\n\n public abstract removeEventListener<\n EventType extends keyof WebSocketClientEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void\n}\n\n/**\n * The WebSocket client instance represents an incoming\n * client connection. The user can control the connection,\n * send and receive events.\n */\nexport class WebSocketClientConnection implements WebSocketClientConnectionProtocol {\n public readonly id: string\n public readonly url: URL\n\n private [kEmitter]: EventTarget\n\n constructor(\n public readonly socket: WebSocket,\n private readonly transport: WebSocketTransport\n ) {\n this.id = createRequestId()\n this.url = new URL(socket.url)\n this[kEmitter] = new EventTarget()\n\n // Emit outgoing client data (\"ws.send()\") as \"message\"\n // events on the \"client\" connection.\n this.transport.addEventListener('outgoing', (event) => {\n const message = bindEvent(\n this.socket,\n new CancelableMessageEvent('message', {\n data: event.data,\n origin: event.origin,\n cancelable: true,\n })\n )\n\n this[kEmitter].dispatchEvent(message)\n\n // This is a bit silly but forward the cancellation state\n // of the \"client\" message event to the \"outgoing\" transport event.\n // This way, other agens (like \"server\" connection) can know\n // whether the client listener has pervented the default.\n if (message.defaultPrevented) {\n event.preventDefault()\n }\n })\n\n /**\n * Emit the \"close\" event on the \"client\" connection\n * whenever the underlying transport is closed.\n * @note \"client.close()\" does NOT dispatch the \"close\"\n * event on the WebSocket because it uses non-configurable\n * close status code. Thus, we listen to the transport\n * instead of the WebSocket's \"close\" event.\n */\n this.transport.addEventListener('close', (event) => {\n this[kEmitter].dispatchEvent(\n bindEvent(this.socket, new CloseEvent('close', event))\n )\n })\n }\n\n /**\n * Listen for the outgoing events from the connected WebSocket client.\n */\n public addEventListener<EventType extends keyof WebSocketClientEventMap>(\n type: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void {\n if (!Reflect.has(listener, kBoundListener)) {\n const boundListener = listener.bind(this.socket)\n\n // Store the bound listener on the original listener\n // so the exact bound function can be accessed in \"removeEventListener()\".\n Object.defineProperty(listener, kBoundListener, {\n value: boundListener,\n enumerable: false,\n configurable: false,\n })\n }\n\n this[kEmitter].addEventListener(\n type,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Removes the listener for the given event.\n */\n public removeEventListener<EventType extends keyof WebSocketClientEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void {\n this[kEmitter].removeEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Send data to the connected client.\n */\n public send(data: WebSocketData): void {\n this.transport.send(data)\n }\n\n /**\n * Close the WebSocket connection.\n * @param {number} code A status code (see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).\n * @param {string} reason A custom connection close reason.\n */\n public close(code?: number, reason?: string): void {\n this.transport.close(code, reason)\n }\n}\n","import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport type { WebSocketData } from './WebSocketTransport'\nimport { bindEvent } from './utils/bindEvent'\nimport { CloseEvent } from './utils/events'\nimport { resolveWebSocketUrl } from '../../utils/resolveWebSocketUrl'\n\nexport type WebSocketEventListener<\n EventType extends WebSocketEventMap[keyof WebSocketEventMap] = Event,\n> = (this: WebSocket, event: EventType) => void\n\nconst WEBSOCKET_CLOSE_CODE_RANGE_ERROR =\n 'InvalidAccessError: close code out of user configurable range'\n\nexport const kPassthroughPromise = Symbol('kPassthroughPromise')\nexport const kOnSend = Symbol('kOnSend')\nexport const kClose = Symbol('kClose')\n\nexport class WebSocketOverride extends EventTarget implements WebSocket {\n static readonly CONNECTING = 0\n static readonly OPEN = 1\n static readonly CLOSING = 2\n static readonly CLOSED = 3\n readonly CONNECTING = 0\n readonly OPEN = 1\n readonly CLOSING = 2\n readonly CLOSED = 3\n\n public url: string\n public protocol: string\n public extensions: string\n public binaryType: BinaryType\n public readyState: WebSocket['readyState']\n public bufferedAmount: number\n\n private _onopen: WebSocketEventListener | null = null\n private _onmessage: WebSocketEventListener<\n MessageEvent<WebSocketData>\n > | null = null\n private _onerror: WebSocketEventListener | null = null\n private _onclose: WebSocketEventListener<CloseEvent> | null = null\n\n private [kPassthroughPromise]: DeferredPromise<boolean>\n private [kOnSend]?: (data: WebSocketData) => void\n\n constructor(url: string | URL, protocols?: string | Array<string>) {\n super()\n this.url = resolveWebSocketUrl(url)\n this.protocol = ''\n this.extensions = ''\n this.binaryType = 'blob'\n this.readyState = this.CONNECTING\n this.bufferedAmount = 0\n\n this[kPassthroughPromise] = new DeferredPromise<boolean>()\n\n queueMicrotask(async () => {\n if (await this[kPassthroughPromise]) {\n return\n }\n\n this.protocol =\n typeof protocols === 'string'\n ? protocols\n : Array.isArray(protocols) && protocols.length > 0\n ? protocols[0]\n : ''\n\n /**\n * @note Check that nothing has prevented this connection\n * (e.g. called `client.close()` in the connection listener).\n * If the connection has been prevented, never dispatch the open event,.\n */\n if (this.readyState === this.CONNECTING) {\n this.readyState = this.OPEN\n this.dispatchEvent(bindEvent(this, new Event('open')))\n }\n })\n }\n\n set onopen(listener: WebSocketEventListener | null) {\n this.removeEventListener('open', this._onopen)\n this._onopen = listener\n if (listener !== null) {\n this.addEventListener('open', listener)\n }\n }\n get onopen(): WebSocketEventListener | null {\n return this._onopen\n }\n\n set onmessage(\n listener: WebSocketEventListener<MessageEvent<WebSocketData>> | null\n ) {\n this.removeEventListener(\n 'message',\n this._onmessage as WebSocketEventListener\n )\n this._onmessage = listener\n if (listener !== null) {\n this.addEventListener('message', listener)\n }\n }\n get onmessage(): WebSocketEventListener<MessageEvent<WebSocketData>> | null {\n return this._onmessage\n }\n\n set onerror(listener: WebSocketEventListener | null) {\n this.removeEventListener('error', this._onerror)\n this._onerror = listener\n if (listener !== null) {\n this.addEventListener('error', listener)\n }\n }\n get onerror(): WebSocketEventListener | null {\n return this._onerror\n }\n\n set onclose(listener: WebSocketEventListener<CloseEvent> | null) {\n this.removeEventListener('close', this._onclose as WebSocketEventListener)\n this._onclose = listener\n if (listener !== null) {\n this.addEventListener('close', listener)\n }\n }\n get onclose(): WebSocketEventListener<CloseEvent> | null {\n return this._onclose\n }\n\n /**\n * @see https://websockets.spec.whatwg.org/#ref-for-dom-websocket-send%E2%91%A0\n */\n public send(data: WebSocketData): void {\n if (this.readyState === this.CONNECTING) {\n this.close()\n throw new DOMException('InvalidStateError')\n }\n\n // Sending when the socket is about to close\n // discards the sent data.\n if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {\n return\n }\n\n // Buffer the data to send in this even loop\n // but send it in the next.\n this.bufferedAmount += getDataSize(data)\n\n queueMicrotask(() => {\n // This is a bit optimistic but since no actual data transfer\n // is involved, all the data will be \"sent\" on the next tick.\n this.bufferedAmount = 0\n\n /**\n * @note Notify the parent about outgoing data.\n * This notifies the transport and the connection\n * listens to the outgoing data to emit the \"message\" event.\n */\n this[kOnSend]?.(data)\n })\n }\n\n public close(code: number = 1000, reason?: string): void {\n invariant(code, WEBSOCKET_CLOSE_CODE_RANGE_ERROR)\n invariant(\n code === 1000 || (code >= 3000 && code <= 4999),\n WEBSOCKET_CLOSE_CODE_RANGE_ERROR\n )\n\n this[kClose](code, reason)\n }\n\n private [kClose](\n code: number = 1000,\n reason?: string,\n wasClean = true\n ): void {\n /**\n * @note Move this check here so that even internal closures,\n * like those triggered by the `server` connection, are not\n * performed twice.\n */\n if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {\n return\n }\n\n this.readyState = this.CLOSING\n\n queueMicrotask(() => {\n this.readyState = this.CLOSED\n\n this.dispatchEvent(\n bindEvent(\n this,\n new CloseEvent('close', {\n code,\n reason,\n wasClean,\n })\n )\n )\n\n // Remove all event listeners once the socket is closed.\n this._onopen = null\n this._onmessage = null\n this._onerror = null\n this._onclose = null\n })\n }\n\n public addEventListener<K extends keyof WebSocketEventMap>(\n type: K,\n listener: (this: WebSocket, event: WebSocketEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions\n ): void\n public addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ): void\n public addEventListener(\n type: unknown,\n listener: unknown,\n options?: unknown\n ): void {\n return super.addEventListener(\n type as string,\n listener as EventListener,\n options as AddEventListenerOptions\n )\n }\n\n removeEventListener<K extends keyof WebSocketEventMap>(\n type: K,\n callback: EventListenerOrEventListenerObject | null,\n options?: boolean | EventListenerOptions\n ): void {\n return super.removeEventListener(type, callback, options)\n }\n}\n\nfunction getDataSize(data: WebSocketData): number {\n if (typeof data === 'string') {\n return data.length\n }\n\n if (data instanceof Blob) {\n return data.size\n }\n\n return data.byteLength\n}\n","import { invariant } from 'outvariant'\nimport {\n kClose,\n WebSocketEventListener,\n WebSocketOverride,\n} from './WebSocketOverride'\nimport type { WebSocketData } from './WebSocketTransport'\nimport type { WebSocketClassTransport } from './WebSocketClassTransport'\nimport { bindEvent } from './utils/bindEvent'\nimport {\n CancelableMessageEvent,\n CancelableCloseEvent,\n CloseEvent,\n} from './utils/events'\n\nconst kEmitter = Symbol('kEmitter')\nconst kBoundListener = Symbol('kBoundListener')\nconst kSend = Symbol('kSend')\n\nexport interface WebSocketServerEventMap {\n open: Event\n message: MessageEvent<WebSocketData>\n error: Event\n close: CloseEvent\n}\n\nexport abstract class WebSocketServerConnectionProtocol {\n public abstract connect(): void\n public abstract send(data: WebSocketData): void\n public abstract close(): void\n\n public abstract addEventListener<\n EventType extends keyof WebSocketServerEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void\n\n public abstract removeEventListener<\n EventType extends keyof WebSocketServerEventMap,\n >(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void\n}\n\n/**\n * The WebSocket server instance represents the actual production\n * WebSocket server connection. It's idle by default but you can\n * establish it by calling `server.connect()`.\n */\nexport class WebSocketServerConnection implements WebSocketServerConnectionProtocol {\n /**\n * A WebSocket instance connected to the original server.\n */\n private realWebSocket?: WebSocket\n private mockCloseController: AbortController\n private realCloseController: AbortController\n private [kEmitter]: EventTarget\n\n constructor(\n private readonly client: WebSocketOverride,\n private readonly transport: WebSocketClassTransport,\n private readonly createConnection: () => WebSocket\n ) {\n this[kEmitter] = new EventTarget()\n this.mockCloseController = new AbortController()\n this.realCloseController = new AbortController()\n\n // Automatically forward outgoing client events\n // to the actual server unless the outgoing message event\n // has been prevented. The \"outgoing\" transport event it\n // dispatched by the \"client\" connection.\n this.transport.addEventListener('outgoing', (event) => {\n // Ignore client messages if the server connection\n // hasn't been established yet. Nowhere to forward.\n if (typeof this.realWebSocket === 'undefined') {\n return\n }\n\n // Every outgoing client message can prevent this forwarding\n // by preventing the default of the outgoing message event.\n // This listener will be added before user-defined listeners,\n // so execute the logic on the next tick.\n queueMicrotask(() => {\n if (!event.defaultPrevented) {\n /**\n * @note Use the internal send mechanism so consumers can tell\n * apart direct user calls to `server.send()` and internal calls.\n * E.g. MSW has to ignore this internal call to log out messages correctly.\n */\n this[kSend](event.data)\n }\n })\n })\n\n this.transport.addEventListener(\n 'incoming',\n this.handleIncomingMessage.bind(this)\n )\n }\n\n /**\n * The `WebSocket` instance connected to the original server.\n * Accessing this before calling `server.connect()` will throw.\n */\n public get socket(): WebSocket {\n invariant(\n this.realWebSocket,\n 'Cannot access \"socket\" on the original WebSocket server object: the connection is not open. Did you forget to call `server.connect()`?'\n )\n\n return this.realWebSocket\n }\n\n /**\n * Open connection to the original WebSocket server.\n */\n public connect(): void {\n invariant(\n !this.realWebSocket || this.realWebSocket.readyState !== WebSocket.OPEN,\n 'Failed to call \"connect()\" on the original WebSocket instance: the connection already open'\n )\n\n const realWebSocket = this.createConnection()\n\n // Inherit the binary type from the mock WebSocket client.\n realWebSocket.binaryType = this.client.binaryType\n\n // Allow the interceptor to listen to when the server connection\n // has been established. This isn't necessary to operate with the connection\n // but may be beneficial in some cases (like conditionally adding logging).\n realWebSocket.addEventListener(\n 'open',\n (event) => {\n this[kEmitter].dispatchEvent(\n bindEvent(this.realWebSocket!, new Event('open', event))\n )\n },\n { once: true }\n )\n\n realWebSocket.addEventListener('message', (event) => {\n // Dispatch the \"incoming\" transport event instead of\n // invoking the internal handler directly. This way,\n // anyone can listen to the \"incoming\" event but this\n // class is the one resulting in it.\n this.transport.dispatchEvent(\n bindEvent(\n this.realWebSocket!,\n new MessageEvent('incoming', {\n data: event.data,\n origin: event.origin,\n })\n )\n )\n })\n\n // Close the original connection when the mock client closes.\n // E.g. \"client.close()\" was called. This is never forwarded anywhere.\n this.client.addEventListener(\n 'close',\n (event) => {\n this.handleMockClose(event)\n },\n {\n signal: this.mockCloseController.signal,\n }\n )\n\n // Forward the \"close\" event to let the interceptor handle\n // closures initiated by the original server.\n realWebSocket.addEventListener(\n 'close',\n (event) => {\n this.handleRealClose(event)\n },\n {\n signal: this.realCloseController.signal,\n }\n )\n\n realWebSocket.addEventListener('error', () => {\n const errorEvent = bindEvent(\n realWebSocket,\n new Event('error', { cancelable: true })\n )\n\n // Emit the \"error\" event on the `server` connection\n // to let the interceptor react to original server errors.\n this[kEmitter].dispatchEvent(errorEvent)\n\n // If the error event from the original server hasn't been prevented,\n // forward it to the underlying client.\n if (!errorEvent.defaultPrevented) {\n this.client.dispatchEvent(bindEvent(this.client, new Event('error')))\n }\n })\n\n this.realWebSocket = realWebSocket\n }\n\n /**\n * Listen for the incoming events from the original WebSocket server.\n */\n public addEventListener<EventType extends keyof WebSocketServerEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: AddEventListenerOptions | boolean\n ): void {\n if (!Reflect.has(listener, kBoundListener)) {\n const boundListener = listener.bind(this.client)\n\n // Store the bound listener on the original listener\n // so the exact bound function can be accessed in \"removeEventListener()\".\n Object.defineProperty(listener, kBoundListener, {\n value: boundListener,\n enumerable: false,\n })\n }\n\n this[kEmitter].addEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Remove the listener for the given event.\n */\n public removeEventListener<EventType extends keyof WebSocketServerEventMap>(\n event: EventType,\n listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>,\n options?: EventListenerOptions | boolean\n ): void {\n this[kEmitter].removeEventListener(\n event,\n Reflect.get(listener, kBoundListener) as EventListener,\n options\n )\n }\n\n /**\n * Send data to the original WebSocket server.\n * @example\n * server.send('hello')\n * server.send(new Blob(['hello']))\n * server.send(new TextEncoder().encode('hello'))\n */\n public send(data: WebSocketData): void {\n this[kSend](data)\n }\n\n private [kSend](data: WebSocketData): void {\n const { realWebSocket } = this\n\n invariant(\n realWebSocket,\n 'Failed to call \"server.send()\" for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?',\n this.client.url\n )\n\n // Silently ignore writes on the closed original WebSocket.\n if (\n realWebSocket.readyState === WebSocket.CLOSING ||\n realWebSocket.readyState === WebSocket.CLOSED\n ) {\n return\n }\n\n // Delegate the send to when the original connection is open.\n // Unlike the mock, connecting to the original server may take time\n // so we cannot call this on the next tick.\n if (realWebSocket.readyState === WebSocket.CONNECTING) {\n realWebSocket.addEventListener(\n 'open',\n () => {\n realWebSocket.send(data)\n },\n { once: true }\n )\n return\n }\n\n // Send the data to the original WebSocket server.\n realWebSocket.send(data)\n }\n\n /**\n * Close the actual server connection.\n */\n public close(): void {\n const { realWebSocket } = this\n\n invariant(\n realWebSocket,\n 'Failed to close server connection for \"%s\": the connection is not open. Did you forget to call \"server.connect()\"?',\n this.client.url\n )\n\n // Remove the \"close\" event listener from the server\n // so it doesn't close the underlying WebSocket client\n // when you call \"server.close()\". This also prevents the\n // `close` event on the `server` connection from being dispatched twice.\n this.realCloseController.abort()\n\n if (\n realWebSocket.readyState === WebSocket.CLOSING ||\n realWebSocket.readyState === WebSocket.CLOSED\n ) {\n return\n }\n\n // Close the actual client connection.\n realWebSocket.close()\n\n // Dispatch the \"close\" event on the `server` connection.\n queueMicrotask(() => {\n this[kEmitter].dispatchEvent(\n bindEvent(\n this.realWebSocket,\n new CancelableCloseEvent('close', {\n /**\n * @note `server.close()` in the interceptor\n * always results in clean closures.\n */\n code: 1000,\n cancelable: true,\n })\n )\n )\n })\n }\n\n private handleIncomingMessage(event: MessageEvent<WebSocketData>): void {\n // Clone the event to dispatch it on this class\n // once again and prevent the \"already being dispatched\"\n // exception. Clone it here so we can observe this event\n // being prevented in the \"server.on()\" listeners.\n const messageEvent = bindEvent(\n event.target,\n new CancelableMessageEvent('message', {\n data: event.data,\n origin: event.origin,\n cancelable: true,\n })\n )\n\n /**\n * @note Emit \"message\" event on the server connection\n * instance to let the interceptor know about these\n * incoming events from the original server. In that listener,\n * the interceptor can modify or skip the event forwarding\n * to the mock WebSocket instance.\n */\n this[kEmitter].dispatchEvent(messageEvent)\n\n /**\n * @note Forward the incoming server events to the client.\n * Preventing the default on the message event stops this.\n */\n if (!messageEvent.defaultPrevented) {\n this.client.dispatchEvent(\n bindEvent(\n /**\n * @note Bind the forwarded original server events\n * to the mock WebSocket instance so it would\n * dispatch them straight away.\n */\n this.client,\n // Clone the message event again to prevent\n // the \"already being dispatched\" exception.\n new MessageEvent('message', {\n data: event.data,\n origin: event.origin,\n })\n )\n )\n }\n }\n\n private handleMockClose(_event: Event): void {\n // Close the original connection if the mock client closes.\n if (this.realWebSocket) {\n this.realWebSocket.close()\n }\n }\n\n private handleRealClose(event: CloseEvent): void {\n // For closures originating from the original server,\n // remove the \"close\" listener from the mock client.\n // original close -> (?) client[kClose]() --X--> \"close\" (again).\n this.mockCloseController.abort()\n\n const closeEvent = bindEvent(\n this.realWebSocket,\n new CancelableCloseEvent('close', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n cancelable: true,\n })\n )\n\n this[kEmitter].dispatchEvent(closeEvent)\n\n // If the close event from the server hasn't been prevented,\n // forward the closure to the mock client.\n if (!closeEvent.defaultPrevented) {\n // Close the intercepted client forcefully to\n // allow non-configurable status codes from the server.\n // If the socket has been closed by now, no harm calling\n // this again—it will have no effect.\n this.client[kClose](event.code, event.reason)\n }\n }\n}\n","import { bindEvent } from './utils/bindEvent'\nimport {\n StrictEventListenerOrEventListenerObject,\n WebSocketData,\n WebSocketTransport,\n WebSocketTransportEventMap,\n} from './WebSocketTransport'\nimport { kOnSend, kClose, WebSocketOverride } from './WebSocketOverride'\nimport { CancelableMessageEvent, CloseEvent } from './utils/events'\n\n/**\n * Abstraction over the given mock `WebSocket` instance that allows\n * for controlling that instance (e.g. sending and receiving messages).\n */\nexport class WebSocketClassTransport\n extends EventTarget\n implements WebSocketTransport\n{\n constructor(protected readonly socket: WebSocketOverride) {\n super()\n\n // Emit the \"close\" event on the transport if the close\n // originates from the WebSocket client. E.g. the application\n // calls \"ws.close()\", not the interceptor.\n this.socket.addEventListener('close', (event) => {\n this.dispatchEvent(bindEvent(this.socket, new CloseEvent('close', event)))\n })\n\n /**\n * Emit the \"outgoing\" event on the transport\n * whenever the WebSocket client sends data (\"ws.send()\").\n */\n this.socket[kOnSend] = (data) => {\n this.dispatchEvent(\n bindEvent(\n this.socket,\n // Dispatch this as cancelable because \"client\" connection\n // re-creates this message event (cannot dispatch the same event).\n new CancelableMessageEvent('outgoing', {\n data,\n origin: this.socket.url,\n cancelable: true,\n })\n )\n )\n }\n }\n\n public addEventListener<EventType extends keyof WebSocketTransportEventMap>(\n type: EventType,\n callback: StrictEventListenerOrEventListenerObject<\n WebSocketTransportEventMap[EventType]\n > | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n return super.addEventListener(type, callback as EventListener, options)\n }\n\n public dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(\n event: WebSocketTransportEventMap[EventType]\n ): boolean {\n return super.dispatchEvent(event)\n }\n\n public send(data: WebSocketData): void {\n queueMicrotask(() => {\n if (\n this.socket.readyState === this.socket.CLOSING ||\n this.socket.readyState === this.socket.CLOSED\n ) {\n return\n }\n\n const dispatchEvent = () => {\n this.socket.dispatchEvent(\n bindEvent(\n /**\n * @note Setting this event's \"target\" to the\n * WebSocket override instance is important.\n * This way it can tell apart original incoming events\n * (must be forwarded to the transport) from the\n * mocked message events like the one below\n * (must be dispatched on the client instance).\n */\n this.socket,\n new MessageEvent('message', {\n data,\n origin: this.socket.url,\n })\n )\n )\n }\n\n if (this.socket.readyState === this.socket.CONNECTING) {\n this.socket.addEventListener(\n 'open',\n () => {\n dispatchEvent()\n },\n { once: true }\n )\n } else {\n dispatchEvent()\n }\n })\n }\n\n public close(code: number, reason?: string): void {\n /**\n * @note Call the internal close method directly\n * to allow closing the connection with the status codes\n * that are non-configurable by the user (> 1000 <= 1015).\n */\n this.socket[kClose](code, reason)\n }\n}\n","import { Interceptor } from '../../Interceptor'\nimport {\n WebSocketClientConnectionProtocol,\n WebSocketClientConnection,\n type WebSocketClientEventMap,\n} from './WebSocketClientConnection'\nimport {\n WebSocketServerConnectionProtocol,\n WebSocketServerConnection,\n type WebSocketServerEventMap,\n} from './WebSocketServerConnection'\nimport { WebSocketClassTransport } from './WebSocketClassTransport'\nimport {\n kClose,\n kPassthroughPromise,\n WebSocketOverride,\n} from './WebSocketOverride'\nimport { bindEvent } from './utils/bindEvent'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { patchesRegistry } from '../../utils/patchesRegistry'\n\nexport {\n type WebSocketData,\n type WebSocketTransport,\n} from './WebSocketTransport'\nexport {\n WebSocketClientEventMap,\n WebSocketClientConnectionProtocol,\n WebSocketClientConnection,\n WebSocketServerEventMap,\n WebSocketServerConnectionProtocol,\n WebSocketServerConnection,\n}\n\nexport {\n CloseEvent,\n CancelableCloseEvent,\n CancelableMessageEvent,\n} from './utils/events'\n\nexport type WebSocketEventMap = {\n connection: [args: WebSocketConnectionData]\n}\n\nexport type WebSocketConnectionData = {\n /**\n * The incoming WebSocket client connection.\n */\n client: WebSocketClientConnection\n\n /**\n * The original WebSocket server connection.\n */\n server: WebSocketServerConnection\n\n /**\n * The connection information.\n */\n info: {\n /**\n * The protocols supported by the WebSocket client.\n */\n protocols: string | Array<string> | undefined\n }\n}\n\n/**\n * Intercept the outgoing WebSocket connections created using\n * the global `WebSocket` class.\n */\nexport class WebSocketInterceptor extends Interceptor<WebSocketEventMap> {\n static symbol = Symbol('websocket')\n\n constructor() {\n super(WebSocketInterceptor.symbol)\n }\n\n protected checkEnvironment(): boolean {\n return hasConfigurableGlobal('WebSocket')\n }\n\n protected setup(): void {\n const logger = this.logger.extend('setup')\n\n const WebSocketProxy = new Proxy(globalThis.WebSocket, {\n construct: (\n target,\n args: ConstructorParameters<typeof globalThis.WebSocket>,\n newTarget\n ) => {\n const [url, protocols] = args\n\n const createConnection = (): WebSocket => {\n return Reflect.construct(target, args, newTarget)\n }\n\n // All WebSocket instances are mocked and don't forward\n // any events to the original server (no connection established).\n // To forward the events, the user must use the \"server.send()\" API.\n const socket = new WebSocketOverride(url, protocols)\n const transport = new WebSocketClassTransport(socket)\n\n // Emit the \"connection\" event to the interceptor on the next tick\n // so the client can modify WebSocket options, like \"binaryType\"\n // while the connection is already pending.\n queueMicrotask(async () => {\n try {\n const server = new WebSocketServerConnection(\n socket,\n transport,\n createConnection\n )\n\n const hasConnectionListeners =\n this.emitter.listenerCount('connection') > 0\n\n // The \"globalThis.WebSocket\" class stands for\n // the client-side connection. Assume it's established\n // as soon as the WebSocket instance is constructed.\n await emitAsync(this.emitter, 'connection', {\n client: new WebSocketClientConnection(socket, transport),\n server,\n info: {\n protocols,\n },\n })\n\n if (hasConnectionListeners) {\n socket[kPassthroughPromise].resolve(false)\n } else {\n socket[kPassthroughPromise].resolve(true)\n\n server.connect()\n\n // Forward the \"open\" event from the original server\n // to the mock WebSocket client in the case of a passthrough connection.\n server.addEventListener('open', () => {\n socket.dispatchEvent(bindEvent(socket, new Event('open')))\n\n // Forward the original connection protocol to the\n // mock WebSocket client.\n if (server['realWebSocket']) {\n socket.protocol = server['realWebSocket'].protocol\n }\n })\n }\n } catch (error) {\n /**\n * @note Translate unhandled exceptions during the connection\n * handling (i.e. interceptor exceptions) as WebSocket connection\n * closures with error. This prevents from the exceptions occurring\n * in `queueMicrotask` from being process-wide and uncatchable.\n */\n if (error instanceof Error) {\n socket.dispatchEvent(new Event('error'))\n\n // No need to close the connection if it's already being closed.\n // E.g. the interceptor called `client.close()` and then threw an error.\n if (\n socket.readyState !== WebSocket.CLOSING &&\n socket.readyState !== WebSocket.CLOSED\n ) {\n socket[kClose](1011, error.message, false)\n }\n\n console.error(error)\n }\n }\n })\n\n return socket\n },\n })\n\n logger.info('patching global WebSocket...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'WebSocket', () => WebSocketProxy)\n )\n\n logger.info('global WebSocket patched!', globalThis.WebSocket.name)\n }\n}\n"],"mappings":"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"} |
| require('../../createRequestId-DOf8Ktjs.cjs'); | ||
| require('../../getRawRequest-B6GjFKAr.cjs'); | ||
| require('../../getRawRequest-DdfaiPVH.cjs'); | ||
| require('../../bufferUtils-Uc0eRItL.cjs'); | ||
| require('../../hasConfigurableGlobal-B5o6PHq3.cjs'); | ||
| require('../../handleRequest-BIte-8l8.cjs'); | ||
| const require_XMLHttpRequest = require('../../XMLHttpRequest-DNly-bu0.cjs'); | ||
| require('../../hasConfigurableGlobal-BS75Oulv.cjs'); | ||
| require('../../handleRequest-tUNU616J.cjs'); | ||
| const require_XMLHttpRequest = require('../../XMLHttpRequest-CHqOQWxg.cjs'); | ||
| exports.XMLHttpRequestInterceptor = require_XMLHttpRequest.XMLHttpRequestInterceptor; |
| import "../../createRequestId-DYCsFHOi.mjs"; | ||
| import "../../getRawRequest-BP--nUwn.mjs"; | ||
| import "../../getRawRequest-B1BqgWG6.mjs"; | ||
| import "../../bufferUtils-BiiO6HZv.mjs"; | ||
| import "../../hasConfigurableGlobal-B39W0thh.mjs"; | ||
| import "../../handleRequest-CnDA8MVO.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "../../XMLHttpRequest-DaQ73vVt.mjs"; | ||
| import "../../hasConfigurableGlobal-FTYwno1G.mjs"; | ||
| import "../../handleRequest-BHrC8Flw.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "../../XMLHttpRequest-DP9ps_69.mjs"; | ||
| export { XMLHttpRequestInterceptor }; |
| require('../createRequestId-DOf8Ktjs.cjs'); | ||
| require('../getRawRequest-B6GjFKAr.cjs'); | ||
| require('../getRawRequest-DdfaiPVH.cjs'); | ||
| require('../bufferUtils-Uc0eRItL.cjs'); | ||
| require('../hasConfigurableGlobal-B5o6PHq3.cjs'); | ||
| require('../handleRequest-BIte-8l8.cjs'); | ||
| const require_fetch = require('../fetch-BmrYPMQ0.cjs'); | ||
| const require_XMLHttpRequest = require('../XMLHttpRequest-DNly-bu0.cjs'); | ||
| require('../hasConfigurableGlobal-BS75Oulv.cjs'); | ||
| require('../handleRequest-tUNU616J.cjs'); | ||
| const require_fetch = require('../fetch-CtkfZi4Z.cjs'); | ||
| const require_XMLHttpRequest = require('../XMLHttpRequest-CHqOQWxg.cjs'); | ||
@@ -9,0 +9,0 @@ //#region src/presets/browser.ts |
| import "../createRequestId-DYCsFHOi.mjs"; | ||
| import "../getRawRequest-BP--nUwn.mjs"; | ||
| import "../getRawRequest-B1BqgWG6.mjs"; | ||
| import "../bufferUtils-BiiO6HZv.mjs"; | ||
| import "../hasConfigurableGlobal-B39W0thh.mjs"; | ||
| import "../handleRequest-CnDA8MVO.mjs"; | ||
| import { t as FetchInterceptor } from "../fetch-BS6cxWzo.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "../XMLHttpRequest-DaQ73vVt.mjs"; | ||
| import "../hasConfigurableGlobal-FTYwno1G.mjs"; | ||
| import "../handleRequest-BHrC8Flw.mjs"; | ||
| import { t as FetchInterceptor } from "../fetch-Ddj5TV04.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "../XMLHttpRequest-DP9ps_69.mjs"; | ||
@@ -9,0 +9,0 @@ //#region src/presets/browser.ts |
@@ -1,3 +0,3 @@ | ||
| const require_fetchUtils = require('./fetchUtils-BAlG765A.cjs'); | ||
| const require_BatchInterceptor = require('./BatchInterceptor-Bo8JunSK.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-umV5xXBy.cjs'); | ||
| const require_BatchInterceptor = require('./BatchInterceptor-Z1lwK23r.cjs'); | ||
| const require_bufferUtils = require('./bufferUtils-S5_-2eN4.cjs'); | ||
@@ -4,0 +4,0 @@ const require_getRawRequest = require('./getRawRequest-B6znU6il.cjs'); |
@@ -61,2 +61,3 @@ import { a as InterceptorReadyState, c as getGlobalSymbol, d as RequestController, f as RequestControllerSource, i as InterceptorEventMap, l as HttpRequestEventMap, n as INTERNAL_REQUEST_ID_HEADER_NAME, o as InterceptorSubscription, r as Interceptor, s as deleteGlobalSymbol, t as ExtractEventNames, u as RequestCredentials } from "./Interceptor-BsZ21ue0.cjs"; | ||
| static isResponseWithBody(status: number): boolean; | ||
| static setStatus(status: number, response: Response): void; | ||
| static setUrl(url: string | undefined, response: Response): void; | ||
@@ -76,2 +77,3 @@ /** | ||
| constructor(body?: BodyInit | null, init?: FetchResponseInit); | ||
| clone(): Response; | ||
| } | ||
@@ -78,0 +80,0 @@ //#endregion |
@@ -61,2 +61,3 @@ import { a as InterceptorReadyState, c as getGlobalSymbol, d as RequestController, f as RequestControllerSource, i as InterceptorEventMap, l as HttpRequestEventMap, n as INTERNAL_REQUEST_ID_HEADER_NAME, o as InterceptorSubscription, r as Interceptor, s as deleteGlobalSymbol, t as ExtractEventNames, u as RequestCredentials } from "./Interceptor-soD-WpQO.mjs"; | ||
| static isResponseWithBody(status: number): boolean; | ||
| static setStatus(status: number, response: Response): void; | ||
| static setUrl(url: string | undefined, response: Response): void; | ||
@@ -76,2 +77,3 @@ /** | ||
| constructor(body?: BodyInit | null, init?: FetchResponseInit); | ||
| clone(): Response; | ||
| } | ||
@@ -78,0 +80,0 @@ //#endregion |
@@ -1,3 +0,3 @@ | ||
| import { a as RequestController, c as Interceptor, d as getGlobalSymbol, i as createRequestId, l as InterceptorReadyState, n as FetchResponse, s as INTERNAL_REQUEST_ID_HEADER_NAME, t as FetchRequest, u as deleteGlobalSymbol } from "./fetchUtils-Bq0Mdmkv.mjs"; | ||
| import { t as BatchInterceptor } from "./BatchInterceptor-0prNZ0wK.mjs"; | ||
| import { a as RequestController, c as Interceptor, d as getGlobalSymbol, i as createRequestId, l as InterceptorReadyState, n as FetchResponse, s as INTERNAL_REQUEST_ID_HEADER_NAME, t as FetchRequest, u as deleteGlobalSymbol } from "./fetchUtils-BKJ1XmiO.mjs"; | ||
| import { t as BatchInterceptor } from "./BatchInterceptor-DdMNfSUY.mjs"; | ||
| import { n as encodeBuffer, t as decodeBuffer } from "./bufferUtils-DxPxwff_.mjs"; | ||
@@ -4,0 +4,0 @@ import { t as getRawRequest } from "./getRawRequest-C2-1urzA.mjs"; |
@@ -1,6 +0,6 @@ | ||
| require('../../fetchUtils-BAlG765A.cjs'); | ||
| const require_ClientRequest = require('../../ClientRequest-OY5NnrOh.cjs'); | ||
| require('../../handleRequest-DUPo40Df.cjs'); | ||
| require('../../fetchUtils-umV5xXBy.cjs'); | ||
| const require_ClientRequest = require('../../ClientRequest-DFs7FPNi.cjs'); | ||
| require('../../handleRequest-DVOthWJo.cjs'); | ||
| require('../../node-DIKcnzhK.cjs'); | ||
| exports.ClientRequestInterceptor = require_ClientRequest.ClientRequestInterceptor; |
@@ -1,6 +0,6 @@ | ||
| import "../../fetchUtils-Bq0Mdmkv.mjs"; | ||
| import { t as ClientRequestInterceptor } from "../../ClientRequest-BquJl6T-.mjs"; | ||
| import "../../handleRequest-u6jpjgrr.mjs"; | ||
| import "../../fetchUtils-BKJ1XmiO.mjs"; | ||
| import { t as ClientRequestInterceptor } from "../../ClientRequest-dRpI9v1o.mjs"; | ||
| import "../../handleRequest-DCLzePtS.mjs"; | ||
| import "../../node-lsdNwZEW.mjs"; | ||
| export { ClientRequestInterceptor }; |
@@ -1,6 +0,6 @@ | ||
| require('../../fetchUtils-BAlG765A.cjs'); | ||
| require('../../handleRequest-DUPo40Df.cjs'); | ||
| require('../../hasConfigurableGlobal-Befl_QJD.cjs'); | ||
| const require_fetch = require('../../fetch-B9UiBg-M.cjs'); | ||
| require('../../fetchUtils-umV5xXBy.cjs'); | ||
| require('../../handleRequest-DVOthWJo.cjs'); | ||
| require('../../hasConfigurableGlobal-BtHi5OlL.cjs'); | ||
| const require_fetch = require('../../fetch-znfXVEPe.cjs'); | ||
| exports.FetchInterceptor = require_fetch.FetchInterceptor; |
@@ -1,6 +0,6 @@ | ||
| import "../../fetchUtils-Bq0Mdmkv.mjs"; | ||
| import "../../handleRequest-u6jpjgrr.mjs"; | ||
| import "../../hasConfigurableGlobal-jN_-v3gp.mjs"; | ||
| import { t as FetchInterceptor } from "../../fetch-UxWd1aQX.mjs"; | ||
| import "../../fetchUtils-BKJ1XmiO.mjs"; | ||
| import "../../handleRequest-DCLzePtS.mjs"; | ||
| import "../../hasConfigurableGlobal-Cwrs35Xo.mjs"; | ||
| import { t as FetchInterceptor } from "../../fetch-BciBtVqm.mjs"; | ||
| export { FetchInterceptor }; |
@@ -1,7 +0,7 @@ | ||
| require('../../fetchUtils-BAlG765A.cjs'); | ||
| require('../../fetchUtils-umV5xXBy.cjs'); | ||
| require('../../bufferUtils-S5_-2eN4.cjs'); | ||
| require('../../handleRequest-DUPo40Df.cjs'); | ||
| const require_XMLHttpRequest = require('../../XMLHttpRequest-C1KTngEF.cjs'); | ||
| require('../../hasConfigurableGlobal-Befl_QJD.cjs'); | ||
| require('../../handleRequest-DVOthWJo.cjs'); | ||
| const require_XMLHttpRequest = require('../../XMLHttpRequest-CUbLHiq6.cjs'); | ||
| require('../../hasConfigurableGlobal-BtHi5OlL.cjs'); | ||
| exports.XMLHttpRequestInterceptor = require_XMLHttpRequest.XMLHttpRequestInterceptor; |
@@ -1,7 +0,7 @@ | ||
| import "../../fetchUtils-Bq0Mdmkv.mjs"; | ||
| import "../../fetchUtils-BKJ1XmiO.mjs"; | ||
| import "../../bufferUtils-DxPxwff_.mjs"; | ||
| import "../../handleRequest-u6jpjgrr.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "../../XMLHttpRequest-Cj07Pos7.mjs"; | ||
| import "../../hasConfigurableGlobal-jN_-v3gp.mjs"; | ||
| import "../../handleRequest-DCLzePtS.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "../../XMLHttpRequest-BgkpwNOu.mjs"; | ||
| import "../../hasConfigurableGlobal-Cwrs35Xo.mjs"; | ||
| export { XMLHttpRequestInterceptor }; |
@@ -1,9 +0,9 @@ | ||
| require('../fetchUtils-BAlG765A.cjs'); | ||
| require('../fetchUtils-umV5xXBy.cjs'); | ||
| require('../bufferUtils-S5_-2eN4.cjs'); | ||
| const require_ClientRequest = require('../ClientRequest-OY5NnrOh.cjs'); | ||
| require('../handleRequest-DUPo40Df.cjs'); | ||
| const require_ClientRequest = require('../ClientRequest-DFs7FPNi.cjs'); | ||
| require('../handleRequest-DVOthWJo.cjs'); | ||
| require('../node-DIKcnzhK.cjs'); | ||
| const require_XMLHttpRequest = require('../XMLHttpRequest-C1KTngEF.cjs'); | ||
| require('../hasConfigurableGlobal-Befl_QJD.cjs'); | ||
| const require_fetch = require('../fetch-B9UiBg-M.cjs'); | ||
| const require_XMLHttpRequest = require('../XMLHttpRequest-CUbLHiq6.cjs'); | ||
| require('../hasConfigurableGlobal-BtHi5OlL.cjs'); | ||
| const require_fetch = require('../fetch-znfXVEPe.cjs'); | ||
@@ -10,0 +10,0 @@ //#region src/presets/node.ts |
@@ -1,9 +0,9 @@ | ||
| import "../fetchUtils-Bq0Mdmkv.mjs"; | ||
| import "../fetchUtils-BKJ1XmiO.mjs"; | ||
| import "../bufferUtils-DxPxwff_.mjs"; | ||
| import { t as ClientRequestInterceptor } from "../ClientRequest-BquJl6T-.mjs"; | ||
| import "../handleRequest-u6jpjgrr.mjs"; | ||
| import { t as ClientRequestInterceptor } from "../ClientRequest-dRpI9v1o.mjs"; | ||
| import "../handleRequest-DCLzePtS.mjs"; | ||
| import "../node-lsdNwZEW.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "../XMLHttpRequest-Cj07Pos7.mjs"; | ||
| import "../hasConfigurableGlobal-jN_-v3gp.mjs"; | ||
| import { t as FetchInterceptor } from "../fetch-UxWd1aQX.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "../XMLHttpRequest-BgkpwNOu.mjs"; | ||
| import "../hasConfigurableGlobal-Cwrs35Xo.mjs"; | ||
| import { t as FetchInterceptor } from "../fetch-BciBtVqm.mjs"; | ||
@@ -10,0 +10,0 @@ //#region src/presets/node.ts |
@@ -1,10 +0,10 @@ | ||
| const require_fetchUtils = require('./fetchUtils-BAlG765A.cjs'); | ||
| const require_BatchInterceptor = require('./BatchInterceptor-Bo8JunSK.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-umV5xXBy.cjs'); | ||
| const require_BatchInterceptor = require('./BatchInterceptor-Z1lwK23r.cjs'); | ||
| require('./bufferUtils-S5_-2eN4.cjs'); | ||
| const require_ClientRequest = require('./ClientRequest-OY5NnrOh.cjs'); | ||
| const require_handleRequest = require('./handleRequest-DUPo40Df.cjs'); | ||
| const require_ClientRequest = require('./ClientRequest-DFs7FPNi.cjs'); | ||
| const require_handleRequest = require('./handleRequest-DVOthWJo.cjs'); | ||
| require('./node-DIKcnzhK.cjs'); | ||
| const require_XMLHttpRequest = require('./XMLHttpRequest-C1KTngEF.cjs'); | ||
| require('./hasConfigurableGlobal-Befl_QJD.cjs'); | ||
| const require_fetch = require('./fetch-B9UiBg-M.cjs'); | ||
| const require_XMLHttpRequest = require('./XMLHttpRequest-CUbLHiq6.cjs'); | ||
| require('./hasConfigurableGlobal-BtHi5OlL.cjs'); | ||
| const require_fetch = require('./fetch-znfXVEPe.cjs'); | ||
@@ -11,0 +11,0 @@ //#region src/RemoteHttpInterceptor.ts |
@@ -1,10 +0,10 @@ | ||
| import { a as RequestController, c as Interceptor, n as FetchResponse, t as FetchRequest } from "./fetchUtils-Bq0Mdmkv.mjs"; | ||
| import { t as BatchInterceptor } from "./BatchInterceptor-0prNZ0wK.mjs"; | ||
| import { a as RequestController, c as Interceptor, n as FetchResponse, t as FetchRequest } from "./fetchUtils-BKJ1XmiO.mjs"; | ||
| import { t as BatchInterceptor } from "./BatchInterceptor-DdMNfSUY.mjs"; | ||
| import "./bufferUtils-DxPxwff_.mjs"; | ||
| import { t as ClientRequestInterceptor } from "./ClientRequest-BquJl6T-.mjs"; | ||
| import { n as isResponseError, t as handleRequest } from "./handleRequest-u6jpjgrr.mjs"; | ||
| import { t as ClientRequestInterceptor } from "./ClientRequest-dRpI9v1o.mjs"; | ||
| import { n as isResponseError, t as handleRequest } from "./handleRequest-DCLzePtS.mjs"; | ||
| import "./node-lsdNwZEW.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "./XMLHttpRequest-Cj07Pos7.mjs"; | ||
| import "./hasConfigurableGlobal-jN_-v3gp.mjs"; | ||
| import { t as FetchInterceptor } from "./fetch-UxWd1aQX.mjs"; | ||
| import { t as XMLHttpRequestInterceptor } from "./XMLHttpRequest-BgkpwNOu.mjs"; | ||
| import "./hasConfigurableGlobal-Cwrs35Xo.mjs"; | ||
| import { t as FetchInterceptor } from "./fetch-BciBtVqm.mjs"; | ||
@@ -11,0 +11,0 @@ //#region src/RemoteHttpInterceptor.ts |
+1
-1
@@ -5,3 +5,3 @@ { | ||
| "description": "Low-level HTTP/HTTPS/XHR/fetch request interception library.", | ||
| "version": "0.41.6", | ||
| "version": "0.41.7", | ||
| "main": "./lib/node/index.cjs", | ||
@@ -8,0 +8,0 @@ "module": "./lib/node/index.mjs", |
@@ -259,1 +259,34 @@ // @vitest-environment node | ||
| }) | ||
| /** | ||
| * Node.js 24+ may pass additional internal arguments to Headers.prototype.set | ||
| * and Headers.prototype.append. This test ensures we only record the first | ||
| * two arguments (name, value) and ignore any additional internal arguments. | ||
| * @see https://github.com/mswjs/interceptors/issues/762 | ||
| */ | ||
| it('ignores extra internal arguments passed to .set() and .append()', () => { | ||
| recordRawFetchHeaders() | ||
| const headers = new Headers() | ||
| // Simulate Node.js 24+ behavior where internal calls may pass extra arguments | ||
| // by calling the prototype methods directly with additional arguments. | ||
| Headers.prototype.set.call( | ||
| headers, | ||
| 'X-Set-Header', | ||
| 'set-value', | ||
| // @ts-expect-error Internal argument | ||
| true | ||
| ) | ||
| Headers.prototype.append.call( | ||
| headers, | ||
| 'X-Append-Header', | ||
| 'append-value', | ||
| // @ts-expect-error Internal argument | ||
| true | ||
| ) | ||
| expect(getRawFetchHeaders(headers)).toEqual([ | ||
| ['X-Set-Header', 'set-value'], | ||
| ['X-Append-Header', 'append-value'], | ||
| ]) | ||
| }) |
@@ -123,5 +123,14 @@ import { FetchRequest, FetchResponse } from '../../../utils/fetchUtils' | ||
| ) { | ||
| // Ensure each header tuple has exactly 2 elements (name, value). | ||
| // Node.js 24+ may have stored tuples with extra internal arguments. | ||
| const rawHeadersFromInit = Reflect.get( | ||
| headersInit, | ||
| kRawHeaders | ||
| ) as RawHeaders | ||
| const sanitizedHeaders = rawHeadersFromInit.map( | ||
| (tuple): HeaderTuple => [tuple[0], tuple[1]] | ||
| ) | ||
| const headers = Reflect.construct( | ||
| target, | ||
| [Reflect.get(headersInit, kRawHeaders)], | ||
| [sanitizedHeaders], | ||
| newTarget | ||
@@ -135,3 +144,3 @@ ) | ||
| */ | ||
| ...Reflect.get(headersInit, kRawHeaders), | ||
| ...sanitizedHeaders, | ||
| ]) | ||
@@ -161,3 +170,6 @@ return headers | ||
| apply(target, thisArg, args: HeaderTuple) { | ||
| recordRawHeader(thisArg, args, 'set') | ||
| // Use only the first two arguments (name, value) to record raw headers. | ||
| // Node.js 24+ may pass additional internal arguments that should not | ||
| // be included in the raw headers array. | ||
| recordRawHeader(thisArg, [args[0], args[1]], 'set') | ||
| return Reflect.apply(target, thisArg, args) | ||
@@ -169,3 +181,6 @@ }, | ||
| apply(target, thisArg, args: HeaderTuple) { | ||
| recordRawHeader(thisArg, args, 'append') | ||
| // Use only the first two arguments (name, value) to record raw headers. | ||
| // Node.js 24+ may pass additional internal arguments that should not | ||
| // be included in the raw headers array. | ||
| recordRawHeader(thisArg, [args[0], args[1]], 'append') | ||
| return Reflect.apply(target, thisArg, args) | ||
@@ -172,0 +187,0 @@ }, |
@@ -17,3 +17,3 @@ import { until } from '@open-draft/until' | ||
| import { isResponseError } from '../../utils/responseUtils' | ||
| import { globalsRegistry } from '../../utils/globalsRegistry' | ||
| import { patchesRegistry } from '../../utils/patchesRegistry' | ||
@@ -116,9 +116,12 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> { | ||
| const decompressedStream = decompressResponse(rawResponse) | ||
| const response = | ||
| decompressedStream === null | ||
| ? rawResponse | ||
| : new FetchResponse(decompressedStream, rawResponse) | ||
| const response = new FetchResponse( | ||
| decompressedStream || rawResponse.body, | ||
| { | ||
| url: request.url, | ||
| status: rawResponse.status, | ||
| statusText: rawResponse.statusText, | ||
| headers: rawResponse.headers, | ||
| } | ||
| ) | ||
| FetchResponse.setUrl(request.url, response) | ||
| /** | ||
@@ -196,3 +199,5 @@ * Undici's handling of following redirect responses. | ||
| this.subscriptions.push(globalsRegistry.replaceGlobal('fetch', fetchProxy)) | ||
| this.subscriptions.push( | ||
| patchesRegistry.applyPatch(globalThis, 'fetch', () => fetchProxy) | ||
| ) | ||
@@ -199,0 +204,0 @@ logger.info('global fetch patched!', globalThis.fetch.name) |
@@ -21,3 +21,3 @@ import { Interceptor } from '../../Interceptor' | ||
| import { emitAsync } from '../../utils/emitAsync' | ||
| import { globalsRegistry } from '../../utils/globalsRegistry' | ||
| import { patchesRegistry } from '../../utils/patchesRegistry' | ||
@@ -180,3 +180,3 @@ export { | ||
| this.subscriptions.push( | ||
| globalsRegistry.replaceGlobal('WebSocket', WebSocketProxy) | ||
| patchesRegistry.applyPatch(globalThis, 'WebSocket', () => WebSocketProxy) | ||
| ) | ||
@@ -183,0 +183,0 @@ |
@@ -6,3 +6,3 @@ import { Emitter } from 'strict-event-emitter' | ||
| import { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal' | ||
| import { globalsRegistry } from '../../utils/globalsRegistry' | ||
| import { patchesRegistry } from '../../utils/patchesRegistry' | ||
@@ -28,9 +28,8 @@ export type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap> | ||
| this.subscriptions.push( | ||
| globalsRegistry.replaceGlobal( | ||
| 'XMLHttpRequest', | ||
| createXMLHttpRequestProxy({ | ||
| patchesRegistry.applyPatch(globalThis, 'XMLHttpRequest', () => { | ||
| return createXMLHttpRequestProxy({ | ||
| emitter: this.emitter, | ||
| logger: this.logger, | ||
| }) | ||
| ) | ||
| }) | ||
| ) | ||
@@ -37,0 +36,0 @@ |
@@ -109,2 +109,24 @@ import { describe, it, expect } from 'vitest' | ||
| }) | ||
| it('sets the response URL', () => { | ||
| const response = new Response('hello world') | ||
| FetchResponse.setUrl('https://example.com/', response) | ||
| expect(response.url).toBe('https://example.com/') | ||
| }) | ||
| it('preserves a custom response URL after cloning Response', () => { | ||
| const response = new FetchResponse('hello world') | ||
| FetchResponse.setUrl('https://example.com/', response) | ||
| expect(response.clone().url).toBe('https://example.com/') | ||
| }) | ||
| it('preserves a custom response URL after cloning FetchResponse', () => { | ||
| const response = new FetchResponse('hello world', { | ||
| url: 'https://example.com/', | ||
| }) | ||
| expect(response.clone().url).toBe('https://example.com/') | ||
| }) | ||
| }) |
+61
-16
@@ -163,2 +163,5 @@ import { canParseUrl } from './canParseUrl' | ||
| const kStatus = Symbol('kStatus') | ||
| const kUrl = Symbol('kUrl') | ||
| export class FetchResponse extends Response { | ||
@@ -189,2 +192,29 @@ /** | ||
| static setStatus(status: number, response: Response): void { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const internalState = getValueBySymbol<UndiciResponseState>( | ||
| 'state', | ||
| response | ||
| ) | ||
| if (internalState) { | ||
| internalState.status = status | ||
| } else { | ||
| Object.defineProperty(response, 'status', { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false, | ||
| }) | ||
| } | ||
| Object.defineProperty(response, kStatus, { | ||
| value: status, | ||
| enumerable: false, | ||
| }) | ||
| } | ||
| static setUrl(url: string | undefined, response: Response): void { | ||
@@ -210,2 +240,7 @@ if (!url || url === 'about:' || !canParseUrl(url)) { | ||
| } | ||
| Object.defineProperty(response, kUrl, { | ||
| value: url, | ||
| enumerable: false, | ||
| }) | ||
| } | ||
@@ -218,5 +253,7 @@ | ||
| const headers = new Headers() | ||
| for (let line = 0; line < rawHeaders.length; line += 2) { | ||
| headers.append(rawHeaders[line], rawHeaders[line + 1]) | ||
| } | ||
| return headers | ||
@@ -266,19 +303,9 @@ } | ||
| /** | ||
| * Since Node.js v24, Undici stores the Response state in an inaccessible field "#state". | ||
| * Forward the modified status/URL to the cloned response manually. | ||
| * @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242 | ||
| */ | ||
| if (status !== safeStatus) { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const state = getValueBySymbol<UndiciResponseState>('state', this) | ||
| if (state) { | ||
| state.status = status | ||
| } else { | ||
| Object.defineProperty(this, 'status', { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false, | ||
| }) | ||
| } | ||
| FetchResponse.setStatus(status, this) | ||
| } | ||
@@ -288,2 +315,20 @@ | ||
| } | ||
| public clone() { | ||
| const clonedResponse = super.clone() | ||
| const customStatus = Reflect.get(this, kStatus) as number | undefined | ||
| if (customStatus) { | ||
| FetchResponse.setStatus(customStatus, clonedResponse) | ||
| } | ||
| const customUrl = Reflect.get(this, kUrl) as string | undefined | ||
| if (customUrl) { | ||
| FetchResponse.setUrl(customUrl, clonedResponse) | ||
| } | ||
| return clonedResponse | ||
| } | ||
| } |
@@ -1,2 +0,2 @@ | ||
| import { getDeepPropertyDescriptor } from './globalsRegistry' | ||
| import { getDeepPropertyDescriptor } from './patchesRegistry' | ||
@@ -3,0 +3,0 @@ /** |
| const require_createRequestId = require('./createRequestId-DOf8Ktjs.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-B6GjFKAr.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-B5o6PHq3.cjs'); | ||
| const require_handleRequest = require('./handleRequest-BIte-8l8.cjs'); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let _open_draft_until = require("@open-draft/until"); | ||
| //#region src/interceptors/fetch/utils/createNetworkError.ts | ||
| function createNetworkError(cause) { | ||
| return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/followRedirect.ts | ||
| const REQUEST_BODY_HEADERS = [ | ||
| "content-encoding", | ||
| "content-language", | ||
| "content-location", | ||
| "content-type", | ||
| "content-length" | ||
| ]; | ||
| const kRedirectCount = Symbol("kRedirectCount"); | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210 | ||
| */ | ||
| 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"); | ||
| } | ||
| /** | ||
| * @note Undici "safely" extracts the request body. | ||
| * I suspect we cannot dispatch this request again | ||
| * since its body has been read and the stream is locked. | ||
| */ | ||
| requestInit.headers = request.headers; | ||
| const finalResponse = await fetch(new Request(locationUrl, requestInit)); | ||
| Object.defineProperty(finalResponse, "redirected", { | ||
| value: true, | ||
| configurable: true | ||
| }); | ||
| return finalResponse; | ||
| } | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761 | ||
| */ | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/brotli-decompress.browser.ts | ||
| var BrotliDecompressionStream = class extends TransformStream { | ||
| constructor() { | ||
| console.warn("[Interceptors]: Brotli decompression of response streams is not supported in the browser"); | ||
| super({ transform(chunk, controller) { | ||
| controller.enqueue(chunk); | ||
| } }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/decompression.ts | ||
| var PipelineStream = class extends TransformStream { | ||
| constructor(transformStreams, ...strategies) { | ||
| super({}, ...strategies); | ||
| const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform)); | ||
| Object.defineProperty(this, "readable", { get() { | ||
| return readable; | ||
| } }); | ||
| } | ||
| }; | ||
| function parseContentEncoding(contentEncoding) { | ||
| return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim()); | ||
| } | ||
| function createDecompressionStream(contentEncoding) { | ||
| if (contentEncoding === "") return null; | ||
| const codings = parseContentEncoding(contentEncoding); | ||
| if (codings.length === 0) return null; | ||
| return new PipelineStream(codings.reduceRight((transformers, coding) => { | ||
| if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip")); | ||
| else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate")); | ||
| else if (coding === "br") return transformers.concat(new BrotliDecompressionStream()); | ||
| else transformers.length = 0; | ||
| return transformers; | ||
| }, [])); | ||
| } | ||
| function decompressResponse(response) { | ||
| if (response.body === null) return null; | ||
| const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || ""); | ||
| if (!decompressionStream) return null; | ||
| response.body.pipeTo(decompressionStream.writable); | ||
| return decompressionStream.readable; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/index.ts | ||
| var FetchInterceptor = class FetchInterceptor extends require_createRequestId.Interceptor { | ||
| static { | ||
| this.symbol = Symbol("fetch"); | ||
| } | ||
| constructor() { | ||
| super(FetchInterceptor.symbol); | ||
| } | ||
| checkEnvironment() { | ||
| return require_hasConfigurableGlobal.hasConfigurableGlobal("fetch"); | ||
| } | ||
| async setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| const pureFetch = globalThis.fetch; | ||
| const fetchProxy = async (input, init) => { | ||
| const requestId = require_createRequestId.createRequestId(); | ||
| const request = new require_getRawRequest.FetchRequest(typeof input === "string" && typeof location !== "undefined" && !require_getRawRequest.canParseUrl(input) ? new URL(input, location.href) : input, init); | ||
| /** | ||
| * @note Set the raw request only if a Request instance was provided to fetch. | ||
| */ | ||
| if (input instanceof Request) require_getRawRequest.setRawRequest(request, input); | ||
| const responsePromise = new _open_draft_deferred_promise.DeferredPromise(); | ||
| const controller = new require_getRawRequest.RequestController(request, { | ||
| passthrough: async () => { | ||
| this.logger.info("request has not been handled, passthrough..."); | ||
| /** | ||
| * @note Clone the request instance right before performing it. | ||
| * This preserves any modifications made to the intercepted request | ||
| * in the "request" listener. This also allows the user to read the | ||
| * request body in the "response" listener (otherwise "unusable"). | ||
| */ | ||
| const requestCloneForResponseEvent = request.clone(); | ||
| const { error: responseError, data: originalResponse } = await (0, _open_draft_until.until)(() => pureFetch(request)); | ||
| if (responseError) return responsePromise.reject(responseError); | ||
| this.logger.info("original fetch performed", originalResponse); | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| const responseClone = require_getRawRequest.FetchResponse.clone(originalResponse); | ||
| await require_hasConfigurableGlobal.emitAsync(this.emitter, "response", { | ||
| response: responseClone, | ||
| isMockedResponse: false, | ||
| request: requestCloneForResponseEvent, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(originalResponse); | ||
| }, | ||
| respondWith: async (rawResponse) => { | ||
| if (require_handleRequest.isResponseError(rawResponse)) { | ||
| this.logger.info("request has errored!", { response: rawResponse }); | ||
| responsePromise.reject(createNetworkError(rawResponse)); | ||
| return; | ||
| } | ||
| this.logger.info("received mocked response!", { rawResponse }); | ||
| const decompressedStream = decompressResponse(rawResponse); | ||
| const response = decompressedStream === null ? rawResponse : new require_getRawRequest.FetchResponse(decompressedStream, rawResponse); | ||
| require_getRawRequest.FetchResponse.setUrl(request.url, response); | ||
| /** | ||
| * Undici's handling of following redirect responses. | ||
| * Treat the "manual" redirect mode as a regular mocked response. | ||
| * This way, the client can manually follow the redirect it receives. | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173 | ||
| */ | ||
| if (require_getRawRequest.FetchResponse.isRedirectResponse(response.status)) { | ||
| if (request.redirect === "error") { | ||
| responsePromise.reject(createNetworkError("unexpected redirect")); | ||
| return; | ||
| } | ||
| if (request.redirect === "follow") { | ||
| followFetchRedirect(request, response).then((response$1) => { | ||
| responsePromise.resolve(response$1); | ||
| }, (reason) => { | ||
| responsePromise.reject(reason); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| await require_hasConfigurableGlobal.emitAsync(this.emitter, "response", { | ||
| response: require_getRawRequest.FetchResponse.clone(response), | ||
| isMockedResponse: true, | ||
| request, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request has been aborted!", { reason }); | ||
| responsePromise.reject(reason); | ||
| } | ||
| }); | ||
| 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")); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId, | ||
| emitter: this.emitter, | ||
| controller | ||
| }); | ||
| return responsePromise; | ||
| }; | ||
| logger.info("patching global fetch..."); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.globalsRegistry.replaceGlobal("fetch", fetchProxy)); | ||
| logger.info("global fetch patched!", globalThis.fetch.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'FetchInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=fetch-BmrYPMQ0.cjs.map |
| {"version":3,"file":"fetch-BmrYPMQ0.cjs","names":["locationUrl: URL","requestInit: RequestInit","readable","Interceptor","hasConfigurableGlobal","fetchProxy: typeof fetch","createRequestId","FetchRequest","canParseUrl","DeferredPromise","RequestController","FetchResponse","emitAsync","isResponseError","response","handleRequest","globalsRegistry"],"sources":["../../src/interceptors/fetch/utils/createNetworkError.ts","../../src/interceptors/fetch/utils/followRedirect.ts","../../src/interceptors/fetch/utils/brotli-decompress.browser.ts","../../src/interceptors/fetch/utils/decompression.ts","../../src/interceptors/fetch/index.ts"],"sourcesContent":["export function createNetworkError(cause?: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","import { createNetworkError } from './createNetworkError'\n\nconst REQUEST_BODY_HEADERS = [\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-type',\n 'content-length',\n]\n\nconst kRedirectCount = Symbol('kRedirectCount')\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210\n */\nexport async function followFetchRedirect(\n request: Request,\n response: Response\n): Promise<Response> {\n if (response.status !== 303 && request.body != null) {\n return Promise.reject(createNetworkError())\n }\n\n const requestUrl = new URL(request.url)\n\n let locationUrl: URL\n try {\n // If the location is a relative URL, use the request URL as the base URL.\n locationUrl = new URL(response.headers.get('location')!, request.url) \n } catch (error) {\n return Promise.reject(createNetworkError(error))\n }\n\n if (\n !(locationUrl.protocol === 'http:' || locationUrl.protocol === 'https:')\n ) {\n return Promise.reject(\n createNetworkError('URL scheme must be a HTTP(S) scheme')\n )\n }\n\n if (Reflect.get(request, kRedirectCount) > 20) {\n return Promise.reject(createNetworkError('redirect count exceeded'))\n }\n\n Object.defineProperty(request, kRedirectCount, {\n value: (Reflect.get(request, kRedirectCount) || 0) + 1,\n })\n\n if (\n request.mode === 'cors' &&\n (locationUrl.username || locationUrl.password) &&\n !sameOrigin(requestUrl, locationUrl)\n ) {\n return Promise.reject(\n createNetworkError('cross origin not allowed for request mode \"cors\"')\n )\n }\n\n const requestInit: RequestInit = {}\n\n if (\n ([301, 302].includes(response.status) && request.method === 'POST') ||\n (response.status === 303 && !['HEAD', 'GET'].includes(request.method))\n ) {\n requestInit.method = 'GET'\n requestInit.body = null\n\n REQUEST_BODY_HEADERS.forEach((headerName) => {\n request.headers.delete(headerName)\n })\n }\n\n if (!sameOrigin(requestUrl, locationUrl)) {\n request.headers.delete('authorization')\n request.headers.delete('proxy-authorization')\n request.headers.delete('cookie')\n request.headers.delete('host')\n }\n\n /**\n * @note Undici \"safely\" extracts the request body.\n * I suspect we cannot dispatch this request again\n * since its body has been read and the stream is locked.\n */\n\n requestInit.headers = request.headers\n const finalResponse = await fetch(new Request(locationUrl, requestInit))\n Object.defineProperty(finalResponse, 'redirected', {\n value: true,\n configurable: true,\n })\n\n return finalResponse\n}\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761\n */\nfunction sameOrigin(left: URL, right: URL): boolean {\n if (left.origin === right.origin && left.origin === 'null') {\n return true\n }\n\n if (\n left.protocol === right.protocol &&\n left.hostname === right.hostname &&\n left.port === right.port\n ) {\n return true\n }\n\n return false\n}\n","export class BrotliDecompressionStream extends TransformStream {\n constructor() {\n console.warn(\n '[Interceptors]: Brotli decompression of response streams is not supported in the browser'\n )\n\n super({\n transform(chunk, controller) {\n // Keep the stream as passthrough, it does nothing.\n controller.enqueue(chunk)\n },\n })\n }\n}\n","// Import from an internal alias that resolves to different modules\n// depending on the environment. This way, we can keep the fetch interceptor\n// intact while using different strategies for Brotli decompression.\nimport { BrotliDecompressionStream } from 'internal:brotli-decompress'\n\nclass PipelineStream extends TransformStream {\n constructor(\n transformStreams: Array<TransformStream>,\n ...strategies: Array<QueuingStrategy>\n ) {\n super({}, ...strategies)\n\n const readable = [super.readable as any, ...transformStreams].reduce(\n (readable, transform) => readable.pipeThrough(transform)\n )\n\n Object.defineProperty(this, 'readable', {\n get() {\n return readable\n },\n })\n }\n}\n\nexport function parseContentEncoding(contentEncoding: string): Array<string> {\n return contentEncoding\n .toLowerCase()\n .split(',')\n .map((coding) => coding.trim())\n}\n\nfunction createDecompressionStream(\n contentEncoding: string\n): TransformStream | null {\n if (contentEncoding === '') {\n return null\n }\n\n const codings = parseContentEncoding(contentEncoding)\n\n if (codings.length === 0) {\n return null\n }\n\n const transformers = codings.reduceRight<Array<TransformStream>>(\n (transformers, coding) => {\n if (coding === 'gzip' || coding === 'x-gzip') {\n return transformers.concat(new DecompressionStream('gzip'))\n } else if (coding === 'deflate') {\n return transformers.concat(new DecompressionStream('deflate'))\n } else if (coding === 'br') {\n return transformers.concat(new BrotliDecompressionStream())\n } else {\n transformers.length = 0\n }\n\n return transformers\n },\n []\n )\n\n return new PipelineStream(transformers)\n}\n\nexport function decompressResponse(\n response: Response\n): ReadableStream<any> | null {\n if (response.body === null) {\n return null\n }\n\n const decompressionStream = createDecompressionStream(\n response.headers.get('content-encoding') || ''\n )\n\n if (!decompressionStream) {\n return null\n }\n\n // Use `pipeTo` and return the decompression stream's readable\n // instead of `pipeThrough` because that will lock the original\n // response stream, making it unusable as the input to Response.\n response.body.pipeTo(decompressionStream.writable)\n return decompressionStream.readable\n}\n","import { until } from '@open-draft/until'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport { createNetworkError } from './utils/createNetworkError'\nimport { followFetchRedirect } from './utils/followRedirect'\nimport { decompressResponse } from './utils/decompression'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { FetchRequest, FetchResponse } from '../../utils/fetchUtils'\nimport { setRawRequest } from '../../getRawRequest'\nimport { isResponseError } from '../../utils/responseUtils'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('fetch')\n }\n\n protected async setup() {\n const logger = this.logger.extend('setup')\n\n const pureFetch = globalThis.fetch\n\n const fetchProxy: typeof fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.href)\n : input\n\n const request = new FetchRequest(resolvedInput, init)\n\n /**\n * @note Set the raw request only if a Request instance was provided to fetch.\n */\n if (input instanceof Request) {\n setRawRequest(request, input)\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const controller = new RequestController(request, {\n passthrough: async () => {\n this.logger.info('request has not been handled, passthrough...')\n\n /**\n * @note Clone the request instance right before performing it.\n * This preserves any modifications made to the intercepted request\n * in the \"request\" listener. This also allows the user to read the\n * request body in the \"response\" listener (otherwise \"unusable\").\n */\n const requestCloneForResponseEvent = request.clone()\n\n // Perform the intercepted request as-is.\n const { error: responseError, data: originalResponse } = await until(\n () => pureFetch(request)\n )\n\n if (responseError) {\n return responsePromise.reject(responseError)\n }\n\n this.logger.info('original fetch performed', originalResponse)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = FetchResponse.clone(originalResponse)\n await emitAsync(this.emitter, 'response', {\n response: responseClone,\n isMockedResponse: false,\n request: requestCloneForResponseEvent,\n requestId,\n })\n }\n\n // Resolve the response promise with the original response\n // since the `fetch()` return this internal promise.\n responsePromise.resolve(originalResponse)\n },\n respondWith: async (rawResponse) => {\n // Handle mocked `Response.error()` (i.e. request errors).\n if (isResponseError(rawResponse)) {\n this.logger.info('request has errored!', { response: rawResponse })\n responsePromise.reject(createNetworkError(rawResponse))\n return\n }\n\n this.logger.info('received mocked response!', {\n rawResponse,\n })\n\n // Decompress the mocked response body, if applicable.\n const decompressedStream = decompressResponse(rawResponse)\n const response =\n decompressedStream === null\n ? rawResponse\n : new FetchResponse(decompressedStream, rawResponse)\n\n FetchResponse.setUrl(request.url, response)\n\n /**\n * Undici's handling of following redirect responses.\n * Treat the \"manual\" redirect mode as a regular mocked response.\n * This way, the client can manually follow the redirect it receives.\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173\n */\n if (FetchResponse.isRedirectResponse(response.status)) {\n // Reject the request promise if its `redirect` is set to `error`\n // and it receives a mocked redirect response.\n if (request.redirect === 'error') {\n responsePromise.reject(createNetworkError('unexpected redirect'))\n return\n }\n\n if (request.redirect === 'follow') {\n followFetchRedirect(request, response).then(\n (response) => {\n responsePromise.resolve(response)\n },\n (reason) => {\n responsePromise.reject(reason)\n }\n )\n return\n }\n }\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: FetchResponse.clone(response),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n responsePromise.resolve(response)\n },\n errorWith: (reason) => {\n this.logger.info('request has been aborted!', { reason })\n responsePromise.reject(reason)\n },\n })\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n })\n\n return responsePromise\n }\n\n logger.info('patching global fetch...')\n\n this.subscriptions.push(globalsRegistry.replaceGlobal('fetch', fetchProxy))\n\n logger.info('global fetch patched!', globalThis.fetch.name)\n }\n}\n"],"mappings":"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"} |
| import { r as Interceptor, t as createRequestId } from "./createRequestId-DYCsFHOi.mjs"; | ||
| import { a as canParseUrl, i as FetchResponse, n as setRawRequest, o as RequestController, r as FetchRequest } from "./getRawRequest-BP--nUwn.mjs"; | ||
| import { n as globalsRegistry, r as emitAsync, t as hasConfigurableGlobal } from "./hasConfigurableGlobal-B39W0thh.mjs"; | ||
| import { n as isResponseError, t as handleRequest } from "./handleRequest-CnDA8MVO.mjs"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { until } from "@open-draft/until"; | ||
| //#region src/interceptors/fetch/utils/createNetworkError.ts | ||
| function createNetworkError(cause) { | ||
| return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/followRedirect.ts | ||
| const REQUEST_BODY_HEADERS = [ | ||
| "content-encoding", | ||
| "content-language", | ||
| "content-location", | ||
| "content-type", | ||
| "content-length" | ||
| ]; | ||
| const kRedirectCount = Symbol("kRedirectCount"); | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210 | ||
| */ | ||
| 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"); | ||
| } | ||
| /** | ||
| * @note Undici "safely" extracts the request body. | ||
| * I suspect we cannot dispatch this request again | ||
| * since its body has been read and the stream is locked. | ||
| */ | ||
| requestInit.headers = request.headers; | ||
| const finalResponse = await fetch(new Request(locationUrl, requestInit)); | ||
| Object.defineProperty(finalResponse, "redirected", { | ||
| value: true, | ||
| configurable: true | ||
| }); | ||
| return finalResponse; | ||
| } | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761 | ||
| */ | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/brotli-decompress.browser.ts | ||
| var BrotliDecompressionStream = class extends TransformStream { | ||
| constructor() { | ||
| console.warn("[Interceptors]: Brotli decompression of response streams is not supported in the browser"); | ||
| super({ transform(chunk, controller) { | ||
| controller.enqueue(chunk); | ||
| } }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/decompression.ts | ||
| var PipelineStream = class extends TransformStream { | ||
| constructor(transformStreams, ...strategies) { | ||
| super({}, ...strategies); | ||
| const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform)); | ||
| Object.defineProperty(this, "readable", { get() { | ||
| return readable; | ||
| } }); | ||
| } | ||
| }; | ||
| function parseContentEncoding(contentEncoding) { | ||
| return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim()); | ||
| } | ||
| function createDecompressionStream(contentEncoding) { | ||
| if (contentEncoding === "") return null; | ||
| const codings = parseContentEncoding(contentEncoding); | ||
| if (codings.length === 0) return null; | ||
| return new PipelineStream(codings.reduceRight((transformers, coding) => { | ||
| if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip")); | ||
| else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate")); | ||
| else if (coding === "br") return transformers.concat(new BrotliDecompressionStream()); | ||
| else transformers.length = 0; | ||
| return transformers; | ||
| }, [])); | ||
| } | ||
| function decompressResponse(response) { | ||
| if (response.body === null) return null; | ||
| const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || ""); | ||
| if (!decompressionStream) return null; | ||
| response.body.pipeTo(decompressionStream.writable); | ||
| return decompressionStream.readable; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/index.ts | ||
| var FetchInterceptor = class FetchInterceptor extends Interceptor { | ||
| static { | ||
| this.symbol = Symbol("fetch"); | ||
| } | ||
| constructor() { | ||
| super(FetchInterceptor.symbol); | ||
| } | ||
| checkEnvironment() { | ||
| return hasConfigurableGlobal("fetch"); | ||
| } | ||
| async setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| const pureFetch = globalThis.fetch; | ||
| const fetchProxy = async (input, init) => { | ||
| const requestId = createRequestId(); | ||
| const request = new FetchRequest(typeof input === "string" && typeof location !== "undefined" && !canParseUrl(input) ? new URL(input, location.href) : input, init); | ||
| /** | ||
| * @note Set the raw request only if a Request instance was provided to fetch. | ||
| */ | ||
| if (input instanceof Request) setRawRequest(request, input); | ||
| const responsePromise = new DeferredPromise(); | ||
| const controller = new RequestController(request, { | ||
| passthrough: async () => { | ||
| this.logger.info("request has not been handled, passthrough..."); | ||
| /** | ||
| * @note Clone the request instance right before performing it. | ||
| * This preserves any modifications made to the intercepted request | ||
| * in the "request" listener. This also allows the user to read the | ||
| * request body in the "response" listener (otherwise "unusable"). | ||
| */ | ||
| const requestCloneForResponseEvent = request.clone(); | ||
| const { error: responseError, data: originalResponse } = await until(() => pureFetch(request)); | ||
| if (responseError) return responsePromise.reject(responseError); | ||
| this.logger.info("original fetch performed", originalResponse); | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| const responseClone = FetchResponse.clone(originalResponse); | ||
| await emitAsync(this.emitter, "response", { | ||
| response: responseClone, | ||
| isMockedResponse: false, | ||
| request: requestCloneForResponseEvent, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(originalResponse); | ||
| }, | ||
| respondWith: async (rawResponse) => { | ||
| if (isResponseError(rawResponse)) { | ||
| this.logger.info("request has errored!", { response: rawResponse }); | ||
| responsePromise.reject(createNetworkError(rawResponse)); | ||
| return; | ||
| } | ||
| this.logger.info("received mocked response!", { rawResponse }); | ||
| const decompressedStream = decompressResponse(rawResponse); | ||
| const response = decompressedStream === null ? rawResponse : new FetchResponse(decompressedStream, rawResponse); | ||
| FetchResponse.setUrl(request.url, response); | ||
| /** | ||
| * Undici's handling of following redirect responses. | ||
| * Treat the "manual" redirect mode as a regular mocked response. | ||
| * This way, the client can manually follow the redirect it receives. | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173 | ||
| */ | ||
| if (FetchResponse.isRedirectResponse(response.status)) { | ||
| if (request.redirect === "error") { | ||
| responsePromise.reject(createNetworkError("unexpected redirect")); | ||
| return; | ||
| } | ||
| if (request.redirect === "follow") { | ||
| followFetchRedirect(request, response).then((response$1) => { | ||
| responsePromise.resolve(response$1); | ||
| }, (reason) => { | ||
| responsePromise.reject(reason); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| await emitAsync(this.emitter, "response", { | ||
| response: FetchResponse.clone(response), | ||
| isMockedResponse: true, | ||
| request, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request has been aborted!", { reason }); | ||
| responsePromise.reject(reason); | ||
| } | ||
| }); | ||
| 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")); | ||
| await handleRequest({ | ||
| request, | ||
| requestId, | ||
| emitter: this.emitter, | ||
| controller | ||
| }); | ||
| return responsePromise; | ||
| }; | ||
| logger.info("patching global fetch..."); | ||
| this.subscriptions.push(globalsRegistry.replaceGlobal("fetch", fetchProxy)); | ||
| logger.info("global fetch patched!", globalThis.fetch.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { FetchInterceptor as t }; | ||
| //# sourceMappingURL=fetch-BS6cxWzo.mjs.map |
| {"version":3,"file":"fetch-BS6cxWzo.mjs","names":["locationUrl: URL","requestInit: RequestInit","readable","fetchProxy: typeof fetch","response"],"sources":["../../src/interceptors/fetch/utils/createNetworkError.ts","../../src/interceptors/fetch/utils/followRedirect.ts","../../src/interceptors/fetch/utils/brotli-decompress.browser.ts","../../src/interceptors/fetch/utils/decompression.ts","../../src/interceptors/fetch/index.ts"],"sourcesContent":["export function createNetworkError(cause?: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","import { createNetworkError } from './createNetworkError'\n\nconst REQUEST_BODY_HEADERS = [\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-type',\n 'content-length',\n]\n\nconst kRedirectCount = Symbol('kRedirectCount')\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210\n */\nexport async function followFetchRedirect(\n request: Request,\n response: Response\n): Promise<Response> {\n if (response.status !== 303 && request.body != null) {\n return Promise.reject(createNetworkError())\n }\n\n const requestUrl = new URL(request.url)\n\n let locationUrl: URL\n try {\n // If the location is a relative URL, use the request URL as the base URL.\n locationUrl = new URL(response.headers.get('location')!, request.url) \n } catch (error) {\n return Promise.reject(createNetworkError(error))\n }\n\n if (\n !(locationUrl.protocol === 'http:' || locationUrl.protocol === 'https:')\n ) {\n return Promise.reject(\n createNetworkError('URL scheme must be a HTTP(S) scheme')\n )\n }\n\n if (Reflect.get(request, kRedirectCount) > 20) {\n return Promise.reject(createNetworkError('redirect count exceeded'))\n }\n\n Object.defineProperty(request, kRedirectCount, {\n value: (Reflect.get(request, kRedirectCount) || 0) + 1,\n })\n\n if (\n request.mode === 'cors' &&\n (locationUrl.username || locationUrl.password) &&\n !sameOrigin(requestUrl, locationUrl)\n ) {\n return Promise.reject(\n createNetworkError('cross origin not allowed for request mode \"cors\"')\n )\n }\n\n const requestInit: RequestInit = {}\n\n if (\n ([301, 302].includes(response.status) && request.method === 'POST') ||\n (response.status === 303 && !['HEAD', 'GET'].includes(request.method))\n ) {\n requestInit.method = 'GET'\n requestInit.body = null\n\n REQUEST_BODY_HEADERS.forEach((headerName) => {\n request.headers.delete(headerName)\n })\n }\n\n if (!sameOrigin(requestUrl, locationUrl)) {\n request.headers.delete('authorization')\n request.headers.delete('proxy-authorization')\n request.headers.delete('cookie')\n request.headers.delete('host')\n }\n\n /**\n * @note Undici \"safely\" extracts the request body.\n * I suspect we cannot dispatch this request again\n * since its body has been read and the stream is locked.\n */\n\n requestInit.headers = request.headers\n const finalResponse = await fetch(new Request(locationUrl, requestInit))\n Object.defineProperty(finalResponse, 'redirected', {\n value: true,\n configurable: true,\n })\n\n return finalResponse\n}\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761\n */\nfunction sameOrigin(left: URL, right: URL): boolean {\n if (left.origin === right.origin && left.origin === 'null') {\n return true\n }\n\n if (\n left.protocol === right.protocol &&\n left.hostname === right.hostname &&\n left.port === right.port\n ) {\n return true\n }\n\n return false\n}\n","export class BrotliDecompressionStream extends TransformStream {\n constructor() {\n console.warn(\n '[Interceptors]: Brotli decompression of response streams is not supported in the browser'\n )\n\n super({\n transform(chunk, controller) {\n // Keep the stream as passthrough, it does nothing.\n controller.enqueue(chunk)\n },\n })\n }\n}\n","// Import from an internal alias that resolves to different modules\n// depending on the environment. This way, we can keep the fetch interceptor\n// intact while using different strategies for Brotli decompression.\nimport { BrotliDecompressionStream } from 'internal:brotli-decompress'\n\nclass PipelineStream extends TransformStream {\n constructor(\n transformStreams: Array<TransformStream>,\n ...strategies: Array<QueuingStrategy>\n ) {\n super({}, ...strategies)\n\n const readable = [super.readable as any, ...transformStreams].reduce(\n (readable, transform) => readable.pipeThrough(transform)\n )\n\n Object.defineProperty(this, 'readable', {\n get() {\n return readable\n },\n })\n }\n}\n\nexport function parseContentEncoding(contentEncoding: string): Array<string> {\n return contentEncoding\n .toLowerCase()\n .split(',')\n .map((coding) => coding.trim())\n}\n\nfunction createDecompressionStream(\n contentEncoding: string\n): TransformStream | null {\n if (contentEncoding === '') {\n return null\n }\n\n const codings = parseContentEncoding(contentEncoding)\n\n if (codings.length === 0) {\n return null\n }\n\n const transformers = codings.reduceRight<Array<TransformStream>>(\n (transformers, coding) => {\n if (coding === 'gzip' || coding === 'x-gzip') {\n return transformers.concat(new DecompressionStream('gzip'))\n } else if (coding === 'deflate') {\n return transformers.concat(new DecompressionStream('deflate'))\n } else if (coding === 'br') {\n return transformers.concat(new BrotliDecompressionStream())\n } else {\n transformers.length = 0\n }\n\n return transformers\n },\n []\n )\n\n return new PipelineStream(transformers)\n}\n\nexport function decompressResponse(\n response: Response\n): ReadableStream<any> | null {\n if (response.body === null) {\n return null\n }\n\n const decompressionStream = createDecompressionStream(\n response.headers.get('content-encoding') || ''\n )\n\n if (!decompressionStream) {\n return null\n }\n\n // Use `pipeTo` and return the decompression stream's readable\n // instead of `pipeThrough` because that will lock the original\n // response stream, making it unusable as the input to Response.\n response.body.pipeTo(decompressionStream.writable)\n return decompressionStream.readable\n}\n","import { until } from '@open-draft/until'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport { createNetworkError } from './utils/createNetworkError'\nimport { followFetchRedirect } from './utils/followRedirect'\nimport { decompressResponse } from './utils/decompression'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { FetchRequest, FetchResponse } from '../../utils/fetchUtils'\nimport { setRawRequest } from '../../getRawRequest'\nimport { isResponseError } from '../../utils/responseUtils'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('fetch')\n }\n\n protected async setup() {\n const logger = this.logger.extend('setup')\n\n const pureFetch = globalThis.fetch\n\n const fetchProxy: typeof fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.href)\n : input\n\n const request = new FetchRequest(resolvedInput, init)\n\n /**\n * @note Set the raw request only if a Request instance was provided to fetch.\n */\n if (input instanceof Request) {\n setRawRequest(request, input)\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const controller = new RequestController(request, {\n passthrough: async () => {\n this.logger.info('request has not been handled, passthrough...')\n\n /**\n * @note Clone the request instance right before performing it.\n * This preserves any modifications made to the intercepted request\n * in the \"request\" listener. This also allows the user to read the\n * request body in the \"response\" listener (otherwise \"unusable\").\n */\n const requestCloneForResponseEvent = request.clone()\n\n // Perform the intercepted request as-is.\n const { error: responseError, data: originalResponse } = await until(\n () => pureFetch(request)\n )\n\n if (responseError) {\n return responsePromise.reject(responseError)\n }\n\n this.logger.info('original fetch performed', originalResponse)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = FetchResponse.clone(originalResponse)\n await emitAsync(this.emitter, 'response', {\n response: responseClone,\n isMockedResponse: false,\n request: requestCloneForResponseEvent,\n requestId,\n })\n }\n\n // Resolve the response promise with the original response\n // since the `fetch()` return this internal promise.\n responsePromise.resolve(originalResponse)\n },\n respondWith: async (rawResponse) => {\n // Handle mocked `Response.error()` (i.e. request errors).\n if (isResponseError(rawResponse)) {\n this.logger.info('request has errored!', { response: rawResponse })\n responsePromise.reject(createNetworkError(rawResponse))\n return\n }\n\n this.logger.info('received mocked response!', {\n rawResponse,\n })\n\n // Decompress the mocked response body, if applicable.\n const decompressedStream = decompressResponse(rawResponse)\n const response =\n decompressedStream === null\n ? rawResponse\n : new FetchResponse(decompressedStream, rawResponse)\n\n FetchResponse.setUrl(request.url, response)\n\n /**\n * Undici's handling of following redirect responses.\n * Treat the \"manual\" redirect mode as a regular mocked response.\n * This way, the client can manually follow the redirect it receives.\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173\n */\n if (FetchResponse.isRedirectResponse(response.status)) {\n // Reject the request promise if its `redirect` is set to `error`\n // and it receives a mocked redirect response.\n if (request.redirect === 'error') {\n responsePromise.reject(createNetworkError('unexpected redirect'))\n return\n }\n\n if (request.redirect === 'follow') {\n followFetchRedirect(request, response).then(\n (response) => {\n responsePromise.resolve(response)\n },\n (reason) => {\n responsePromise.reject(reason)\n }\n )\n return\n }\n }\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: FetchResponse.clone(response),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n responsePromise.resolve(response)\n },\n errorWith: (reason) => {\n this.logger.info('request has been aborted!', { reason })\n responsePromise.reject(reason)\n },\n })\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n })\n\n return responsePromise\n }\n\n logger.info('patching global fetch...')\n\n this.subscriptions.push(globalsRegistry.replaceGlobal('fetch', fetchProxy))\n\n logger.info('global fetch patched!', globalThis.fetch.name)\n }\n}\n"],"mappings":"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"} |
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let outvariant = require("outvariant"); | ||
| //#region src/InterceptorError.ts | ||
| var InterceptorError = class InterceptorError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "InterceptorError"; | ||
| Object.setPrototypeOf(this, InterceptorError.prototype); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/RequestController.ts | ||
| var RequestController = class RequestController { | ||
| static { | ||
| this.PENDING = 0; | ||
| } | ||
| static { | ||
| this.PASSTHROUGH = 1; | ||
| } | ||
| static { | ||
| this.RESPONSE = 2; | ||
| } | ||
| static { | ||
| this.ERROR = 3; | ||
| } | ||
| constructor(request, source) { | ||
| this.request = request; | ||
| this.source = source; | ||
| this.readyState = RequestController.PENDING; | ||
| this.handled = new _open_draft_deferred_promise.DeferredPromise(); | ||
| } | ||
| get #handled() { | ||
| return this.handled; | ||
| } | ||
| /** | ||
| * Perform this request as-is. | ||
| */ | ||
| async passthrough() { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url); | ||
| this.readyState = RequestController.PASSTHROUGH; | ||
| await this.source.passthrough(); | ||
| this.#handled.resolve(); | ||
| } | ||
| /** | ||
| * Respond to this request with the given `Response` instance. | ||
| * | ||
| * @example | ||
| * controller.respondWith(new Response()) | ||
| * controller.respondWith(Response.json({ id })) | ||
| * controller.respondWith(Response.error()) | ||
| */ | ||
| respondWith(response) { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState); | ||
| this.readyState = RequestController.RESPONSE; | ||
| this.#handled.resolve(); | ||
| /** | ||
| * @note Although `source.respondWith()` is potentially asynchronous, | ||
| * do NOT await it for backward-compatibility. Awaiting it will short-circuit | ||
| * the request listener invocation as soon as a listener responds to a request. | ||
| * Ideally, that's what we want, but that's not what we promise the user. | ||
| */ | ||
| this.source.respondWith(response); | ||
| } | ||
| /** | ||
| * Error this request with the given reason. | ||
| * | ||
| * @example | ||
| * controller.errorWith() | ||
| * controller.errorWith(new Error('Oops!')) | ||
| * controller.errorWith({ message: 'Oops!'}) | ||
| */ | ||
| errorWith(reason) { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState); | ||
| this.readyState = RequestController.ERROR; | ||
| this.source.errorWith(reason); | ||
| this.#handled.resolve(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/utils/canParseUrl.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given URL string | ||
| * can be parsed into a `URL` instance. | ||
| * A substitute for `URL.canParse()` for Node.js 18. | ||
| */ | ||
| function canParseUrl(url) { | ||
| try { | ||
| new URL(url); | ||
| return true; | ||
| } catch (_error) { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/getValueBySymbol.ts | ||
| /** | ||
| * Returns the value behind the symbol with the given name. | ||
| */ | ||
| function getValueBySymbol(symbolName, source) { | ||
| const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => { | ||
| return symbol$1.description === symbolName; | ||
| }); | ||
| if (symbol) return Reflect.get(source, symbol); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/fetchUtils.ts | ||
| var FetchRequest = class FetchRequest extends Request { | ||
| static #resolveProperty(input, init = {}, key) { | ||
| return init[key] ?? (input instanceof Request ? input[key] : void 0); | ||
| } | ||
| /** | ||
| * Check if the given request method is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#methods | ||
| */ | ||
| static isConfigurableMethod(method) { | ||
| return method !== "CONNECT" && method !== "TRACE" && method !== "TRACK"; | ||
| } | ||
| static isMethodWithBody(method) { | ||
| return method !== "HEAD" && method !== "GET" && FetchRequest.isConfigurableMethod(method); | ||
| } | ||
| /** | ||
| * Check if the given request `mode` is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#concept-request-mode | ||
| */ | ||
| static isConfigurableMode(mode) { | ||
| return mode !== "navigate" && mode !== "websocket" && mode !== "webtransport"; | ||
| } | ||
| constructor(input, init) { | ||
| const method = FetchRequest.#resolveProperty(input, init, "method") || "GET"; | ||
| const safeMethod = FetchRequest.isConfigurableMethod(method) ? method : "GET"; | ||
| const hasExplicitBody = init != null && "body" in init; | ||
| /** | ||
| * Only include `body` in the super init when it needs to be overridden. | ||
| * When `input` is a Request and no explicit body is in `init`, let the | ||
| * Request constructor handle body transfer naturally so it properly | ||
| * marks the original request's body as consumed (bodyUsed = true). | ||
| */ | ||
| const bodyInit = !FetchRequest.isMethodWithBody(method) ? { body: void 0 } : hasExplicitBody ? { body: init.body } : {}; | ||
| const mode = FetchRequest.#resolveProperty(input, init, "mode") ?? void 0; | ||
| const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : void 0; | ||
| super(input, { | ||
| ...init || {}, | ||
| method: safeMethod, | ||
| mode: safeMode, | ||
| duplex: init?.duplex ?? (FetchRequest.isMethodWithBody(method) ? "half" : void 0), | ||
| ...bodyInit | ||
| }); | ||
| if (method !== safeMethod) this.#setInternalProperty("method", method); | ||
| if (method === "CONNECT") { | ||
| const url = new URL(input instanceof Request ? input.url : input); | ||
| let authority; | ||
| /** | ||
| * @note Node.js has a bug parsing raw CONNECT requests URLs like | ||
| * "http://127.0.0.1:1337/localhost:80". It would treat "localhost:" as a protocol. | ||
| */ | ||
| if (url.protocol === "localhost:") authority = url.href; | ||
| else authority = url.pathname.replace(/^\/+/, ""); | ||
| /** | ||
| * @note Define "url" as a getter because Undici uses their own | ||
| * logic to resolve the "request.url" property. Simply reassigning | ||
| * its value doesn't do anything. This is a destructive action | ||
| * but it's safe because "CONNECT" requests are forbidden per fetch. | ||
| */ | ||
| Object.defineProperty(this, "url", { | ||
| get: () => authority, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| if (mode != null && mode !== safeMode) this.#setInternalProperty("mode", mode); | ||
| } | ||
| #setInternalProperty(key, value) { | ||
| const internalState = getValueBySymbol("state", this); | ||
| if (internalState) Reflect.set(internalState, key, value); | ||
| else Object.defineProperty(this, key, { | ||
| value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| }; | ||
| var FetchResponse = class FetchResponse extends Response { | ||
| static { | ||
| this.STATUS_CODES_WITHOUT_BODY = [ | ||
| 101, | ||
| 103, | ||
| 204, | ||
| 205, | ||
| 304 | ||
| ]; | ||
| } | ||
| static { | ||
| this.STATUS_CODES_WITH_REDIRECT = [ | ||
| 301, | ||
| 302, | ||
| 303, | ||
| 307, | ||
| 308 | ||
| ]; | ||
| } | ||
| static isConfigurableStatusCode(status) { | ||
| return status >= 200 && status <= 599; | ||
| } | ||
| static isRedirectResponse(status) { | ||
| return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status); | ||
| } | ||
| /** | ||
| * Returns a boolean indicating whether the given response status | ||
| * code represents a response that can have a body. | ||
| */ | ||
| static isResponseWithBody(status) { | ||
| return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status); | ||
| } | ||
| static setUrl(url, response) { | ||
| if (!url || url === "about:" || !canParseUrl(url)) return; | ||
| const state = getValueBySymbol("state", response); | ||
| if (state) state.urlList.push(new URL(url)); | ||
| else Object.defineProperty(response, "url", { | ||
| value: url, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| /** | ||
| * Parses the given raw HTTP headers into a Fetch API `Headers` instance. | ||
| */ | ||
| static parseRawHeaders(rawHeaders) { | ||
| const headers = new Headers(); | ||
| for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]); | ||
| return headers; | ||
| } | ||
| /** | ||
| * Safely clones the given `Response`. | ||
| * Coerces response clone exceptions into 500 mocked responses. | ||
| * Handy in the environments that introduce arbitrary response | ||
| * cloning restrictions, like "101 Switching Protocols" cloning | ||
| * in "miniflare". | ||
| */ | ||
| static clone(response) { | ||
| try { | ||
| return response.clone(); | ||
| } catch (error) { | ||
| return Response.json(error instanceof Error ? { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| } : {}, { | ||
| status: 500, | ||
| statusText: "Unclonable Response" | ||
| }); | ||
| } | ||
| } | ||
| constructor(body, init = {}) { | ||
| const status = init.status ?? 200; | ||
| const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200; | ||
| const finalBody = FetchResponse.isResponseWithBody(status) ? body : null; | ||
| super(finalBody, { | ||
| status: safeStatus, | ||
| statusText: init.statusText, | ||
| headers: init.headers | ||
| }); | ||
| if (status !== safeStatus) { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const state = getValueBySymbol("state", this); | ||
| if (state) state.status = status; | ||
| else Object.defineProperty(this, "status", { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| FetchResponse.setUrl(init.url, this); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/getRawRequest.ts | ||
| const kRawRequest = Symbol("kRawRequest"); | ||
| /** | ||
| * Returns a raw request instance associated with this request. | ||
| * | ||
| * @example | ||
| * interceptor.on('request', ({ request }) => { | ||
| * const rawRequest = getRawRequest(request) | ||
| * | ||
| * if (rawRequest instanceof http.ClientRequest) { | ||
| * console.log(rawRequest.rawHeaders) | ||
| * } | ||
| * }) | ||
| */ | ||
| function getRawRequest(request) { | ||
| return Reflect.get(request, kRawRequest); | ||
| } | ||
| function setRawRequest(request, rawRequest) { | ||
| Reflect.set(request, kRawRequest, rawRequest); | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'FetchRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'FetchResponse', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchResponse; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'InterceptorError', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return InterceptorError; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'RequestController', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return RequestController; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'canParseUrl', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return canParseUrl; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'getRawRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return getRawRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'setRawRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return setRawRequest; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=getRawRequest-B6GjFKAr.cjs.map |
| {"version":3,"file":"getRawRequest-B6GjFKAr.cjs","names":["request: Request","source: RequestControllerSource","DeferredPromise","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts","../../src/getRawRequest.ts"],"sourcesContent":["export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n if (status !== safeStatus) {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const state = getValueBySymbol<UndiciResponseState>('state', this)\n\n if (state) {\n state.status = status\n } else {\n Object.defineProperty(this, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n}\n","const kRawRequest = Symbol('kRawRequest')\n\n/**\n * Returns a raw request instance associated with this request.\n *\n * @example\n * interceptor.on('request', ({ request }) => {\n * const rawRequest = getRawRequest(request)\n *\n * if (rawRequest instanceof http.ClientRequest) {\n * console.log(rawRequest.rawHeaders)\n * }\n * })\n */\nexport function getRawRequest(request: Request): unknown | undefined {\n return Reflect.get(request, kRawRequest)\n}\n\nexport function setRawRequest(request: Request, rawRequest: unknown): void {\n Reflect.set(request, kRawRequest, rawRequest)\n}\n"],"mappings":"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"} |
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { invariant } from "outvariant"; | ||
| //#region src/InterceptorError.ts | ||
| var InterceptorError = class InterceptorError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "InterceptorError"; | ||
| Object.setPrototypeOf(this, InterceptorError.prototype); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/RequestController.ts | ||
| var RequestController = class RequestController { | ||
| static { | ||
| this.PENDING = 0; | ||
| } | ||
| static { | ||
| this.PASSTHROUGH = 1; | ||
| } | ||
| static { | ||
| this.RESPONSE = 2; | ||
| } | ||
| static { | ||
| this.ERROR = 3; | ||
| } | ||
| constructor(request, source) { | ||
| this.request = request; | ||
| this.source = source; | ||
| this.readyState = RequestController.PENDING; | ||
| this.handled = new DeferredPromise(); | ||
| } | ||
| get #handled() { | ||
| return this.handled; | ||
| } | ||
| /** | ||
| * Perform this request as-is. | ||
| */ | ||
| async passthrough() { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url); | ||
| this.readyState = RequestController.PASSTHROUGH; | ||
| await this.source.passthrough(); | ||
| this.#handled.resolve(); | ||
| } | ||
| /** | ||
| * Respond to this request with the given `Response` instance. | ||
| * | ||
| * @example | ||
| * controller.respondWith(new Response()) | ||
| * controller.respondWith(Response.json({ id })) | ||
| * controller.respondWith(Response.error()) | ||
| */ | ||
| respondWith(response) { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState); | ||
| this.readyState = RequestController.RESPONSE; | ||
| this.#handled.resolve(); | ||
| /** | ||
| * @note Although `source.respondWith()` is potentially asynchronous, | ||
| * do NOT await it for backward-compatibility. Awaiting it will short-circuit | ||
| * the request listener invocation as soon as a listener responds to a request. | ||
| * Ideally, that's what we want, but that's not what we promise the user. | ||
| */ | ||
| this.source.respondWith(response); | ||
| } | ||
| /** | ||
| * Error this request with the given reason. | ||
| * | ||
| * @example | ||
| * controller.errorWith() | ||
| * controller.errorWith(new Error('Oops!')) | ||
| * controller.errorWith({ message: 'Oops!'}) | ||
| */ | ||
| errorWith(reason) { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState); | ||
| this.readyState = RequestController.ERROR; | ||
| this.source.errorWith(reason); | ||
| this.#handled.resolve(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/utils/canParseUrl.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given URL string | ||
| * can be parsed into a `URL` instance. | ||
| * A substitute for `URL.canParse()` for Node.js 18. | ||
| */ | ||
| function canParseUrl(url) { | ||
| try { | ||
| new URL(url); | ||
| return true; | ||
| } catch (_error) { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/getValueBySymbol.ts | ||
| /** | ||
| * Returns the value behind the symbol with the given name. | ||
| */ | ||
| function getValueBySymbol(symbolName, source) { | ||
| const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => { | ||
| return symbol$1.description === symbolName; | ||
| }); | ||
| if (symbol) return Reflect.get(source, symbol); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/fetchUtils.ts | ||
| var FetchRequest = class FetchRequest extends Request { | ||
| static #resolveProperty(input, init = {}, key) { | ||
| return init[key] ?? (input instanceof Request ? input[key] : void 0); | ||
| } | ||
| /** | ||
| * Check if the given request method is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#methods | ||
| */ | ||
| static isConfigurableMethod(method) { | ||
| return method !== "CONNECT" && method !== "TRACE" && method !== "TRACK"; | ||
| } | ||
| static isMethodWithBody(method) { | ||
| return method !== "HEAD" && method !== "GET" && FetchRequest.isConfigurableMethod(method); | ||
| } | ||
| /** | ||
| * Check if the given request `mode` is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#concept-request-mode | ||
| */ | ||
| static isConfigurableMode(mode) { | ||
| return mode !== "navigate" && mode !== "websocket" && mode !== "webtransport"; | ||
| } | ||
| constructor(input, init) { | ||
| const method = FetchRequest.#resolveProperty(input, init, "method") || "GET"; | ||
| const safeMethod = FetchRequest.isConfigurableMethod(method) ? method : "GET"; | ||
| const hasExplicitBody = init != null && "body" in init; | ||
| /** | ||
| * Only include `body` in the super init when it needs to be overridden. | ||
| * When `input` is a Request and no explicit body is in `init`, let the | ||
| * Request constructor handle body transfer naturally so it properly | ||
| * marks the original request's body as consumed (bodyUsed = true). | ||
| */ | ||
| const bodyInit = !FetchRequest.isMethodWithBody(method) ? { body: void 0 } : hasExplicitBody ? { body: init.body } : {}; | ||
| const mode = FetchRequest.#resolveProperty(input, init, "mode") ?? void 0; | ||
| const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : void 0; | ||
| super(input, { | ||
| ...init || {}, | ||
| method: safeMethod, | ||
| mode: safeMode, | ||
| duplex: init?.duplex ?? (FetchRequest.isMethodWithBody(method) ? "half" : void 0), | ||
| ...bodyInit | ||
| }); | ||
| if (method !== safeMethod) this.#setInternalProperty("method", method); | ||
| if (method === "CONNECT") { | ||
| const url = new URL(input instanceof Request ? input.url : input); | ||
| let authority; | ||
| /** | ||
| * @note Node.js has a bug parsing raw CONNECT requests URLs like | ||
| * "http://127.0.0.1:1337/localhost:80". It would treat "localhost:" as a protocol. | ||
| */ | ||
| if (url.protocol === "localhost:") authority = url.href; | ||
| else authority = url.pathname.replace(/^\/+/, ""); | ||
| /** | ||
| * @note Define "url" as a getter because Undici uses their own | ||
| * logic to resolve the "request.url" property. Simply reassigning | ||
| * its value doesn't do anything. This is a destructive action | ||
| * but it's safe because "CONNECT" requests are forbidden per fetch. | ||
| */ | ||
| Object.defineProperty(this, "url", { | ||
| get: () => authority, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| if (mode != null && mode !== safeMode) this.#setInternalProperty("mode", mode); | ||
| } | ||
| #setInternalProperty(key, value) { | ||
| const internalState = getValueBySymbol("state", this); | ||
| if (internalState) Reflect.set(internalState, key, value); | ||
| else Object.defineProperty(this, key, { | ||
| value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| }; | ||
| var FetchResponse = class FetchResponse extends Response { | ||
| static { | ||
| this.STATUS_CODES_WITHOUT_BODY = [ | ||
| 101, | ||
| 103, | ||
| 204, | ||
| 205, | ||
| 304 | ||
| ]; | ||
| } | ||
| static { | ||
| this.STATUS_CODES_WITH_REDIRECT = [ | ||
| 301, | ||
| 302, | ||
| 303, | ||
| 307, | ||
| 308 | ||
| ]; | ||
| } | ||
| static isConfigurableStatusCode(status) { | ||
| return status >= 200 && status <= 599; | ||
| } | ||
| static isRedirectResponse(status) { | ||
| return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status); | ||
| } | ||
| /** | ||
| * Returns a boolean indicating whether the given response status | ||
| * code represents a response that can have a body. | ||
| */ | ||
| static isResponseWithBody(status) { | ||
| return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status); | ||
| } | ||
| static setUrl(url, response) { | ||
| if (!url || url === "about:" || !canParseUrl(url)) return; | ||
| const state = getValueBySymbol("state", response); | ||
| if (state) state.urlList.push(new URL(url)); | ||
| else Object.defineProperty(response, "url", { | ||
| value: url, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| /** | ||
| * Parses the given raw HTTP headers into a Fetch API `Headers` instance. | ||
| */ | ||
| static parseRawHeaders(rawHeaders) { | ||
| const headers = new Headers(); | ||
| for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]); | ||
| return headers; | ||
| } | ||
| /** | ||
| * Safely clones the given `Response`. | ||
| * Coerces response clone exceptions into 500 mocked responses. | ||
| * Handy in the environments that introduce arbitrary response | ||
| * cloning restrictions, like "101 Switching Protocols" cloning | ||
| * in "miniflare". | ||
| */ | ||
| static clone(response) { | ||
| try { | ||
| return response.clone(); | ||
| } catch (error) { | ||
| return Response.json(error instanceof Error ? { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| } : {}, { | ||
| status: 500, | ||
| statusText: "Unclonable Response" | ||
| }); | ||
| } | ||
| } | ||
| constructor(body, init = {}) { | ||
| const status = init.status ?? 200; | ||
| const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200; | ||
| const finalBody = FetchResponse.isResponseWithBody(status) ? body : null; | ||
| super(finalBody, { | ||
| status: safeStatus, | ||
| statusText: init.statusText, | ||
| headers: init.headers | ||
| }); | ||
| if (status !== safeStatus) { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const state = getValueBySymbol("state", this); | ||
| if (state) state.status = status; | ||
| else Object.defineProperty(this, "status", { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| FetchResponse.setUrl(init.url, this); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/getRawRequest.ts | ||
| const kRawRequest = Symbol("kRawRequest"); | ||
| /** | ||
| * Returns a raw request instance associated with this request. | ||
| * | ||
| * @example | ||
| * interceptor.on('request', ({ request }) => { | ||
| * const rawRequest = getRawRequest(request) | ||
| * | ||
| * if (rawRequest instanceof http.ClientRequest) { | ||
| * console.log(rawRequest.rawHeaders) | ||
| * } | ||
| * }) | ||
| */ | ||
| function getRawRequest(request) { | ||
| return Reflect.get(request, kRawRequest); | ||
| } | ||
| function setRawRequest(request, rawRequest) { | ||
| Reflect.set(request, kRawRequest, rawRequest); | ||
| } | ||
| //#endregion | ||
| export { canParseUrl as a, FetchResponse as i, setRawRequest as n, RequestController as o, FetchRequest as r, InterceptorError as s, getRawRequest as t }; | ||
| //# sourceMappingURL=getRawRequest-BP--nUwn.mjs.map |
| {"version":3,"file":"getRawRequest-BP--nUwn.mjs","names":["request: Request","source: RequestControllerSource","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts","../../src/getRawRequest.ts"],"sourcesContent":["export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n if (status !== safeStatus) {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const state = getValueBySymbol<UndiciResponseState>('state', this)\n\n if (state) {\n state.status = status\n } else {\n Object.defineProperty(this, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n}\n","const kRawRequest = Symbol('kRawRequest')\n\n/**\n * Returns a raw request instance associated with this request.\n *\n * @example\n * interceptor.on('request', ({ request }) => {\n * const rawRequest = getRawRequest(request)\n *\n * if (rawRequest instanceof http.ClientRequest) {\n * console.log(rawRequest.rawHeaders)\n * }\n * })\n */\nexport function getRawRequest(request: Request): unknown | undefined {\n return Reflect.get(request, kRawRequest)\n}\n\nexport function setRawRequest(request: Request, rawRequest: unknown): void {\n Reflect.set(request, kRawRequest, rawRequest)\n}\n"],"mappings":"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"} |
| const require_getRawRequest = require('./getRawRequest-B6GjFKAr.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-B5o6PHq3.cjs'); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let _open_draft_until = require("@open-draft/until"); | ||
| //#region src/utils/isObject.ts | ||
| /** | ||
| * Determines if a given value is an instance of object. | ||
| */ | ||
| function isObject(value, loose = false) { | ||
| return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]"; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isPropertyAccessible.ts | ||
| /** | ||
| * A function that validates if property access is possible on an object | ||
| * without throwing. It returns `true` if the property access is possible | ||
| * and `false` otherwise. | ||
| * | ||
| * Environments like miniflare will throw on property access on certain objects | ||
| * like Request and Response, for unimplemented properties. | ||
| */ | ||
| function isPropertyAccessible(obj, key) { | ||
| try { | ||
| obj[key]; | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/responseUtils.ts | ||
| /** | ||
| * Creates a generic 500 Unhandled Exception response. | ||
| */ | ||
| function createServerErrorResponse(body) { | ||
| return new Response(JSON.stringify(body instanceof Error ? { | ||
| name: body.name, | ||
| message: body.message, | ||
| stack: body.stack | ||
| } : body), { | ||
| status: 500, | ||
| statusText: "Unhandled Exception", | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| /** | ||
| * Check if the given response is a `Response.error()`. | ||
| * | ||
| * @note Some environments, like Miniflare (Cloudflare) do not | ||
| * implement the "Response.type" property and throw on its access. | ||
| * Safely check if we can access "type" on "Response" before continuing. | ||
| * @see https://github.com/mswjs/msw/issues/1834 | ||
| */ | ||
| function isResponseError(response) { | ||
| return response != null && response instanceof Response && isPropertyAccessible(response, "type") && response.type === "error"; | ||
| } | ||
| /** | ||
| * Check if the given value is a `Response` or a Response-like object. | ||
| * This is different from `value instanceof Response` because it supports | ||
| * custom `Response` constructors, like the one when using Undici directly. | ||
| */ | ||
| function isResponseLike(value) { | ||
| return isObject(value, true) && isPropertyAccessible(value, "status") && isPropertyAccessible(value, "statusText") && isPropertyAccessible(value, "bodyUsed"); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isNodeLikeError.ts | ||
| function isNodeLikeError(error) { | ||
| if (error == null) return false; | ||
| if (!(error instanceof Error)) return false; | ||
| return "code" in error && "errno" in error; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/handleRequest.ts | ||
| async function handleRequest(options) { | ||
| const handleResponse = async (response) => { | ||
| if (response instanceof Error) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| if (isResponseError(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| /** | ||
| * Handle normal responses or response-like objects. | ||
| * @note This must come before the arbitrary object check | ||
| * since Response instances are, in fact, objects. | ||
| */ | ||
| if (isResponseLike(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| if (isObject(response)) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| const handleResponseError = async (error) => { | ||
| if (error instanceof require_getRawRequest.InterceptorError) throw result.error; | ||
| if (isNodeLikeError(error)) { | ||
| await options.controller.errorWith(error); | ||
| return true; | ||
| } | ||
| if (error instanceof Response) return await handleResponse(error); | ||
| return false; | ||
| }; | ||
| const requestAbortPromise = new _open_draft_deferred_promise.DeferredPromise(); | ||
| /** | ||
| * @note `signal` is not always defined in React Native. | ||
| */ | ||
| if (options.request.signal) { | ||
| if (options.request.signal.aborted) { | ||
| await options.controller.errorWith(options.request.signal.reason); | ||
| return; | ||
| } | ||
| options.request.signal.addEventListener("abort", () => { | ||
| requestAbortPromise.reject(options.request.signal.reason); | ||
| }, { once: true }); | ||
| } | ||
| const result = await (0, _open_draft_until.until)(async () => { | ||
| const requestListenersPromise = require_hasConfigurableGlobal.emitAsync(options.emitter, "request", { | ||
| requestId: options.requestId, | ||
| request: options.request, | ||
| controller: options.controller | ||
| }); | ||
| await Promise.race([ | ||
| requestAbortPromise, | ||
| requestListenersPromise, | ||
| options.controller.handled | ||
| ]); | ||
| }); | ||
| if (requestAbortPromise.state === "rejected") { | ||
| await options.controller.errorWith(requestAbortPromise.rejectionReason); | ||
| return; | ||
| } | ||
| if (result.error) { | ||
| if (await handleResponseError(result.error)) return; | ||
| if (options.emitter.listenerCount("unhandledException") > 0) { | ||
| const unhandledExceptionController = new require_getRawRequest.RequestController(options.request, { | ||
| passthrough() {}, | ||
| async respondWith(response) { | ||
| await handleResponse(response); | ||
| }, | ||
| async errorWith(reason) { | ||
| /** | ||
| * @note Handle the result of the unhandled controller | ||
| * in the same way as the original request controller. | ||
| * The exception here is that thrown errors within the | ||
| * "unhandledException" event do NOT result in another | ||
| * emit of the same event. They are forwarded as-is. | ||
| */ | ||
| await options.controller.errorWith(reason); | ||
| } | ||
| }); | ||
| await require_hasConfigurableGlobal.emitAsync(options.emitter, "unhandledException", { | ||
| error: result.error, | ||
| request: options.request, | ||
| requestId: options.requestId, | ||
| controller: unhandledExceptionController | ||
| }); | ||
| if (unhandledExceptionController.readyState !== require_getRawRequest.RequestController.PENDING) return; | ||
| } | ||
| await options.controller.respondWith(createServerErrorResponse(result.error)); | ||
| return; | ||
| } | ||
| if (options.controller.readyState === require_getRawRequest.RequestController.PENDING) return await options.controller.passthrough(); | ||
| return options.controller.handled; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'handleRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return handleRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isResponseError', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isResponseError; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=handleRequest-BIte-8l8.cjs.map |
| {"version":3,"file":"handleRequest-BIte-8l8.cjs","names":["InterceptorError","DeferredPromise","emitAsync","RequestController"],"sources":["../../src/utils/isObject.ts","../../src/utils/isPropertyAccessible.ts","../../src/utils/responseUtils.ts","../../src/utils/isNodeLikeError.ts","../../src/utils/handleRequest.ts"],"sourcesContent":["/**\n * Determines if a given value is an instance of object.\n */\nexport function isObject<T>(value: any, loose = false): value is T {\n return loose\n ? Object.prototype.toString.call(value).startsWith('[object ')\n : Object.prototype.toString.call(value) === '[object Object]'\n}\n","/**\n * A function that validates if property access is possible on an object\n * without throwing. It returns `true` if the property access is possible\n * and `false` otherwise.\n *\n * Environments like miniflare will throw on property access on certain objects\n * like Request and Response, for unimplemented properties.\n */\nexport function isPropertyAccessible<Obj extends Record<string, any>>(\n obj: Obj,\n key: keyof Obj\n) {\n try {\n obj[key]\n return true\n } catch {\n return false\n }\n}\n","import { isObject } from './isObject'\nimport { isPropertyAccessible } from './isPropertyAccessible'\n\n/**\n * Creates a generic 500 Unhandled Exception response.\n */\nexport function createServerErrorResponse(body: unknown): Response {\n return new Response(\n JSON.stringify(\n body instanceof Error\n ? {\n name: body.name,\n message: body.message,\n stack: body.stack,\n }\n : body\n ),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n}\n\nexport type ResponseError = Response & { type: 'error' }\n\n/**\n * Check if the given response is a `Response.error()`.\n *\n * @note Some environments, like Miniflare (Cloudflare) do not\n * implement the \"Response.type\" property and throw on its access.\n * Safely check if we can access \"type\" on \"Response\" before continuing.\n * @see https://github.com/mswjs/msw/issues/1834\n */\nexport function isResponseError(response: unknown): response is ResponseError {\n return (\n response != null &&\n response instanceof Response &&\n isPropertyAccessible(response, 'type') &&\n response.type === 'error'\n )\n}\n\n/**\n * Check if the given value is a `Response` or a Response-like object.\n * This is different from `value instanceof Response` because it supports\n * custom `Response` constructors, like the one when using Undici directly.\n */\nexport function isResponseLike(value: unknown): value is Response {\n return (\n isObject<Record<string, any>>(value, true) &&\n isPropertyAccessible(value, 'status') &&\n isPropertyAccessible(value, 'statusText') &&\n isPropertyAccessible(value, 'bodyUsed')\n )\n}\n","export function isNodeLikeError(\n error: unknown\n): error is NodeJS.ErrnoException {\n if (error == null) {\n return false\n }\n\n if (!(error instanceof Error)) {\n return false\n }\n\n return 'code' in error && 'errno' in error\n}\n","import type { Emitter } from 'strict-event-emitter'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport type { HttpRequestEventMap } from '../glossary'\nimport { emitAsync } from './emitAsync'\nimport { RequestController } from '../RequestController'\nimport {\n createServerErrorResponse,\n isResponseError,\n isResponseLike,\n} from './responseUtils'\nimport { InterceptorError } from '../InterceptorError'\nimport { isNodeLikeError } from './isNodeLikeError'\nimport { isObject } from './isObject'\n\ninterface HandleRequestOptions {\n requestId: string\n request: Request\n emitter: Emitter<HttpRequestEventMap>\n controller: RequestController\n}\n\nexport async function handleRequest(\n options: HandleRequestOptions\n): Promise<void> {\n const handleResponse = async (\n response: Response | Error | Record<string, any>\n ) => {\n if (response instanceof Error) {\n await options.controller.errorWith(response)\n return true\n }\n\n // Handle \"Response.error()\" instances.\n if (isResponseError(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n /**\n * Handle normal responses or response-like objects.\n * @note This must come before the arbitrary object check\n * since Response instances are, in fact, objects.\n */\n if (isResponseLike(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n // Handle arbitrary objects provided to `.errorWith(reason)`.\n if (isObject(response)) {\n await options.controller.errorWith(response)\n return true\n }\n\n return false\n }\n\n const handleResponseError = async (error: unknown): Promise<boolean> => {\n // Forward the special interceptor error instances\n // to the developer. These must not be handled in any way.\n if (error instanceof InterceptorError) {\n throw result.error\n }\n\n // Support mocking Node.js-like errors.\n if (isNodeLikeError(error)) {\n await options.controller.errorWith(error)\n return true\n }\n\n // Handle thrown responses.\n if (error instanceof Response) {\n return await handleResponse(error)\n }\n\n return false\n }\n\n // Add the last \"request\" listener to check if the request\n // has been handled in any way. If it hasn't, resolve the\n // response promise with undefined.\n // options.emitter.once('request', async ({ requestId: pendingRequestId }) => {\n // if (\n // pendingRequestId === options.requestId &&\n // options.controller.readyState === RequestController.PENDING\n // ) {\n // await options.controller.passthrough()\n // }\n // })\n\n const requestAbortPromise = new DeferredPromise<void, unknown>()\n\n /**\n * @note `signal` is not always defined in React Native.\n */\n if (options.request.signal) {\n if (options.request.signal.aborted) {\n await options.controller.errorWith(options.request.signal.reason)\n return\n }\n\n options.request.signal.addEventListener(\n 'abort',\n () => {\n requestAbortPromise.reject(options.request.signal.reason)\n },\n { once: true }\n )\n }\n\n const result = await until(async () => {\n // Emit the \"request\" event and wait until all the listeners\n // for that event are finished (e.g. async listeners awaited).\n // By the end of this promise, the developer cannot affect the\n // request anymore.\n const requestListenersPromise = emitAsync(options.emitter, 'request', {\n requestId: options.requestId,\n request: options.request,\n controller: options.controller,\n })\n\n await Promise.race([\n // Short-circuit the request handling promise if the request gets aborted.\n requestAbortPromise,\n requestListenersPromise,\n options.controller.handled,\n ])\n })\n\n // Handle the request being aborted while waiting for the request listeners.\n if (requestAbortPromise.state === 'rejected') {\n await options.controller.errorWith(requestAbortPromise.rejectionReason)\n return\n }\n\n if (result.error) {\n // Handle the error during the request listener execution.\n // These can be thrown responses or request errors.\n if (await handleResponseError(result.error)) {\n return\n }\n\n // If the developer has added \"unhandledException\" listeners,\n // allow them to handle the error. They can translate it to a\n // mocked response, network error, or forward it as-is.\n if (options.emitter.listenerCount('unhandledException') > 0) {\n // Create a new request controller just for the unhandled exception case.\n // This is needed because the original controller might have been already\n // interacted with (e.g. \"respondWith\" or \"errorWith\" called on it).\n const unhandledExceptionController = new RequestController(\n options.request,\n {\n /**\n * @note Intentionally empty passthrough handle.\n * This controller is created within another controller and we only need\n * to know if `unhandledException` listeners handled the request.\n */\n passthrough() {},\n async respondWith(response) {\n await handleResponse(response)\n },\n async errorWith(reason) {\n /**\n * @note Handle the result of the unhandled controller\n * in the same way as the original request controller.\n * The exception here is that thrown errors within the\n * \"unhandledException\" event do NOT result in another\n * emit of the same event. They are forwarded as-is.\n */\n await options.controller.errorWith(reason)\n },\n }\n )\n\n await emitAsync(options.emitter, 'unhandledException', {\n error: result.error,\n request: options.request,\n requestId: options.requestId,\n controller: unhandledExceptionController,\n })\n\n // If all the \"unhandledException\" listeners have finished\n // but have not handled the request in any way, passthrough.\n if (\n unhandledExceptionController.readyState !== RequestController.PENDING\n ) {\n return\n }\n }\n\n // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.\n await options.controller.respondWith(\n createServerErrorResponse(result.error)\n )\n return\n }\n\n // If the request hasn't been handled by this point, passthrough.\n if (options.controller.readyState === RequestController.PENDING) {\n return await options.controller.passthrough()\n }\n\n return options.controller.handled\n}\n"],"mappings":"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"} |
| import { o as RequestController, s as InterceptorError } from "./getRawRequest-BP--nUwn.mjs"; | ||
| import { r as emitAsync } from "./hasConfigurableGlobal-B39W0thh.mjs"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { until } from "@open-draft/until"; | ||
| //#region src/utils/isObject.ts | ||
| /** | ||
| * Determines if a given value is an instance of object. | ||
| */ | ||
| function isObject(value, loose = false) { | ||
| return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]"; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isPropertyAccessible.ts | ||
| /** | ||
| * A function that validates if property access is possible on an object | ||
| * without throwing. It returns `true` if the property access is possible | ||
| * and `false` otherwise. | ||
| * | ||
| * Environments like miniflare will throw on property access on certain objects | ||
| * like Request and Response, for unimplemented properties. | ||
| */ | ||
| function isPropertyAccessible(obj, key) { | ||
| try { | ||
| obj[key]; | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/responseUtils.ts | ||
| /** | ||
| * Creates a generic 500 Unhandled Exception response. | ||
| */ | ||
| function createServerErrorResponse(body) { | ||
| return new Response(JSON.stringify(body instanceof Error ? { | ||
| name: body.name, | ||
| message: body.message, | ||
| stack: body.stack | ||
| } : body), { | ||
| status: 500, | ||
| statusText: "Unhandled Exception", | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| /** | ||
| * Check if the given response is a `Response.error()`. | ||
| * | ||
| * @note Some environments, like Miniflare (Cloudflare) do not | ||
| * implement the "Response.type" property and throw on its access. | ||
| * Safely check if we can access "type" on "Response" before continuing. | ||
| * @see https://github.com/mswjs/msw/issues/1834 | ||
| */ | ||
| function isResponseError(response) { | ||
| return response != null && response instanceof Response && isPropertyAccessible(response, "type") && response.type === "error"; | ||
| } | ||
| /** | ||
| * Check if the given value is a `Response` or a Response-like object. | ||
| * This is different from `value instanceof Response` because it supports | ||
| * custom `Response` constructors, like the one when using Undici directly. | ||
| */ | ||
| function isResponseLike(value) { | ||
| return isObject(value, true) && isPropertyAccessible(value, "status") && isPropertyAccessible(value, "statusText") && isPropertyAccessible(value, "bodyUsed"); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isNodeLikeError.ts | ||
| function isNodeLikeError(error) { | ||
| if (error == null) return false; | ||
| if (!(error instanceof Error)) return false; | ||
| return "code" in error && "errno" in error; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/handleRequest.ts | ||
| async function handleRequest(options) { | ||
| const handleResponse = async (response) => { | ||
| if (response instanceof Error) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| if (isResponseError(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| /** | ||
| * Handle normal responses or response-like objects. | ||
| * @note This must come before the arbitrary object check | ||
| * since Response instances are, in fact, objects. | ||
| */ | ||
| if (isResponseLike(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| if (isObject(response)) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| const handleResponseError = async (error) => { | ||
| if (error instanceof InterceptorError) throw result.error; | ||
| if (isNodeLikeError(error)) { | ||
| await options.controller.errorWith(error); | ||
| return true; | ||
| } | ||
| if (error instanceof Response) return await handleResponse(error); | ||
| return false; | ||
| }; | ||
| const requestAbortPromise = new DeferredPromise(); | ||
| /** | ||
| * @note `signal` is not always defined in React Native. | ||
| */ | ||
| if (options.request.signal) { | ||
| if (options.request.signal.aborted) { | ||
| await options.controller.errorWith(options.request.signal.reason); | ||
| return; | ||
| } | ||
| options.request.signal.addEventListener("abort", () => { | ||
| requestAbortPromise.reject(options.request.signal.reason); | ||
| }, { once: true }); | ||
| } | ||
| const result = await until(async () => { | ||
| const requestListenersPromise = emitAsync(options.emitter, "request", { | ||
| requestId: options.requestId, | ||
| request: options.request, | ||
| controller: options.controller | ||
| }); | ||
| await Promise.race([ | ||
| requestAbortPromise, | ||
| requestListenersPromise, | ||
| options.controller.handled | ||
| ]); | ||
| }); | ||
| if (requestAbortPromise.state === "rejected") { | ||
| await options.controller.errorWith(requestAbortPromise.rejectionReason); | ||
| return; | ||
| } | ||
| if (result.error) { | ||
| if (await handleResponseError(result.error)) return; | ||
| if (options.emitter.listenerCount("unhandledException") > 0) { | ||
| const unhandledExceptionController = new RequestController(options.request, { | ||
| passthrough() {}, | ||
| async respondWith(response) { | ||
| await handleResponse(response); | ||
| }, | ||
| async errorWith(reason) { | ||
| /** | ||
| * @note Handle the result of the unhandled controller | ||
| * in the same way as the original request controller. | ||
| * The exception here is that thrown errors within the | ||
| * "unhandledException" event do NOT result in another | ||
| * emit of the same event. They are forwarded as-is. | ||
| */ | ||
| await options.controller.errorWith(reason); | ||
| } | ||
| }); | ||
| await emitAsync(options.emitter, "unhandledException", { | ||
| error: result.error, | ||
| request: options.request, | ||
| requestId: options.requestId, | ||
| controller: unhandledExceptionController | ||
| }); | ||
| if (unhandledExceptionController.readyState !== RequestController.PENDING) return; | ||
| } | ||
| await options.controller.respondWith(createServerErrorResponse(result.error)); | ||
| return; | ||
| } | ||
| if (options.controller.readyState === RequestController.PENDING) return await options.controller.passthrough(); | ||
| return options.controller.handled; | ||
| } | ||
| //#endregion | ||
| export { isResponseError as n, handleRequest as t }; | ||
| //# sourceMappingURL=handleRequest-CnDA8MVO.mjs.map |
| {"version":3,"file":"handleRequest-CnDA8MVO.mjs","names":[],"sources":["../../src/utils/isObject.ts","../../src/utils/isPropertyAccessible.ts","../../src/utils/responseUtils.ts","../../src/utils/isNodeLikeError.ts","../../src/utils/handleRequest.ts"],"sourcesContent":["/**\n * Determines if a given value is an instance of object.\n */\nexport function isObject<T>(value: any, loose = false): value is T {\n return loose\n ? Object.prototype.toString.call(value).startsWith('[object ')\n : Object.prototype.toString.call(value) === '[object Object]'\n}\n","/**\n * A function that validates if property access is possible on an object\n * without throwing. It returns `true` if the property access is possible\n * and `false` otherwise.\n *\n * Environments like miniflare will throw on property access on certain objects\n * like Request and Response, for unimplemented properties.\n */\nexport function isPropertyAccessible<Obj extends Record<string, any>>(\n obj: Obj,\n key: keyof Obj\n) {\n try {\n obj[key]\n return true\n } catch {\n return false\n }\n}\n","import { isObject } from './isObject'\nimport { isPropertyAccessible } from './isPropertyAccessible'\n\n/**\n * Creates a generic 500 Unhandled Exception response.\n */\nexport function createServerErrorResponse(body: unknown): Response {\n return new Response(\n JSON.stringify(\n body instanceof Error\n ? {\n name: body.name,\n message: body.message,\n stack: body.stack,\n }\n : body\n ),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n}\n\nexport type ResponseError = Response & { type: 'error' }\n\n/**\n * Check if the given response is a `Response.error()`.\n *\n * @note Some environments, like Miniflare (Cloudflare) do not\n * implement the \"Response.type\" property and throw on its access.\n * Safely check if we can access \"type\" on \"Response\" before continuing.\n * @see https://github.com/mswjs/msw/issues/1834\n */\nexport function isResponseError(response: unknown): response is ResponseError {\n return (\n response != null &&\n response instanceof Response &&\n isPropertyAccessible(response, 'type') &&\n response.type === 'error'\n )\n}\n\n/**\n * Check if the given value is a `Response` or a Response-like object.\n * This is different from `value instanceof Response` because it supports\n * custom `Response` constructors, like the one when using Undici directly.\n */\nexport function isResponseLike(value: unknown): value is Response {\n return (\n isObject<Record<string, any>>(value, true) &&\n isPropertyAccessible(value, 'status') &&\n isPropertyAccessible(value, 'statusText') &&\n isPropertyAccessible(value, 'bodyUsed')\n )\n}\n","export function isNodeLikeError(\n error: unknown\n): error is NodeJS.ErrnoException {\n if (error == null) {\n return false\n }\n\n if (!(error instanceof Error)) {\n return false\n }\n\n return 'code' in error && 'errno' in error\n}\n","import type { Emitter } from 'strict-event-emitter'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport type { HttpRequestEventMap } from '../glossary'\nimport { emitAsync } from './emitAsync'\nimport { RequestController } from '../RequestController'\nimport {\n createServerErrorResponse,\n isResponseError,\n isResponseLike,\n} from './responseUtils'\nimport { InterceptorError } from '../InterceptorError'\nimport { isNodeLikeError } from './isNodeLikeError'\nimport { isObject } from './isObject'\n\ninterface HandleRequestOptions {\n requestId: string\n request: Request\n emitter: Emitter<HttpRequestEventMap>\n controller: RequestController\n}\n\nexport async function handleRequest(\n options: HandleRequestOptions\n): Promise<void> {\n const handleResponse = async (\n response: Response | Error | Record<string, any>\n ) => {\n if (response instanceof Error) {\n await options.controller.errorWith(response)\n return true\n }\n\n // Handle \"Response.error()\" instances.\n if (isResponseError(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n /**\n * Handle normal responses or response-like objects.\n * @note This must come before the arbitrary object check\n * since Response instances are, in fact, objects.\n */\n if (isResponseLike(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n // Handle arbitrary objects provided to `.errorWith(reason)`.\n if (isObject(response)) {\n await options.controller.errorWith(response)\n return true\n }\n\n return false\n }\n\n const handleResponseError = async (error: unknown): Promise<boolean> => {\n // Forward the special interceptor error instances\n // to the developer. These must not be handled in any way.\n if (error instanceof InterceptorError) {\n throw result.error\n }\n\n // Support mocking Node.js-like errors.\n if (isNodeLikeError(error)) {\n await options.controller.errorWith(error)\n return true\n }\n\n // Handle thrown responses.\n if (error instanceof Response) {\n return await handleResponse(error)\n }\n\n return false\n }\n\n // Add the last \"request\" listener to check if the request\n // has been handled in any way. If it hasn't, resolve the\n // response promise with undefined.\n // options.emitter.once('request', async ({ requestId: pendingRequestId }) => {\n // if (\n // pendingRequestId === options.requestId &&\n // options.controller.readyState === RequestController.PENDING\n // ) {\n // await options.controller.passthrough()\n // }\n // })\n\n const requestAbortPromise = new DeferredPromise<void, unknown>()\n\n /**\n * @note `signal` is not always defined in React Native.\n */\n if (options.request.signal) {\n if (options.request.signal.aborted) {\n await options.controller.errorWith(options.request.signal.reason)\n return\n }\n\n options.request.signal.addEventListener(\n 'abort',\n () => {\n requestAbortPromise.reject(options.request.signal.reason)\n },\n { once: true }\n )\n }\n\n const result = await until(async () => {\n // Emit the \"request\" event and wait until all the listeners\n // for that event are finished (e.g. async listeners awaited).\n // By the end of this promise, the developer cannot affect the\n // request anymore.\n const requestListenersPromise = emitAsync(options.emitter, 'request', {\n requestId: options.requestId,\n request: options.request,\n controller: options.controller,\n })\n\n await Promise.race([\n // Short-circuit the request handling promise if the request gets aborted.\n requestAbortPromise,\n requestListenersPromise,\n options.controller.handled,\n ])\n })\n\n // Handle the request being aborted while waiting for the request listeners.\n if (requestAbortPromise.state === 'rejected') {\n await options.controller.errorWith(requestAbortPromise.rejectionReason)\n return\n }\n\n if (result.error) {\n // Handle the error during the request listener execution.\n // These can be thrown responses or request errors.\n if (await handleResponseError(result.error)) {\n return\n }\n\n // If the developer has added \"unhandledException\" listeners,\n // allow them to handle the error. They can translate it to a\n // mocked response, network error, or forward it as-is.\n if (options.emitter.listenerCount('unhandledException') > 0) {\n // Create a new request controller just for the unhandled exception case.\n // This is needed because the original controller might have been already\n // interacted with (e.g. \"respondWith\" or \"errorWith\" called on it).\n const unhandledExceptionController = new RequestController(\n options.request,\n {\n /**\n * @note Intentionally empty passthrough handle.\n * This controller is created within another controller and we only need\n * to know if `unhandledException` listeners handled the request.\n */\n passthrough() {},\n async respondWith(response) {\n await handleResponse(response)\n },\n async errorWith(reason) {\n /**\n * @note Handle the result of the unhandled controller\n * in the same way as the original request controller.\n * The exception here is that thrown errors within the\n * \"unhandledException\" event do NOT result in another\n * emit of the same event. They are forwarded as-is.\n */\n await options.controller.errorWith(reason)\n },\n }\n )\n\n await emitAsync(options.emitter, 'unhandledException', {\n error: result.error,\n request: options.request,\n requestId: options.requestId,\n controller: unhandledExceptionController,\n })\n\n // If all the \"unhandledException\" listeners have finished\n // but have not handled the request in any way, passthrough.\n if (\n unhandledExceptionController.readyState !== RequestController.PENDING\n ) {\n return\n }\n }\n\n // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.\n await options.controller.respondWith(\n createServerErrorResponse(result.error)\n )\n return\n }\n\n // If the request hasn't been handled by this point, passthrough.\n if (options.controller.readyState === RequestController.PENDING) {\n return await options.controller.passthrough()\n }\n\n return options.controller.handled\n}\n"],"mappings":"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"} |
| import { invariant } from "outvariant"; | ||
| //#region src/utils/emitAsync.ts | ||
| /** | ||
| * Emits an event on the given emitter but executes | ||
| * the listeners sequentially. This accounts for asynchronous | ||
| * listeners (e.g. those having "sleep" and handling the request). | ||
| */ | ||
| async function emitAsync(emitter, eventName, ...data) { | ||
| const listeners = emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| for (const listener of listeners) await listener.apply(emitter, data); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/globalsRegistry.ts | ||
| var GlobalsRegistry = class { | ||
| #globals = /* @__PURE__ */ new Map(); | ||
| replaceGlobal(key, nextValue) { | ||
| invariant(!this.#globals.has(key), `Failed to replace a global value at "${key}": already replaced.`); | ||
| const match = getDeepPropertyDescriptor(globalThis, key); | ||
| if (typeof match === "undefined") { | ||
| console.warn(`Failed to replace a global value at "${key}": not a global value.`); | ||
| return () => {}; | ||
| } | ||
| Object.defineProperty(globalThis, key, { | ||
| value: nextValue, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| const restoreGlobal = () => { | ||
| if (!this.#globals.has(key)) return; | ||
| if (match.owner === globalThis) Object.defineProperty(match.owner, key, match.descriptor); | ||
| else | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the owner isn't `globalThis`, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(globalThis, key); | ||
| this.#globals.delete(key); | ||
| }; | ||
| this.#globals.set(key, restoreGlobal); | ||
| return restoreGlobal; | ||
| } | ||
| restoreAllGlobals() { | ||
| const errors = []; | ||
| for (const [, restoreGlobal] of this.#globals) try { | ||
| restoreGlobal(); | ||
| } catch (error) { | ||
| if (error instanceof Error) errors.push(error); | ||
| else throw error; | ||
| } | ||
| if (errors.length > 0) throw new AggregateError(errors, "FOO!"); | ||
| } | ||
| }; | ||
| const globalsRegistry = new GlobalsRegistry(); | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| function getDeepPropertyDescriptor(owner, key) { | ||
| let currentOwner = owner; | ||
| let descriptor; | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key); | ||
| if (descriptor) return { | ||
| owner: currentOwner, | ||
| descriptor | ||
| }; | ||
| currentOwner = Object.getPrototypeOf(currentOwner); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/hasConfigurableGlobal.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given global property | ||
| * is defined and is configurable. | ||
| */ | ||
| function hasConfigurableGlobal(propertyName) { | ||
| const match = getDeepPropertyDescriptor(globalThis, propertyName); | ||
| if (typeof match === "undefined") return false; | ||
| const { descriptor } = match; | ||
| if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false; | ||
| if (typeof descriptor.get === "undefined" && descriptor.value == null) return false; | ||
| if (typeof descriptor.set === "undefined" && !descriptor.configurable) { | ||
| console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| //#endregion | ||
| export { globalsRegistry as n, emitAsync as r, hasConfigurableGlobal as t }; | ||
| //# sourceMappingURL=hasConfigurableGlobal-B39W0thh.mjs.map |
| {"version":3,"file":"hasConfigurableGlobal-B39W0thh.mjs","names":["#globals","errors: Array<Error>","currentOwner: Owner | null","descriptor: PropertyDescriptor | undefined"],"sources":["../../src/utils/emitAsync.ts","../../src/utils/globalsRegistry.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","import { invariant } from 'outvariant'\n\nclass GlobalsRegistry {\n #globals = new Map<keyof typeof globalThis, () => void>()\n\n public replaceGlobal<K extends keyof typeof globalThis>(\n key: K,\n nextValue: (typeof globalThis)[K]\n ): () => void {\n invariant(\n !this.#globals.has(key),\n `Failed to replace a global value at \"${key}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(globalThis, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${key}\": not a global value.`\n )\n return () => {}\n }\n\n Object.defineProperty(globalThis, key, {\n value: nextValue,\n enumerable: true,\n configurable: true,\n })\n\n const restoreGlobal = () => {\n if (!this.#globals.has(key)) {\n return\n }\n\n if (match.owner === globalThis) {\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the owner isn't `globalThis`, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(globalThis, key)\n }\n\n this.#globals.delete(key)\n }\n\n this.#globals.set(key, restoreGlobal)\n\n return restoreGlobal\n }\n\n public restoreAllGlobals(): void {\n const errors: Array<Error> = []\n\n for (const [, restoreGlobal] of this.#globals) {\n try {\n restoreGlobal()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const globalsRegistry = new GlobalsRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: keyof Owner\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n","import { getDeepPropertyDescriptor } from './globalsRegistry'\n\n/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(\n propertyName: keyof typeof globalThis\n): boolean {\n const match = getDeepPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof match === 'undefined') {\n return false\n }\n\n const { descriptor } = match\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;;;;;;AAOA,eAAsB,UAIpB,SACA,WACA,GAAG,MACY;CACf,MAAM,YAAY,QAAQ,UAAU,UAAU;AAE9C,KAAI,UAAU,WAAW,EACvB;AAGF,MAAK,MAAM,YAAY,UACrB,OAAM,SAAS,MAAM,SAAS,KAAK;;;;;ACpBvC,IAAM,kBAAN,MAAsB;CACpB,2BAAW,IAAI,KAA0C;CAEzD,AAAO,cACL,KACA,WACY;AACZ,YACE,CAAC,MAAKA,QAAS,IAAI,IAAI,EACvB,wCAAwC,IAAI,sBAC7C;EAED,MAAM,QAAQ,0BAA0B,YAAY,IAAI;AAExD,MAAI,OAAO,UAAU,aAAa;AAChC,WAAQ,KACN,wCAAwC,IAAI,wBAC7C;AACD,gBAAa;;AAGf,SAAO,eAAe,YAAY,KAAK;GACrC,OAAO;GACP,YAAY;GACZ,cAAc;GACf,CAAC;EAEF,MAAM,sBAAsB;AAC1B,OAAI,CAAC,MAAKA,QAAS,IAAI,IAAI,CACzB;AAGF,OAAI,MAAM,UAAU,WAClB,QAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;;;;;;;AAOzD,WAAQ,eAAe,YAAY,IAAI;AAGzC,SAAKA,QAAS,OAAO,IAAI;;AAG3B,QAAKA,QAAS,IAAI,KAAK,cAAc;AAErC,SAAO;;CAGT,AAAO,oBAA0B;EAC/B,MAAMC,SAAuB,EAAE;AAE/B,OAAK,MAAM,GAAG,kBAAkB,MAAKD,QACnC,KAAI;AACF,kBAAe;WACR,OAAO;AACd,OAAI,iBAAiB,MACnB,QAAO,KAAK,MAAM;OAElB,OAAM;;AAKZ,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,QAAQ,OAAO;;;AAK9C,MAAa,kBAAkB,IAAI,iBAAiB;;;;;;;AAapD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAIE,eAA6B;CACjC,IAAIC;AAEJ,QAAO,cAAc;AACnB,eAAa,OAAO,yBAAyB,cAAc,IAAI;AAE/D,MAAI,WACF,QAAO;GACL,OAAO;GACP;GACD;AAGH,iBAAe,OAAO,eAAe,aAAa;;;;;;;;;;AClGtD,SAAgB,sBACd,cACS;CACT,MAAM,QAAQ,0BAA0B,YAAY,aAAa;AAGjE,KAAI,OAAO,UAAU,YACnB,QAAO;CAGT,MAAM,EAAE,eAAe;AAGvB,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"} |
| let outvariant = require("outvariant"); | ||
| //#region src/utils/emitAsync.ts | ||
| /** | ||
| * Emits an event on the given emitter but executes | ||
| * the listeners sequentially. This accounts for asynchronous | ||
| * listeners (e.g. those having "sleep" and handling the request). | ||
| */ | ||
| async function emitAsync(emitter, eventName, ...data) { | ||
| const listeners = emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| for (const listener of listeners) await listener.apply(emitter, data); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/globalsRegistry.ts | ||
| var GlobalsRegistry = class { | ||
| #globals = /* @__PURE__ */ new Map(); | ||
| replaceGlobal(key, nextValue) { | ||
| (0, outvariant.invariant)(!this.#globals.has(key), `Failed to replace a global value at "${key}": already replaced.`); | ||
| const match = getDeepPropertyDescriptor(globalThis, key); | ||
| if (typeof match === "undefined") { | ||
| console.warn(`Failed to replace a global value at "${key}": not a global value.`); | ||
| return () => {}; | ||
| } | ||
| Object.defineProperty(globalThis, key, { | ||
| value: nextValue, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| const restoreGlobal = () => { | ||
| if (!this.#globals.has(key)) return; | ||
| if (match.owner === globalThis) Object.defineProperty(match.owner, key, match.descriptor); | ||
| else | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the owner isn't `globalThis`, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(globalThis, key); | ||
| this.#globals.delete(key); | ||
| }; | ||
| this.#globals.set(key, restoreGlobal); | ||
| return restoreGlobal; | ||
| } | ||
| restoreAllGlobals() { | ||
| const errors = []; | ||
| for (const [, restoreGlobal] of this.#globals) try { | ||
| restoreGlobal(); | ||
| } catch (error) { | ||
| if (error instanceof Error) errors.push(error); | ||
| else throw error; | ||
| } | ||
| if (errors.length > 0) throw new AggregateError(errors, "FOO!"); | ||
| } | ||
| }; | ||
| const globalsRegistry = new GlobalsRegistry(); | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| function getDeepPropertyDescriptor(owner, key) { | ||
| let currentOwner = owner; | ||
| let descriptor; | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key); | ||
| if (descriptor) return { | ||
| owner: currentOwner, | ||
| descriptor | ||
| }; | ||
| currentOwner = Object.getPrototypeOf(currentOwner); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/hasConfigurableGlobal.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given global property | ||
| * is defined and is configurable. | ||
| */ | ||
| function hasConfigurableGlobal(propertyName) { | ||
| const match = getDeepPropertyDescriptor(globalThis, propertyName); | ||
| if (typeof match === "undefined") return false; | ||
| const { descriptor } = match; | ||
| if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false; | ||
| if (typeof descriptor.get === "undefined" && descriptor.value == null) return false; | ||
| if (typeof descriptor.set === "undefined" && !descriptor.configurable) { | ||
| console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'emitAsync', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return emitAsync; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'globalsRegistry', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return globalsRegistry; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'hasConfigurableGlobal', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return hasConfigurableGlobal; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=hasConfigurableGlobal-B5o6PHq3.cjs.map |
| {"version":3,"file":"hasConfigurableGlobal-B5o6PHq3.cjs","names":["#globals","errors: Array<Error>","currentOwner: Owner | null","descriptor: PropertyDescriptor | undefined"],"sources":["../../src/utils/emitAsync.ts","../../src/utils/globalsRegistry.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","import { invariant } from 'outvariant'\n\nclass GlobalsRegistry {\n #globals = new Map<keyof typeof globalThis, () => void>()\n\n public replaceGlobal<K extends keyof typeof globalThis>(\n key: K,\n nextValue: (typeof globalThis)[K]\n ): () => void {\n invariant(\n !this.#globals.has(key),\n `Failed to replace a global value at \"${key}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(globalThis, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${key}\": not a global value.`\n )\n return () => {}\n }\n\n Object.defineProperty(globalThis, key, {\n value: nextValue,\n enumerable: true,\n configurable: true,\n })\n\n const restoreGlobal = () => {\n if (!this.#globals.has(key)) {\n return\n }\n\n if (match.owner === globalThis) {\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the owner isn't `globalThis`, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(globalThis, key)\n }\n\n this.#globals.delete(key)\n }\n\n this.#globals.set(key, restoreGlobal)\n\n return restoreGlobal\n }\n\n public restoreAllGlobals(): void {\n const errors: Array<Error> = []\n\n for (const [, restoreGlobal] of this.#globals) {\n try {\n restoreGlobal()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const globalsRegistry = new GlobalsRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: keyof Owner\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n","import { getDeepPropertyDescriptor } from './globalsRegistry'\n\n/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(\n propertyName: keyof typeof globalThis\n): boolean {\n const match = getDeepPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof match === 'undefined') {\n return false\n }\n\n const { descriptor } = match\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;;;;;;AAOA,eAAsB,UAIpB,SACA,WACA,GAAG,MACY;CACf,MAAM,YAAY,QAAQ,UAAU,UAAU;AAE9C,KAAI,UAAU,WAAW,EACvB;AAGF,MAAK,MAAM,YAAY,UACrB,OAAM,SAAS,MAAM,SAAS,KAAK;;;;;ACpBvC,IAAM,kBAAN,MAAsB;CACpB,2BAAW,IAAI,KAA0C;CAEzD,AAAO,cACL,KACA,WACY;AACZ,4BACE,CAAC,MAAKA,QAAS,IAAI,IAAI,EACvB,wCAAwC,IAAI,sBAC7C;EAED,MAAM,QAAQ,0BAA0B,YAAY,IAAI;AAExD,MAAI,OAAO,UAAU,aAAa;AAChC,WAAQ,KACN,wCAAwC,IAAI,wBAC7C;AACD,gBAAa;;AAGf,SAAO,eAAe,YAAY,KAAK;GACrC,OAAO;GACP,YAAY;GACZ,cAAc;GACf,CAAC;EAEF,MAAM,sBAAsB;AAC1B,OAAI,CAAC,MAAKA,QAAS,IAAI,IAAI,CACzB;AAGF,OAAI,MAAM,UAAU,WAClB,QAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;;;;;;;AAOzD,WAAQ,eAAe,YAAY,IAAI;AAGzC,SAAKA,QAAS,OAAO,IAAI;;AAG3B,QAAKA,QAAS,IAAI,KAAK,cAAc;AAErC,SAAO;;CAGT,AAAO,oBAA0B;EAC/B,MAAMC,SAAuB,EAAE;AAE/B,OAAK,MAAM,GAAG,kBAAkB,MAAKD,QACnC,KAAI;AACF,kBAAe;WACR,OAAO;AACd,OAAI,iBAAiB,MACnB,QAAO,KAAK,MAAM;OAElB,OAAM;;AAKZ,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,QAAQ,OAAO;;;AAK9C,MAAa,kBAAkB,IAAI,iBAAiB;;;;;;;AAapD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAIE,eAA6B;CACjC,IAAIC;AAEJ,QAAO,cAAc;AACnB,eAAa,OAAO,yBAAyB,cAAc,IAAI;AAE/D,MAAI,WACF,QAAO;GACL,OAAO;GACP;GACD;AAGH,iBAAe,OAAO,eAAe,aAAa;;;;;;;;;;AClGtD,SAAgB,sBACd,cACS;CACT,MAAM,QAAQ,0BAA0B,YAAY,aAAa;AAGjE,KAAI,OAAO,UAAU,YACnB,QAAO;CAGT,MAAM,EAAE,eAAe;AAGvB,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"} |
| import { n as INTERNAL_REQUEST_ID_HEADER_NAME, r as Interceptor, t as createRequestId } from "./createRequestId-DYCsFHOi.mjs"; | ||
| import { i as FetchResponse, n as setRawRequest, o as RequestController, r as FetchRequest } from "./getRawRequest-BP--nUwn.mjs"; | ||
| import { n as encodeBuffer, r as toArrayBuffer, t as decodeBuffer } from "./bufferUtils-BiiO6HZv.mjs"; | ||
| import { n as globalsRegistry, t as hasConfigurableGlobal } from "./hasConfigurableGlobal-B39W0thh.mjs"; | ||
| import { n as isResponseError, t as handleRequest } from "./handleRequest-CnDA8MVO.mjs"; | ||
| import { invariant } from "outvariant"; | ||
| import { isNodeProcess } from "is-node-process"; | ||
| //#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts | ||
| /** | ||
| * Concatenate two `Uint8Array` buffers. | ||
| */ | ||
| function concatArrayBuffer(left, right) { | ||
| const result = new Uint8Array(left.byteLength + right.byteLength); | ||
| result.set(left, 0); | ||
| result.set(right, left.byteLength); | ||
| return result; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts | ||
| var EventPolyfill = class { | ||
| constructor(type, options) { | ||
| this.NONE = 0; | ||
| this.CAPTURING_PHASE = 1; | ||
| this.AT_TARGET = 2; | ||
| this.BUBBLING_PHASE = 3; | ||
| this.type = ""; | ||
| this.srcElement = null; | ||
| this.currentTarget = null; | ||
| this.eventPhase = 0; | ||
| this.isTrusted = true; | ||
| this.composed = false; | ||
| this.cancelable = true; | ||
| this.defaultPrevented = false; | ||
| this.bubbles = true; | ||
| this.lengthComputable = true; | ||
| this.loaded = 0; | ||
| this.total = 0; | ||
| this.cancelBubble = false; | ||
| this.returnValue = true; | ||
| this.type = type; | ||
| this.target = options?.target || null; | ||
| this.currentTarget = options?.currentTarget || null; | ||
| this.timeStamp = Date.now(); | ||
| } | ||
| composedPath() { | ||
| return []; | ||
| } | ||
| initEvent(type, bubbles, cancelable) { | ||
| this.type = type; | ||
| this.bubbles = !!bubbles; | ||
| this.cancelable = !!cancelable; | ||
| } | ||
| preventDefault() { | ||
| this.defaultPrevented = true; | ||
| } | ||
| stopPropagation() {} | ||
| stopImmediatePropagation() {} | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts | ||
| var ProgressEventPolyfill = class extends EventPolyfill { | ||
| constructor(type, init) { | ||
| super(type); | ||
| this.lengthComputable = init?.lengthComputable || false; | ||
| this.composed = init?.composed || false; | ||
| this.loaded = init?.loaded || 0; | ||
| this.total = init?.total || 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createEvent.ts | ||
| const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined"; | ||
| function createEvent(target, type, init) { | ||
| const progressEvents = [ | ||
| "error", | ||
| "progress", | ||
| "loadstart", | ||
| "loadend", | ||
| "load", | ||
| "timeout", | ||
| "abort" | ||
| ]; | ||
| /** | ||
| * `ProgressEvent` is not supported in React Native. | ||
| * @see https://github.com/mswjs/interceptors/issues/40 | ||
| */ | ||
| const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill; | ||
| return progressEvents.includes(type) ? new ProgressEventClass(type, { | ||
| lengthComputable: true, | ||
| loaded: init?.loaded || 0, | ||
| total: init?.total || 0 | ||
| }) : new EventPolyfill(type, { | ||
| target, | ||
| currentTarget: target | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/findPropertySource.ts | ||
| /** | ||
| * Returns the source object of the given property on the target object | ||
| * (the target itself, any parent in its prototype, or null). | ||
| */ | ||
| function findPropertySource(target, propertyName) { | ||
| if (!(propertyName in target)) return null; | ||
| if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target; | ||
| const prototype = Reflect.getPrototypeOf(target); | ||
| return prototype ? findPropertySource(prototype, propertyName) : null; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/createProxy.ts | ||
| function createProxy(target, options) { | ||
| return new Proxy(target, optionsToProxyHandler(options)); | ||
| } | ||
| function optionsToProxyHandler(options) { | ||
| const { constructorCall, methodCall, getProperty, setProperty } = options; | ||
| const handler = {}; | ||
| if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) { | ||
| const next = Reflect.construct.bind(null, target, args, newTarget); | ||
| return constructorCall.call(newTarget, args, next); | ||
| }; | ||
| handler.set = function(target, propertyName, nextValue) { | ||
| const next = () => { | ||
| const propertySource = findPropertySource(target, propertyName) || target; | ||
| const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName); | ||
| if (typeof ownDescriptors?.set !== "undefined") { | ||
| ownDescriptors.set.apply(target, [nextValue]); | ||
| return true; | ||
| } | ||
| return Reflect.defineProperty(propertySource, propertyName, { | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| value: nextValue | ||
| }); | ||
| }; | ||
| if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next); | ||
| return next(); | ||
| }; | ||
| handler.get = function(target, propertyName, receiver) { | ||
| /** | ||
| * @note Using `Reflect.get()` here causes "TypeError: Illegal invocation". | ||
| */ | ||
| const next = () => target[propertyName]; | ||
| const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next(); | ||
| if (typeof value === "function") return (...args) => { | ||
| const next$1 = value.bind(target, ...args); | ||
| if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1); | ||
| return next$1(); | ||
| }; | ||
| return value; | ||
| }; | ||
| return handler; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts | ||
| function isDomParserSupportedType(type) { | ||
| return [ | ||
| "application/xhtml+xml", | ||
| "application/xml", | ||
| "image/svg+xml", | ||
| "text/html", | ||
| "text/xml" | ||
| ].some((supportedType) => { | ||
| return type.startsWith(supportedType); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/parseJson.ts | ||
| /** | ||
| * Parses a given string into JSON. | ||
| * Gracefully handles invalid JSON by returning `null`. | ||
| */ | ||
| function parseJson(data) { | ||
| try { | ||
| return JSON.parse(data); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createResponse.ts | ||
| /** | ||
| * Creates a Fetch API `Response` instance from the given | ||
| * `XMLHttpRequest` instance and a response body. | ||
| */ | ||
| function createResponse(request, body) { | ||
| return new FetchResponse(FetchResponse.isResponseWithBody(request.status) ? body : null, { | ||
| url: request.responseURL, | ||
| status: request.status, | ||
| statusText: request.statusText, | ||
| headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders()) | ||
| }); | ||
| } | ||
| function createHeadersFromXMLHttpRequestHeaders(headersString) { | ||
| const headers = new Headers(); | ||
| const lines = headersString.split(/[\r\n]+/); | ||
| for (const line of lines) { | ||
| if (line.trim() === "") continue; | ||
| const [name, ...parts] = line.split(": "); | ||
| const value = parts.join(": "); | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts | ||
| /** | ||
| * Return a total byte length of the given request/response body. | ||
| * If the `Content-Length` header is present, it will be used as the byte length. | ||
| */ | ||
| async function getBodyByteLength(input) { | ||
| const explicitContentLength = input.headers.get("content-length"); | ||
| if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength); | ||
| return (await input.arrayBuffer()).byteLength; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts | ||
| const kIsRequestHandled = Symbol("kIsRequestHandled"); | ||
| const IS_NODE = isNodeProcess(); | ||
| const kFetchRequest = Symbol("kFetchRequest"); | ||
| /** | ||
| * An `XMLHttpRequest` instance controller that allows us | ||
| * to handle any given request instance (e.g. responding to it). | ||
| */ | ||
| var XMLHttpRequestController = class { | ||
| constructor(initialRequest, logger) { | ||
| this.initialRequest = initialRequest; | ||
| this.logger = logger; | ||
| this.method = "GET"; | ||
| this.url = null; | ||
| this[kIsRequestHandled] = false; | ||
| this.events = /* @__PURE__ */ new Map(); | ||
| this.uploadEvents = /* @__PURE__ */ new Map(); | ||
| this.requestId = createRequestId(); | ||
| this.requestHeaders = new Headers(); | ||
| this.responseBuffer = new Uint8Array(); | ||
| this.request = createProxy(initialRequest, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "ontimeout": { | ||
| const eventName = propertyName.slice(2); | ||
| /** | ||
| * @note Proxy callbacks to event listeners because JSDOM has trouble | ||
| * translating these properties to callbacks. It seemed to be operating | ||
| * on events exclusively. | ||
| */ | ||
| this.request.addEventListener(eventName, nextValue); | ||
| return invoke(); | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "open": { | ||
| const [method, url] = args; | ||
| if (typeof url === "undefined") { | ||
| this.method = "GET"; | ||
| this.url = toAbsoluteUrl(method); | ||
| } else { | ||
| this.method = method; | ||
| this.url = toAbsoluteUrl(url); | ||
| } | ||
| this.logger = this.logger.extend(`${this.method} ${this.url.href}`); | ||
| this.logger.info("open", this.method, this.url.href); | ||
| return invoke(); | ||
| } | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerEvent(eventName, listener); | ||
| this.logger.info("addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| case "setRequestHeader": { | ||
| const [name, value] = args; | ||
| this.requestHeaders.set(name, value); | ||
| this.logger.info("setRequestHeader", name, value); | ||
| return invoke(); | ||
| } | ||
| case "send": { | ||
| const [body] = args; | ||
| this.request.addEventListener("load", () => { | ||
| if (typeof this.onResponse !== "undefined") { | ||
| const fetchResponse = createResponse( | ||
| this.request, | ||
| /** | ||
| * The `response` property is the right way to read | ||
| * the ambiguous response body, as the request's "responseType" may differ. | ||
| * @see https://xhr.spec.whatwg.org/#the-response-attribute | ||
| */ | ||
| this.request.response | ||
| ); | ||
| this.onResponse.call(this, { | ||
| response: fetchResponse, | ||
| isMockedResponse: this[kIsRequestHandled], | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }); | ||
| } | ||
| }); | ||
| const requestBody = typeof body === "string" ? encodeBuffer(body) : body; | ||
| const fetchRequest = this.toFetchApiRequest(requestBody); | ||
| this[kFetchRequest] = fetchRequest.clone(); | ||
| /** | ||
| * @note Start request handling on the next tick so that the user | ||
| * could add event listeners for "loadend" before the interceptor fires it. | ||
| */ | ||
| queueMicrotask(() => { | ||
| (this.onRequest?.call(this, { | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }) || Promise.resolve()).finally(() => { | ||
| if (!this[kIsRequestHandled]) { | ||
| this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState); | ||
| /** | ||
| * @note Set the intercepted request ID on the original request in Node.js | ||
| * so that if it triggers any other interceptors, they don't attempt | ||
| * to process it once again. | ||
| * | ||
| * For instance, XMLHttpRequest is often implemented via "http.ClientRequest" | ||
| * and we don't want for both XHR and ClientRequest interceptors to | ||
| * handle the same request at the same time (e.g. emit the "response" event twice). | ||
| */ | ||
| if (IS_NODE) this.request.setRequestHeader(INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId); | ||
| return invoke(); | ||
| } | ||
| }); | ||
| }); | ||
| break; | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| } | ||
| }); | ||
| /** | ||
| * Proxy the `.upload` property to gather the event listeners/callbacks. | ||
| */ | ||
| define(this.request, "upload", createProxy(this.request.upload, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "onloadstart": | ||
| case "onprogress": | ||
| case "onaboart": | ||
| case "onerror": | ||
| case "onload": | ||
| case "ontimeout": | ||
| case "onloadend": { | ||
| const eventName = propertyName.slice(2); | ||
| this.registerUploadEvent(eventName, nextValue); | ||
| } | ||
| } | ||
| return invoke(); | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerUploadEvent(eventName, listener); | ||
| this.logger.info("upload.addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| } | ||
| } | ||
| })); | ||
| } | ||
| registerEvent(eventName, listener) { | ||
| const nextEvents = (this.events.get(eventName) || []).concat(listener); | ||
| this.events.set(eventName, nextEvents); | ||
| this.logger.info("registered event \"%s\"", eventName, listener); | ||
| } | ||
| registerUploadEvent(eventName, listener) { | ||
| const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener); | ||
| this.uploadEvents.set(eventName, nextEvents); | ||
| this.logger.info("registered upload event \"%s\"", eventName, listener); | ||
| } | ||
| /** | ||
| * Responds to the current request with the given | ||
| * Fetch API `Response` instance. | ||
| */ | ||
| async respondWith(response) { | ||
| /** | ||
| * @note Since `XMLHttpRequestController` delegates the handling of the responses | ||
| * to the "load" event listener that doesn't distinguish between the mocked and original | ||
| * responses, mark the request that had a mocked response with a corresponding symbol. | ||
| * | ||
| * Mark this request as having a mocked response immediately since | ||
| * calculating request/response total body length is asynchronous. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| /** | ||
| * Dispatch request upload events for requests with a body. | ||
| * @see https://github.com/mswjs/interceptors/issues/573 | ||
| */ | ||
| if (this[kFetchRequest]) { | ||
| const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]); | ||
| this.trigger("loadstart", this.request.upload, { | ||
| loaded: 0, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("progress", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("load", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| } | ||
| this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText); | ||
| define(this.request, "status", response.status); | ||
| define(this.request, "statusText", response.statusText); | ||
| define(this.request, "responseURL", this.url.href); | ||
| this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => { | ||
| this.logger.info("getResponseHeader", args[0]); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning null"); | ||
| return null; | ||
| } | ||
| const headerValue = response.headers.get(args[0]); | ||
| this.logger.info("resolved response header \"%s\" to", args[0], headerValue); | ||
| return headerValue; | ||
| } }); | ||
| this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => { | ||
| this.logger.info("getAllResponseHeaders"); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning empty string"); | ||
| return ""; | ||
| } | ||
| const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => { | ||
| return `${headerName}: ${headerValue}`; | ||
| }).join("\r\n"); | ||
| this.logger.info("resolved all response headers to", allHeaders); | ||
| return allHeaders; | ||
| } }); | ||
| Object.defineProperties(this.request, { | ||
| response: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.response | ||
| }, | ||
| responseText: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseText | ||
| }, | ||
| responseXML: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseXML | ||
| } | ||
| }); | ||
| const totalResponseBodyLength = await getBodyByteLength(response.clone()); | ||
| this.logger.info("calculated response body length", totalResponseBodyLength); | ||
| this.trigger("loadstart", this.request, { | ||
| loaded: 0, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.setReadyState(this.request.HEADERS_RECEIVED); | ||
| this.setReadyState(this.request.LOADING); | ||
| const finalizeResponse = () => { | ||
| this.logger.info("finalizing the mocked response..."); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("load", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| }; | ||
| if (response.body) { | ||
| this.logger.info("mocked response has body, streaming..."); | ||
| const reader = response.body.getReader(); | ||
| const readNextResponseBodyChunk = async () => { | ||
| const { value, done } = await reader.read(); | ||
| if (done) { | ||
| this.logger.info("response body stream done!"); | ||
| finalizeResponse(); | ||
| return; | ||
| } | ||
| if (value) { | ||
| this.logger.info("read response body chunk:", value); | ||
| this.responseBuffer = concatArrayBuffer(this.responseBuffer, value); | ||
| this.trigger("progress", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| } | ||
| readNextResponseBodyChunk(); | ||
| }; | ||
| readNextResponseBodyChunk(); | ||
| } else finalizeResponse(); | ||
| } | ||
| responseBufferToText() { | ||
| return decodeBuffer(this.responseBuffer); | ||
| } | ||
| get response() { | ||
| this.logger.info("getResponse (responseType: %s)", this.request.responseType); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| switch (this.request.responseType) { | ||
| case "json": { | ||
| const responseJson = parseJson(this.responseBufferToText()); | ||
| this.logger.info("resolved response JSON", responseJson); | ||
| return responseJson; | ||
| } | ||
| case "arraybuffer": { | ||
| const arrayBuffer = toArrayBuffer(this.responseBuffer); | ||
| this.logger.info("resolved response ArrayBuffer", arrayBuffer); | ||
| return arrayBuffer; | ||
| } | ||
| case "blob": { | ||
| const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain"; | ||
| const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType }); | ||
| this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType); | ||
| return responseBlob; | ||
| } | ||
| default: { | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText); | ||
| return responseText; | ||
| } | ||
| } | ||
| } | ||
| get responseText() { | ||
| /** | ||
| * Throw when trying to read the response body as text when the | ||
| * "responseType" doesn't expect text. This just respects the spec better. | ||
| * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute | ||
| */ | ||
| invariant(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return ""; | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("getResponseText: \"%s\"", responseText); | ||
| return responseText; | ||
| } | ||
| get responseXML() { | ||
| invariant(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| const contentType = this.request.getResponseHeader("Content-Type") || ""; | ||
| if (typeof DOMParser === "undefined") { | ||
| console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly."); | ||
| return null; | ||
| } | ||
| if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType); | ||
| return null; | ||
| } | ||
| errorWith(error) { | ||
| /** | ||
| * @note Mark this request as handled even if it received a mock error. | ||
| * This prevents the controller from trying to perform this request as-is. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| this.logger.info("responding with an error"); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("error", this.request); | ||
| this.trigger("loadend", this.request); | ||
| } | ||
| /** | ||
| * Transitions this request's `readyState` to the given one. | ||
| */ | ||
| setReadyState(nextReadyState) { | ||
| this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState); | ||
| if (this.request.readyState === nextReadyState) { | ||
| this.logger.info("ready state identical, skipping transition..."); | ||
| return; | ||
| } | ||
| define(this.request, "readyState", nextReadyState); | ||
| this.logger.info("set readyState to: %d", nextReadyState); | ||
| if (nextReadyState !== this.request.UNSENT) { | ||
| this.logger.info("triggering \"readystatechange\" event..."); | ||
| this.trigger("readystatechange", this.request); | ||
| } | ||
| } | ||
| /** | ||
| * Triggers given event on the `XMLHttpRequest` instance. | ||
| */ | ||
| trigger(eventName, target, options) { | ||
| const callback = target[`on${eventName}`]; | ||
| const event = createEvent(target, eventName, options); | ||
| this.logger.info("trigger \"%s\"", eventName, options || ""); | ||
| if (typeof callback === "function") { | ||
| this.logger.info("found a direct \"%s\" callback, calling...", eventName); | ||
| callback.call(target, event); | ||
| } | ||
| const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events; | ||
| for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) { | ||
| this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName); | ||
| listeners.forEach((listener) => listener.call(target, event)); | ||
| } | ||
| } | ||
| /** | ||
| * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance. | ||
| */ | ||
| toFetchApiRequest(body) { | ||
| this.logger.info("converting request to a Fetch API Request..."); | ||
| const resolvedBody = body instanceof Document ? body.documentElement.innerText : body; | ||
| const fetchRequest = new FetchRequest(this.url.href, { | ||
| method: this.method, | ||
| headers: this.requestHeaders, | ||
| credentials: this.request.withCredentials ? "include" : "same-origin", | ||
| body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody | ||
| }); | ||
| define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "append": | ||
| case "set": { | ||
| const [headerName, headerValue] = args; | ||
| this.request.setRequestHeader(headerName, headerValue); | ||
| break; | ||
| } | ||
| case "delete": { | ||
| const [headerName] = args; | ||
| console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`); | ||
| break; | ||
| } | ||
| } | ||
| return invoke(); | ||
| } })); | ||
| setRawRequest(fetchRequest, this.request); | ||
| this.logger.info("converted request to a Fetch API Request!", fetchRequest); | ||
| return fetchRequest; | ||
| } | ||
| }; | ||
| function toAbsoluteUrl(url) { | ||
| /** | ||
| * @note XMLHttpRequest interceptor may run in environments | ||
| * that implement XMLHttpRequest but don't implement "location" | ||
| * (for example, React Native). If that's the case, return the | ||
| * input URL as-is (nothing to be relative to). | ||
| * @see https://github.com/mswjs/msw/issues/1777 | ||
| */ | ||
| if (typeof location === "undefined") return new URL(url); | ||
| return new URL(url.toString(), location.href); | ||
| } | ||
| function define(target, property, value) { | ||
| Reflect.defineProperty(target, property, { | ||
| writable: true, | ||
| enumerable: true, | ||
| value | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts | ||
| /** | ||
| * Create a proxied `XMLHttpRequest` class. | ||
| * The proxied class establishes spies on certain methods, | ||
| * allowing us to intercept requests and respond to them. | ||
| */ | ||
| function createXMLHttpRequestProxy({ emitter, logger }) { | ||
| return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) { | ||
| logger.info("constructed new XMLHttpRequest"); | ||
| const originalRequest = Reflect.construct(target, args, newTarget); | ||
| /** | ||
| * @note Forward prototype descriptors onto the proxied object. | ||
| * XMLHttpRequest is implemented in JSDOM in a way that assigns | ||
| * a bunch of descriptors, like "set responseType()" on the prototype. | ||
| * With this propagation, we make sure that those descriptors trigger | ||
| * when the user operates with the proxied request instance. | ||
| */ | ||
| const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype); | ||
| for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]); | ||
| const xhrRequestController = new XMLHttpRequestController(originalRequest, logger); | ||
| xhrRequestController.onRequest = async function({ request, requestId }) { | ||
| const controller = new RequestController(request, { | ||
| passthrough: () => { | ||
| this.logger.info("no mocked response received, performing request as-is..."); | ||
| }, | ||
| respondWith: async (response) => { | ||
| if (isResponseError(response)) { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| await this.respondWith(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request errored!", { error: reason }); | ||
| if (reason instanceof Error) this.errorWith(reason); | ||
| } | ||
| }); | ||
| this.logger.info("awaiting mocked response..."); | ||
| this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request")); | ||
| await handleRequest({ | ||
| request, | ||
| requestId, | ||
| controller, | ||
| emitter | ||
| }); | ||
| }; | ||
| xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) { | ||
| this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response")); | ||
| emitter.emit("response", { | ||
| response, | ||
| isMockedResponse, | ||
| request, | ||
| requestId | ||
| }); | ||
| }; | ||
| return xhrRequestController.request; | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/index.ts | ||
| var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends Interceptor { | ||
| static { | ||
| this.interceptorSymbol = Symbol("xhr"); | ||
| } | ||
| constructor() { | ||
| super(XMLHttpRequestInterceptor.interceptorSymbol); | ||
| } | ||
| checkEnvironment() { | ||
| return hasConfigurableGlobal("XMLHttpRequest"); | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("patching global XMLHttpRequest..."); | ||
| this.subscriptions.push(globalsRegistry.replaceGlobal("XMLHttpRequest", createXMLHttpRequestProxy({ | ||
| emitter: this.emitter, | ||
| logger: this.logger | ||
| }))); | ||
| logger.info("global XMLHttpRequest patched!", globalThis.XMLHttpRequest.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { XMLHttpRequestInterceptor as t }; | ||
| //# sourceMappingURL=XMLHttpRequest-DaQ73vVt.mjs.map |
| {"version":3,"file":"XMLHttpRequest-DaQ73vVt.mjs","names":["handler: ProxyHandler<T>","next","initialRequest: XMLHttpRequest","logger: Logger"],"sources":["../../src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts","../../src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts","../../src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts","../../src/interceptors/XMLHttpRequest/utils/createEvent.ts","../../src/utils/findPropertySource.ts","../../src/utils/createProxy.ts","../../src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts","../../src/utils/parseJson.ts","../../src/interceptors/XMLHttpRequest/utils/createResponse.ts","../../src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts","../../src/interceptors/XMLHttpRequest/index.ts"],"sourcesContent":["/**\n * Concatenate two `Uint8Array` buffers.\n */\nexport function concatArrayBuffer(\n left: Uint8Array,\n right: Uint8Array\n): Uint8Array {\n const result = new Uint8Array(left.byteLength + right.byteLength)\n result.set(left, 0)\n result.set(right, left.byteLength)\n return result\n}\n","export class EventPolyfill implements Event {\n readonly NONE = 0\n readonly CAPTURING_PHASE = 1\n readonly AT_TARGET = 2\n readonly BUBBLING_PHASE = 3\n\n public type: string = ''\n public srcElement: EventTarget | null = null\n public target: EventTarget | null\n public currentTarget: EventTarget | null = null\n public eventPhase: number = 0\n public timeStamp: number\n public isTrusted: boolean = true\n public composed: boolean = false\n public cancelable: boolean = true\n public defaultPrevented: boolean = false\n public bubbles: boolean = true\n public lengthComputable: boolean = true\n public loaded: number = 0\n public total: number = 0\n\n cancelBubble: boolean = false\n returnValue: boolean = true\n\n constructor(\n type: string,\n options?: { target: EventTarget; currentTarget: EventTarget }\n ) {\n this.type = type\n this.target = options?.target || null\n this.currentTarget = options?.currentTarget || null\n this.timeStamp = Date.now()\n }\n\n public composedPath(): EventTarget[] {\n return []\n }\n\n public initEvent(type: string, bubbles?: boolean, cancelable?: boolean) {\n this.type = type\n this.bubbles = !!bubbles\n this.cancelable = !!cancelable\n }\n\n public preventDefault() {\n this.defaultPrevented = true\n }\n\n public stopPropagation() {}\n public stopImmediatePropagation() {}\n}\n","import { EventPolyfill } from './EventPolyfill'\n\nexport class ProgressEventPolyfill extends EventPolyfill {\n readonly lengthComputable: boolean\n readonly composed: boolean\n readonly loaded: number\n readonly total: number\n\n constructor(type: string, init?: ProgressEventInit) {\n super(type)\n\n this.lengthComputable = init?.lengthComputable || false\n this.composed = init?.composed || false\n this.loaded = init?.loaded || 0\n this.total = init?.total || 0\n }\n}\n","import { EventPolyfill } from '../polyfills/EventPolyfill'\nimport { ProgressEventPolyfill } from '../polyfills/ProgressEventPolyfill'\n\nconst SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined'\n\nexport function createEvent(\n target: XMLHttpRequest | XMLHttpRequestUpload,\n type: string,\n init?: ProgressEventInit\n): EventPolyfill | ProgressEvent {\n const progressEvents = [\n 'error',\n 'progress',\n 'loadstart',\n 'loadend',\n 'load',\n 'timeout',\n 'abort',\n ]\n\n /**\n * `ProgressEvent` is not supported in React Native.\n * @see https://github.com/mswjs/interceptors/issues/40\n */\n const ProgressEventClass = SUPPORTS_PROGRESS_EVENT\n ? ProgressEvent\n : ProgressEventPolyfill\n\n const event = progressEvents.includes(type)\n ? new ProgressEventClass(type, {\n lengthComputable: true,\n loaded: init?.loaded || 0,\n total: init?.total || 0,\n })\n : new EventPolyfill(type, {\n target,\n currentTarget: target,\n })\n\n return event\n}\n","/**\n * Returns the source object of the given property on the target object\n * (the target itself, any parent in its prototype, or null).\n */\nexport function findPropertySource(\n target: object,\n propertyName: string | symbol\n): object | null {\n if (!(propertyName in target)) {\n return null\n }\n\n const hasProperty = Object.prototype.hasOwnProperty.call(target, propertyName)\n if (hasProperty) {\n return target\n }\n\n const prototype = Reflect.getPrototypeOf(target)\n return prototype ? findPropertySource(prototype, propertyName) : null\n}\n","import { findPropertySource } from './findPropertySource'\n\nexport interface ProxyOptions<Target extends Record<string, any>> {\n constructorCall?(args: Array<unknown>, next: NextFunction<Target>): Target\n\n methodCall?<F extends keyof Target>(\n this: Target,\n data: [methodName: F, args: Array<unknown>],\n next: NextFunction<void>\n ): void\n\n setProperty?(\n data: [propertyName: string | symbol, nextValue: unknown],\n next: NextFunction<boolean>\n ): boolean\n\n getProperty?(\n data: [propertyName: string | symbol, receiver: Target],\n next: NextFunction<void>\n ): void\n}\n\nexport type NextFunction<ReturnType> = () => ReturnType\n\nexport function createProxy<Target extends object>(\n target: Target,\n options: ProxyOptions<Target>\n): Target {\n const proxy = new Proxy(target, optionsToProxyHandler(options))\n\n return proxy\n}\n\nfunction optionsToProxyHandler<T extends Record<string, any>>(\n options: ProxyOptions<T>\n): ProxyHandler<T> {\n const { constructorCall, methodCall, getProperty, setProperty } = options\n const handler: ProxyHandler<T> = {}\n\n if (typeof constructorCall !== 'undefined') {\n handler.construct = function (target, args, newTarget) {\n const next = Reflect.construct.bind(null, target as any, args, newTarget)\n return constructorCall.call(newTarget, args, next)\n }\n }\n\n handler.set = function (target, propertyName, nextValue) {\n const next = () => {\n const propertySource = findPropertySource(target, propertyName) || target\n const ownDescriptors = Reflect.getOwnPropertyDescriptor(\n propertySource,\n propertyName\n )\n\n // Respect any custom setters present for this property.\n if (typeof ownDescriptors?.set !== 'undefined') {\n ownDescriptors.set.apply(target, [nextValue])\n return true\n }\n\n // Otherwise, set the property on the source.\n return Reflect.defineProperty(propertySource, propertyName, {\n writable: true,\n enumerable: true,\n configurable: true,\n value: nextValue,\n })\n }\n\n if (typeof setProperty !== 'undefined') {\n return setProperty.call(target, [propertyName, nextValue], next)\n }\n\n return next()\n }\n\n handler.get = function (target, propertyName, receiver) {\n /**\n * @note Using `Reflect.get()` here causes \"TypeError: Illegal invocation\".\n */\n const next = () => target[propertyName as any]\n\n const value =\n typeof getProperty !== 'undefined'\n ? getProperty.call(target, [propertyName, receiver], next)\n : next()\n\n if (typeof value === 'function') {\n return (...args: Array<any>) => {\n const next = value.bind(target, ...args)\n\n if (typeof methodCall !== 'undefined') {\n return methodCall.call(target, [propertyName as any, args], next)\n }\n\n return next()\n }\n }\n\n return value\n }\n\n return handler\n}\n","export function isDomParserSupportedType(\n type: string\n): type is DOMParserSupportedType {\n const supportedTypes: Array<DOMParserSupportedType> = [\n 'application/xhtml+xml',\n 'application/xml',\n 'image/svg+xml',\n 'text/html',\n 'text/xml',\n ]\n return supportedTypes.some((supportedType) => {\n return type.startsWith(supportedType)\n })\n}\n","/**\n * Parses a given string into JSON.\n * Gracefully handles invalid JSON by returning `null`.\n */\nexport function parseJson(data: string): Record<string, unknown> | null {\n try {\n const json = JSON.parse(data)\n return json\n } catch (_) {\n return null\n }\n}\n","import { FetchResponse } from '../../../utils/fetchUtils'\n\n/**\n * Creates a Fetch API `Response` instance from the given\n * `XMLHttpRequest` instance and a response body.\n */\nexport function createResponse(\n request: XMLHttpRequest,\n body: BodyInit | null\n): Response {\n /**\n * Handle XMLHttpRequest responses that must have null as the\n * response body when represented using Fetch API Response.\n * XMLHttpRequest response will always have an empty string\n * as the \"request.response\" in those cases, resulting in an error\n * when constructing a Response instance.\n * @see https://github.com/mswjs/interceptors/issues/379\n */\n const responseBodyOrNull = FetchResponse.isResponseWithBody(request.status)\n ? body\n : null\n\n return new FetchResponse(responseBodyOrNull, {\n url: request.responseURL,\n status: request.status,\n statusText: request.statusText,\n headers: createHeadersFromXMLHttpRequestHeaders(\n request.getAllResponseHeaders()\n ),\n })\n}\n\nfunction createHeadersFromXMLHttpRequestHeaders(headersString: string): Headers {\n const headers = new Headers()\n\n const lines = headersString.split(/[\\r\\n]+/)\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n const [name, ...parts] = line.split(': ')\n const value = parts.join(': ')\n\n headers.append(name, value)\n }\n\n return headers\n}\n","/**\n * Return a total byte length of the given request/response body.\n * If the `Content-Length` header is present, it will be used as the byte length.\n */\nexport async function getBodyByteLength(\n input: Request | Response\n): Promise<number> {\n const explicitContentLength = input.headers.get('content-length')\n\n if (explicitContentLength != null && explicitContentLength !== '') {\n return Number(explicitContentLength)\n }\n\n const buffer = await input.arrayBuffer()\n return buffer.byteLength\n}\n","import { invariant } from 'outvariant'\nimport { isNodeProcess } from 'is-node-process'\nimport type { Logger } from '@open-draft/logger'\nimport { concatArrayBuffer } from './utils/concatArrayBuffer'\nimport { createEvent } from './utils/createEvent'\nimport {\n decodeBuffer,\n encodeBuffer,\n toArrayBuffer,\n} from '../../utils/bufferUtils'\nimport { createProxy } from '../../utils/createProxy'\nimport { isDomParserSupportedType } from './utils/isDomParserSupportedType'\nimport { parseJson } from '../../utils/parseJson'\nimport { createResponse } from './utils/createResponse'\nimport { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'\nimport { createRequestId } from '../../createRequestId'\nimport { getBodyByteLength } from './utils/getBodyByteLength'\nimport { setRawRequest } from '../../getRawRequest'\nimport { FetchRequest } from '../../utils/fetchUtils'\n\nconst kIsRequestHandled = Symbol('kIsRequestHandled')\nconst IS_NODE = isNodeProcess()\nconst kFetchRequest = Symbol('kFetchRequest')\n\n/**\n * An `XMLHttpRequest` instance controller that allows us\n * to handle any given request instance (e.g. responding to it).\n */\nexport class XMLHttpRequestController {\n public request: XMLHttpRequest\n public requestId: string\n public onRequest?: (\n this: XMLHttpRequestController,\n args: {\n request: Request\n requestId: string\n }\n ) => Promise<void>\n public onResponse?: (\n this: XMLHttpRequestController,\n args: {\n response: Response\n isMockedResponse: boolean\n request: Request\n requestId: string\n }\n ) => void;\n\n [kIsRequestHandled]: boolean;\n [kFetchRequest]?: Request\n private method: string = 'GET'\n private url: URL = null as any\n private requestHeaders: Headers\n private responseBuffer: Uint8Array\n private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>\n private uploadEvents: Map<\n keyof XMLHttpRequestEventTargetEventMap,\n Array<Function>\n >\n\n constructor(\n readonly initialRequest: XMLHttpRequest,\n public logger: Logger\n ) {\n this[kIsRequestHandled] = false\n\n this.events = new Map()\n this.uploadEvents = new Map()\n this.requestId = createRequestId()\n this.requestHeaders = new Headers()\n this.responseBuffer = new Uint8Array()\n\n this.request = createProxy(initialRequest, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'ontimeout': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n /**\n * @note Proxy callbacks to event listeners because JSDOM has trouble\n * translating these properties to callbacks. It seemed to be operating\n * on events exclusively.\n */\n this.request.addEventListener(eventName, nextValue as any)\n\n return invoke()\n }\n\n default: {\n return invoke()\n }\n }\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'open': {\n const [method, url] = args as [string, string | undefined]\n\n if (typeof url === 'undefined') {\n this.method = 'GET'\n this.url = toAbsoluteUrl(method)\n } else {\n this.method = method\n this.url = toAbsoluteUrl(url)\n }\n\n this.logger = this.logger.extend(`${this.method} ${this.url.href}`)\n this.logger.info('open', this.method, this.url.href)\n\n return invoke()\n }\n\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n\n this.registerEvent(eventName, listener)\n this.logger.info('addEventListener', eventName, listener)\n\n return invoke()\n }\n\n case 'setRequestHeader': {\n const [name, value] = args as [string, string]\n this.requestHeaders.set(name, value)\n\n this.logger.info('setRequestHeader', name, value)\n\n return invoke()\n }\n\n case 'send': {\n const [body] = args as [\n body?: XMLHttpRequestBodyInit | Document | null,\n ]\n\n this.request.addEventListener('load', () => {\n if (typeof this.onResponse !== 'undefined') {\n // Create a Fetch API Response representation of whichever\n // response this XMLHttpRequest received. Note those may\n // be either a mocked and the original response.\n const fetchResponse = createResponse(\n this.request,\n /**\n * The `response` property is the right way to read\n * the ambiguous response body, as the request's \"responseType\" may differ.\n * @see https://xhr.spec.whatwg.org/#the-response-attribute\n */\n this.request.response\n )\n\n // Notify the consumer about the response.\n this.onResponse.call(this, {\n response: fetchResponse,\n isMockedResponse: this[kIsRequestHandled],\n request: fetchRequest,\n requestId: this.requestId!,\n })\n }\n })\n\n const requestBody =\n typeof body === 'string' ? encodeBuffer(body) : body\n\n // Delegate request handling to the consumer.\n const fetchRequest = this.toFetchApiRequest(requestBody)\n this[kFetchRequest] = fetchRequest.clone()\n\n /**\n * @note Start request handling on the next tick so that the user\n * could add event listeners for \"loadend\" before the interceptor fires it.\n */\n queueMicrotask(() => {\n const onceRequestSettled =\n this.onRequest?.call(this, {\n request: fetchRequest,\n requestId: this.requestId!,\n }) || Promise.resolve()\n\n onceRequestSettled.finally(() => {\n // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.\n if (!this[kIsRequestHandled]) {\n this.logger.info(\n 'request callback settled but request has not been handled (readystate %d), performing as-is...',\n this.request.readyState\n )\n\n /**\n * @note Set the intercepted request ID on the original request in Node.js\n * so that if it triggers any other interceptors, they don't attempt\n * to process it once again.\n *\n * For instance, XMLHttpRequest is often implemented via \"http.ClientRequest\"\n * and we don't want for both XHR and ClientRequest interceptors to\n * handle the same request at the same time (e.g. emit the \"response\" event twice).\n */\n if (IS_NODE) {\n this.request.setRequestHeader(\n INTERNAL_REQUEST_ID_HEADER_NAME,\n this.requestId!\n )\n }\n\n return invoke()\n }\n })\n })\n\n break\n }\n\n default: {\n return invoke()\n }\n }\n },\n })\n\n /**\n * Proxy the `.upload` property to gather the event listeners/callbacks.\n */\n define(\n this.request,\n 'upload',\n createProxy(this.request.upload, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'onloadstart':\n case 'onprogress':\n case 'onaboart':\n case 'onerror':\n case 'onload':\n case 'ontimeout':\n case 'onloadend': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n this.registerUploadEvent(eventName, nextValue as Function)\n }\n }\n\n return invoke()\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n this.registerUploadEvent(eventName, listener)\n this.logger.info('upload.addEventListener', eventName, listener)\n\n return invoke()\n }\n }\n },\n })\n )\n }\n\n private registerEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.events.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.events.set(eventName, nextEvents)\n\n this.logger.info('registered event \"%s\"', eventName, listener)\n }\n\n private registerUploadEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.uploadEvents.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.uploadEvents.set(eventName, nextEvents)\n\n this.logger.info('registered upload event \"%s\"', eventName, listener)\n }\n\n /**\n * Responds to the current request with the given\n * Fetch API `Response` instance.\n */\n public async respondWith(response: Response): Promise<void> {\n /**\n * @note Since `XMLHttpRequestController` delegates the handling of the responses\n * to the \"load\" event listener that doesn't distinguish between the mocked and original\n * responses, mark the request that had a mocked response with a corresponding symbol.\n *\n * Mark this request as having a mocked response immediately since\n * calculating request/response total body length is asynchronous.\n */\n this[kIsRequestHandled] = true\n\n /**\n * Dispatch request upload events for requests with a body.\n * @see https://github.com/mswjs/interceptors/issues/573\n */\n if (this[kFetchRequest]) {\n const totalRequestBodyLength = await getBodyByteLength(\n this[kFetchRequest]\n )\n\n this.trigger('loadstart', this.request.upload, {\n loaded: 0,\n total: totalRequestBodyLength,\n })\n this.trigger('progress', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n this.trigger('load', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n\n this.trigger('loadend', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n }\n\n this.logger.info(\n 'responding with a mocked response: %d %s',\n response.status,\n response.statusText\n )\n\n define(this.request, 'status', response.status)\n define(this.request, 'statusText', response.statusText)\n define(this.request, 'responseURL', this.url.href)\n\n this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {\n apply: (_, __, args: [name: string]) => {\n this.logger.info('getResponseHeader', args[0])\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning null')\n\n // Headers not received yet, nothing to return.\n return null\n }\n\n const headerValue = response.headers.get(args[0])\n this.logger.info(\n 'resolved response header \"%s\" to',\n args[0],\n headerValue\n )\n\n return headerValue\n },\n })\n\n this.request.getAllResponseHeaders = new Proxy(\n this.request.getAllResponseHeaders,\n {\n apply: () => {\n this.logger.info('getAllResponseHeaders')\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning empty string')\n\n // Headers not received yet, nothing to return.\n return ''\n }\n\n const headersList = Array.from(response.headers.entries())\n const allHeaders = headersList\n .map(([headerName, headerValue]) => {\n return `${headerName}: ${headerValue}`\n })\n .join('\\r\\n')\n\n this.logger.info('resolved all response headers to', allHeaders)\n\n return allHeaders\n },\n }\n )\n\n // Update the response getters to resolve against the mocked response.\n Object.defineProperties(this.request, {\n response: {\n enumerable: true,\n configurable: false,\n get: () => this.response,\n },\n responseText: {\n enumerable: true,\n configurable: false,\n get: () => this.responseText,\n },\n responseXML: {\n enumerable: true,\n configurable: false,\n get: () => this.responseXML,\n },\n })\n\n const totalResponseBodyLength = await getBodyByteLength(response.clone())\n\n this.logger.info('calculated response body length', totalResponseBodyLength)\n\n this.trigger('loadstart', this.request, {\n loaded: 0,\n total: totalResponseBodyLength,\n })\n\n this.setReadyState(this.request.HEADERS_RECEIVED)\n this.setReadyState(this.request.LOADING)\n\n const finalizeResponse = () => {\n this.logger.info('finalizing the mocked response...')\n\n this.setReadyState(this.request.DONE)\n\n this.trigger('load', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n\n this.trigger('loadend', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n if (response.body) {\n this.logger.info('mocked response has body, streaming...')\n\n const reader = response.body.getReader()\n\n const readNextResponseBodyChunk = async () => {\n const { value, done } = await reader.read()\n\n if (done) {\n this.logger.info('response body stream done!')\n finalizeResponse()\n return\n }\n\n if (value) {\n this.logger.info('read response body chunk:', value)\n this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)\n\n this.trigger('progress', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n readNextResponseBodyChunk()\n }\n\n readNextResponseBodyChunk()\n } else {\n finalizeResponse()\n }\n }\n\n private responseBufferToText(): string {\n return decodeBuffer(this.responseBuffer)\n }\n\n get response(): unknown {\n this.logger.info(\n 'getResponse (responseType: %s)',\n this.request.responseType\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n switch (this.request.responseType) {\n case 'json': {\n const responseJson = parseJson(this.responseBufferToText())\n this.logger.info('resolved response JSON', responseJson)\n\n return responseJson\n }\n\n case 'arraybuffer': {\n const arrayBuffer = toArrayBuffer(this.responseBuffer)\n this.logger.info('resolved response ArrayBuffer', arrayBuffer)\n\n return arrayBuffer\n }\n\n case 'blob': {\n const mimeType =\n this.request.getResponseHeader('Content-Type') || 'text/plain'\n const responseBlob = new Blob([this.responseBufferToText()], {\n type: mimeType,\n })\n\n this.logger.info(\n 'resolved response Blob (mime type: %s)',\n responseBlob,\n mimeType\n )\n\n return responseBlob\n }\n\n default: {\n const responseText = this.responseBufferToText()\n this.logger.info(\n 'resolving \"%s\" response type as text',\n this.request.responseType,\n responseText\n )\n\n return responseText\n }\n }\n }\n\n get responseText(): string {\n /**\n * Throw when trying to read the response body as text when the\n * \"responseType\" doesn't expect text. This just respects the spec better.\n * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute\n */\n invariant(\n this.request.responseType === '' || this.request.responseType === 'text',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (\n this.request.readyState !== this.request.LOADING &&\n this.request.readyState !== this.request.DONE\n ) {\n return ''\n }\n\n const responseText = this.responseBufferToText()\n this.logger.info('getResponseText: \"%s\"', responseText)\n\n return responseText\n }\n\n get responseXML(): Document | null {\n invariant(\n this.request.responseType === '' ||\n this.request.responseType === 'document',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n const contentType = this.request.getResponseHeader('Content-Type') || ''\n\n if (typeof DOMParser === 'undefined') {\n console.warn(\n 'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'\n )\n return null\n }\n\n if (isDomParserSupportedType(contentType)) {\n return new DOMParser().parseFromString(\n this.responseBufferToText(),\n contentType\n )\n }\n\n return null\n }\n\n public errorWith(error?: Error): void {\n /**\n * @note Mark this request as handled even if it received a mock error.\n * This prevents the controller from trying to perform this request as-is.\n */\n this[kIsRequestHandled] = true\n this.logger.info('responding with an error')\n\n this.setReadyState(this.request.DONE)\n this.trigger('error', this.request)\n this.trigger('loadend', this.request)\n }\n\n /**\n * Transitions this request's `readyState` to the given one.\n */\n private setReadyState(nextReadyState: number): void {\n this.logger.info(\n 'setReadyState: %d -> %d',\n this.request.readyState,\n nextReadyState\n )\n\n if (this.request.readyState === nextReadyState) {\n this.logger.info('ready state identical, skipping transition...')\n return\n }\n\n define(this.request, 'readyState', nextReadyState)\n\n this.logger.info('set readyState to: %d', nextReadyState)\n\n if (nextReadyState !== this.request.UNSENT) {\n this.logger.info('triggering \"readystatechange\" event...')\n\n this.trigger('readystatechange', this.request)\n }\n }\n\n /**\n * Triggers given event on the `XMLHttpRequest` instance.\n */\n private trigger<\n EventName extends keyof (XMLHttpRequestEventTargetEventMap & {\n readystatechange: ProgressEvent<XMLHttpRequestEventTarget>\n }),\n >(\n eventName: EventName,\n target: XMLHttpRequest | XMLHttpRequestUpload,\n options?: ProgressEventInit\n ): void {\n const callback = (target as XMLHttpRequest)[`on${eventName}`]\n const event = createEvent(target, eventName, options)\n\n this.logger.info('trigger \"%s\"', eventName, options || '')\n\n // Invoke direct callbacks.\n if (typeof callback === 'function') {\n this.logger.info('found a direct \"%s\" callback, calling...', eventName)\n callback.call(target as XMLHttpRequest, event)\n }\n\n // Invoke event listeners.\n const events =\n target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events\n\n for (const [registeredEventName, listeners] of events) {\n if (registeredEventName === eventName) {\n this.logger.info(\n 'found %d listener(s) for \"%s\" event, calling...',\n listeners.length,\n eventName\n )\n\n listeners.forEach((listener) => listener.call(target, event))\n }\n }\n }\n\n /**\n * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.\n */\n private toFetchApiRequest(\n body: XMLHttpRequestBodyInit | Document | null | undefined\n ): Request {\n this.logger.info('converting request to a Fetch API Request...')\n\n // If the `Document` is used as the body of this XMLHttpRequest,\n // set its inner text as the Fetch API Request body.\n const resolvedBody =\n body instanceof Document ? body.documentElement.innerText : body\n\n const fetchRequest = new FetchRequest(this.url.href, {\n method: this.method,\n headers: this.requestHeaders,\n /**\n * @see https://xhr.spec.whatwg.org/#cross-origin-credentials\n */\n credentials: this.request.withCredentials ? 'include' : 'same-origin',\n body: ['GET', 'HEAD'].includes(this.method.toUpperCase())\n ? null\n : resolvedBody,\n })\n\n const proxyHeaders = createProxy(fetchRequest.headers, {\n methodCall: ([methodName, args], invoke) => {\n // Forward the latest state of the internal request headers\n // because the interceptor might have modified them\n // without responding to the request.\n switch (methodName) {\n case 'append':\n case 'set': {\n const [headerName, headerValue] = args as [string, string]\n this.request.setRequestHeader(headerName, headerValue)\n break\n }\n\n case 'delete': {\n const [headerName] = args as [string]\n console.warn(\n `XMLHttpRequest: Cannot remove a \"${headerName}\" header from the Fetch API representation of the \"${fetchRequest.method} ${fetchRequest.url}\" request. XMLHttpRequest headers cannot be removed.`\n )\n break\n }\n }\n\n return invoke()\n },\n })\n define(fetchRequest, 'headers', proxyHeaders)\n setRawRequest(fetchRequest, this.request)\n\n this.logger.info('converted request to a Fetch API Request!', fetchRequest)\n\n return fetchRequest\n }\n}\n\nfunction toAbsoluteUrl(url: string | URL): URL {\n /**\n * @note XMLHttpRequest interceptor may run in environments\n * that implement XMLHttpRequest but don't implement \"location\"\n * (for example, React Native). If that's the case, return the\n * input URL as-is (nothing to be relative to).\n * @see https://github.com/mswjs/msw/issues/1777\n */\n if (typeof location === 'undefined') {\n return new URL(url)\n }\n\n return new URL(url.toString(), location.href)\n}\n\nfunction define(\n target: object,\n property: string | symbol,\n value: unknown\n): void {\n Reflect.defineProperty(target, property, {\n // Ensure writable properties to allow redefining readonly properties.\n writable: true,\n enumerable: true,\n value,\n })\n}\n","import type { Logger } from '@open-draft/logger'\nimport { XMLHttpRequestEmitter } from '.'\nimport { RequestController } from '../../RequestController'\nimport { XMLHttpRequestController } from './XMLHttpRequestController'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { isResponseError } from '../../utils/responseUtils'\n\nexport interface XMLHttpRequestProxyOptions {\n emitter: XMLHttpRequestEmitter\n logger: Logger\n}\n\n/**\n * Create a proxied `XMLHttpRequest` class.\n * The proxied class establishes spies on certain methods,\n * allowing us to intercept requests and respond to them.\n */\nexport function createXMLHttpRequestProxy({\n emitter,\n logger,\n}: XMLHttpRequestProxyOptions) {\n const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {\n construct(target, args, newTarget) {\n logger.info('constructed new XMLHttpRequest')\n\n const originalRequest = Reflect.construct(\n target,\n args,\n newTarget\n ) as XMLHttpRequest\n\n /**\n * @note Forward prototype descriptors onto the proxied object.\n * XMLHttpRequest is implemented in JSDOM in a way that assigns\n * a bunch of descriptors, like \"set responseType()\" on the prototype.\n * With this propagation, we make sure that those descriptors trigger\n * when the user operates with the proxied request instance.\n */\n const prototypeDescriptors = Object.getOwnPropertyDescriptors(\n target.prototype\n )\n for (const propertyName in prototypeDescriptors) {\n Reflect.defineProperty(\n originalRequest,\n propertyName,\n prototypeDescriptors[propertyName]\n )\n }\n\n const xhrRequestController = new XMLHttpRequestController(\n originalRequest,\n logger\n )\n\n xhrRequestController.onRequest = async function ({ request, requestId }) {\n const controller = new RequestController(request, {\n passthrough: () => {\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n },\n respondWith: async (response) => {\n if (isResponseError(response)) {\n this.errorWith(new TypeError('Network error'))\n return\n }\n\n await this.respondWith(response)\n },\n errorWith: (reason) => {\n this.logger.info('request errored!', { error: reason })\n\n if (reason instanceof Error) {\n this.errorWith(reason)\n }\n },\n })\n\n this.logger.info('awaiting mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n controller,\n emitter,\n })\n }\n\n xhrRequestController.onResponse = async function ({\n response,\n isMockedResponse,\n request,\n requestId,\n }) {\n this.logger.info(\n 'emitting the \"response\" event for %s listener(s)...',\n emitter.listenerCount('response')\n )\n\n emitter.emit('response', {\n response,\n isMockedResponse,\n request,\n requestId,\n })\n }\n\n // Return the proxied request from the controller\n // so that the controller can react to the consumer's interactions\n // with this request (opening/sending/etc).\n return xhrRequestController.request\n },\n })\n\n return XMLHttpRequestProxy\n}\n","import { Emitter } from 'strict-event-emitter'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>\n\nexport class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {\n static interceptorSymbol = Symbol('xhr')\n\n constructor() {\n super(XMLHttpRequestInterceptor.interceptorSymbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('XMLHttpRequest')\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('patching global XMLHttpRequest...')\n\n this.subscriptions.push(\n globalsRegistry.replaceGlobal(\n 'XMLHttpRequest',\n createXMLHttpRequestProxy({\n emitter: this.emitter,\n logger: this.logger,\n })\n )\n )\n\n logger.info(\n 'global XMLHttpRequest patched!',\n globalThis.XMLHttpRequest.name\n )\n }\n}\n"],"mappings":"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"} |
| const require_createRequestId = require('./createRequestId-DOf8Ktjs.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-B6GjFKAr.cjs'); | ||
| const require_bufferUtils = require('./bufferUtils-Uc0eRItL.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-B5o6PHq3.cjs'); | ||
| const require_handleRequest = require('./handleRequest-BIte-8l8.cjs'); | ||
| let outvariant = require("outvariant"); | ||
| let is_node_process = require("is-node-process"); | ||
| //#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts | ||
| /** | ||
| * Concatenate two `Uint8Array` buffers. | ||
| */ | ||
| function concatArrayBuffer(left, right) { | ||
| const result = new Uint8Array(left.byteLength + right.byteLength); | ||
| result.set(left, 0); | ||
| result.set(right, left.byteLength); | ||
| return result; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts | ||
| var EventPolyfill = class { | ||
| constructor(type, options) { | ||
| this.NONE = 0; | ||
| this.CAPTURING_PHASE = 1; | ||
| this.AT_TARGET = 2; | ||
| this.BUBBLING_PHASE = 3; | ||
| this.type = ""; | ||
| this.srcElement = null; | ||
| this.currentTarget = null; | ||
| this.eventPhase = 0; | ||
| this.isTrusted = true; | ||
| this.composed = false; | ||
| this.cancelable = true; | ||
| this.defaultPrevented = false; | ||
| this.bubbles = true; | ||
| this.lengthComputable = true; | ||
| this.loaded = 0; | ||
| this.total = 0; | ||
| this.cancelBubble = false; | ||
| this.returnValue = true; | ||
| this.type = type; | ||
| this.target = options?.target || null; | ||
| this.currentTarget = options?.currentTarget || null; | ||
| this.timeStamp = Date.now(); | ||
| } | ||
| composedPath() { | ||
| return []; | ||
| } | ||
| initEvent(type, bubbles, cancelable) { | ||
| this.type = type; | ||
| this.bubbles = !!bubbles; | ||
| this.cancelable = !!cancelable; | ||
| } | ||
| preventDefault() { | ||
| this.defaultPrevented = true; | ||
| } | ||
| stopPropagation() {} | ||
| stopImmediatePropagation() {} | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts | ||
| var ProgressEventPolyfill = class extends EventPolyfill { | ||
| constructor(type, init) { | ||
| super(type); | ||
| this.lengthComputable = init?.lengthComputable || false; | ||
| this.composed = init?.composed || false; | ||
| this.loaded = init?.loaded || 0; | ||
| this.total = init?.total || 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createEvent.ts | ||
| const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined"; | ||
| function createEvent(target, type, init) { | ||
| const progressEvents = [ | ||
| "error", | ||
| "progress", | ||
| "loadstart", | ||
| "loadend", | ||
| "load", | ||
| "timeout", | ||
| "abort" | ||
| ]; | ||
| /** | ||
| * `ProgressEvent` is not supported in React Native. | ||
| * @see https://github.com/mswjs/interceptors/issues/40 | ||
| */ | ||
| const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill; | ||
| return progressEvents.includes(type) ? new ProgressEventClass(type, { | ||
| lengthComputable: true, | ||
| loaded: init?.loaded || 0, | ||
| total: init?.total || 0 | ||
| }) : new EventPolyfill(type, { | ||
| target, | ||
| currentTarget: target | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/findPropertySource.ts | ||
| /** | ||
| * Returns the source object of the given property on the target object | ||
| * (the target itself, any parent in its prototype, or null). | ||
| */ | ||
| function findPropertySource(target, propertyName) { | ||
| if (!(propertyName in target)) return null; | ||
| if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target; | ||
| const prototype = Reflect.getPrototypeOf(target); | ||
| return prototype ? findPropertySource(prototype, propertyName) : null; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/createProxy.ts | ||
| function createProxy(target, options) { | ||
| return new Proxy(target, optionsToProxyHandler(options)); | ||
| } | ||
| function optionsToProxyHandler(options) { | ||
| const { constructorCall, methodCall, getProperty, setProperty } = options; | ||
| const handler = {}; | ||
| if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) { | ||
| const next = Reflect.construct.bind(null, target, args, newTarget); | ||
| return constructorCall.call(newTarget, args, next); | ||
| }; | ||
| handler.set = function(target, propertyName, nextValue) { | ||
| const next = () => { | ||
| const propertySource = findPropertySource(target, propertyName) || target; | ||
| const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName); | ||
| if (typeof ownDescriptors?.set !== "undefined") { | ||
| ownDescriptors.set.apply(target, [nextValue]); | ||
| return true; | ||
| } | ||
| return Reflect.defineProperty(propertySource, propertyName, { | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| value: nextValue | ||
| }); | ||
| }; | ||
| if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next); | ||
| return next(); | ||
| }; | ||
| handler.get = function(target, propertyName, receiver) { | ||
| /** | ||
| * @note Using `Reflect.get()` here causes "TypeError: Illegal invocation". | ||
| */ | ||
| const next = () => target[propertyName]; | ||
| const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next(); | ||
| if (typeof value === "function") return (...args) => { | ||
| const next$1 = value.bind(target, ...args); | ||
| if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1); | ||
| return next$1(); | ||
| }; | ||
| return value; | ||
| }; | ||
| return handler; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts | ||
| function isDomParserSupportedType(type) { | ||
| return [ | ||
| "application/xhtml+xml", | ||
| "application/xml", | ||
| "image/svg+xml", | ||
| "text/html", | ||
| "text/xml" | ||
| ].some((supportedType) => { | ||
| return type.startsWith(supportedType); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/parseJson.ts | ||
| /** | ||
| * Parses a given string into JSON. | ||
| * Gracefully handles invalid JSON by returning `null`. | ||
| */ | ||
| function parseJson(data) { | ||
| try { | ||
| return JSON.parse(data); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createResponse.ts | ||
| /** | ||
| * Creates a Fetch API `Response` instance from the given | ||
| * `XMLHttpRequest` instance and a response body. | ||
| */ | ||
| function createResponse(request, body) { | ||
| return new require_getRawRequest.FetchResponse(require_getRawRequest.FetchResponse.isResponseWithBody(request.status) ? body : null, { | ||
| url: request.responseURL, | ||
| status: request.status, | ||
| statusText: request.statusText, | ||
| headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders()) | ||
| }); | ||
| } | ||
| function createHeadersFromXMLHttpRequestHeaders(headersString) { | ||
| const headers = new Headers(); | ||
| const lines = headersString.split(/[\r\n]+/); | ||
| for (const line of lines) { | ||
| if (line.trim() === "") continue; | ||
| const [name, ...parts] = line.split(": "); | ||
| const value = parts.join(": "); | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts | ||
| /** | ||
| * Return a total byte length of the given request/response body. | ||
| * If the `Content-Length` header is present, it will be used as the byte length. | ||
| */ | ||
| async function getBodyByteLength(input) { | ||
| const explicitContentLength = input.headers.get("content-length"); | ||
| if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength); | ||
| return (await input.arrayBuffer()).byteLength; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts | ||
| const kIsRequestHandled = Symbol("kIsRequestHandled"); | ||
| const IS_NODE = (0, is_node_process.isNodeProcess)(); | ||
| const kFetchRequest = Symbol("kFetchRequest"); | ||
| /** | ||
| * An `XMLHttpRequest` instance controller that allows us | ||
| * to handle any given request instance (e.g. responding to it). | ||
| */ | ||
| var XMLHttpRequestController = class { | ||
| constructor(initialRequest, logger) { | ||
| this.initialRequest = initialRequest; | ||
| this.logger = logger; | ||
| this.method = "GET"; | ||
| this.url = null; | ||
| this[kIsRequestHandled] = false; | ||
| this.events = /* @__PURE__ */ new Map(); | ||
| this.uploadEvents = /* @__PURE__ */ new Map(); | ||
| this.requestId = require_createRequestId.createRequestId(); | ||
| this.requestHeaders = new Headers(); | ||
| this.responseBuffer = new Uint8Array(); | ||
| this.request = createProxy(initialRequest, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "ontimeout": { | ||
| const eventName = propertyName.slice(2); | ||
| /** | ||
| * @note Proxy callbacks to event listeners because JSDOM has trouble | ||
| * translating these properties to callbacks. It seemed to be operating | ||
| * on events exclusively. | ||
| */ | ||
| this.request.addEventListener(eventName, nextValue); | ||
| return invoke(); | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "open": { | ||
| const [method, url] = args; | ||
| if (typeof url === "undefined") { | ||
| this.method = "GET"; | ||
| this.url = toAbsoluteUrl(method); | ||
| } else { | ||
| this.method = method; | ||
| this.url = toAbsoluteUrl(url); | ||
| } | ||
| this.logger = this.logger.extend(`${this.method} ${this.url.href}`); | ||
| this.logger.info("open", this.method, this.url.href); | ||
| return invoke(); | ||
| } | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerEvent(eventName, listener); | ||
| this.logger.info("addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| case "setRequestHeader": { | ||
| const [name, value] = args; | ||
| this.requestHeaders.set(name, value); | ||
| this.logger.info("setRequestHeader", name, value); | ||
| return invoke(); | ||
| } | ||
| case "send": { | ||
| const [body] = args; | ||
| this.request.addEventListener("load", () => { | ||
| if (typeof this.onResponse !== "undefined") { | ||
| const fetchResponse = createResponse( | ||
| this.request, | ||
| /** | ||
| * The `response` property is the right way to read | ||
| * the ambiguous response body, as the request's "responseType" may differ. | ||
| * @see https://xhr.spec.whatwg.org/#the-response-attribute | ||
| */ | ||
| this.request.response | ||
| ); | ||
| this.onResponse.call(this, { | ||
| response: fetchResponse, | ||
| isMockedResponse: this[kIsRequestHandled], | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }); | ||
| } | ||
| }); | ||
| const requestBody = typeof body === "string" ? require_bufferUtils.encodeBuffer(body) : body; | ||
| const fetchRequest = this.toFetchApiRequest(requestBody); | ||
| this[kFetchRequest] = fetchRequest.clone(); | ||
| /** | ||
| * @note Start request handling on the next tick so that the user | ||
| * could add event listeners for "loadend" before the interceptor fires it. | ||
| */ | ||
| queueMicrotask(() => { | ||
| (this.onRequest?.call(this, { | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }) || Promise.resolve()).finally(() => { | ||
| if (!this[kIsRequestHandled]) { | ||
| this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState); | ||
| /** | ||
| * @note Set the intercepted request ID on the original request in Node.js | ||
| * so that if it triggers any other interceptors, they don't attempt | ||
| * to process it once again. | ||
| * | ||
| * For instance, XMLHttpRequest is often implemented via "http.ClientRequest" | ||
| * and we don't want for both XHR and ClientRequest interceptors to | ||
| * handle the same request at the same time (e.g. emit the "response" event twice). | ||
| */ | ||
| if (IS_NODE) this.request.setRequestHeader(require_createRequestId.INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId); | ||
| return invoke(); | ||
| } | ||
| }); | ||
| }); | ||
| break; | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| } | ||
| }); | ||
| /** | ||
| * Proxy the `.upload` property to gather the event listeners/callbacks. | ||
| */ | ||
| define(this.request, "upload", createProxy(this.request.upload, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "onloadstart": | ||
| case "onprogress": | ||
| case "onaboart": | ||
| case "onerror": | ||
| case "onload": | ||
| case "ontimeout": | ||
| case "onloadend": { | ||
| const eventName = propertyName.slice(2); | ||
| this.registerUploadEvent(eventName, nextValue); | ||
| } | ||
| } | ||
| return invoke(); | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerUploadEvent(eventName, listener); | ||
| this.logger.info("upload.addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| } | ||
| } | ||
| })); | ||
| } | ||
| registerEvent(eventName, listener) { | ||
| const nextEvents = (this.events.get(eventName) || []).concat(listener); | ||
| this.events.set(eventName, nextEvents); | ||
| this.logger.info("registered event \"%s\"", eventName, listener); | ||
| } | ||
| registerUploadEvent(eventName, listener) { | ||
| const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener); | ||
| this.uploadEvents.set(eventName, nextEvents); | ||
| this.logger.info("registered upload event \"%s\"", eventName, listener); | ||
| } | ||
| /** | ||
| * Responds to the current request with the given | ||
| * Fetch API `Response` instance. | ||
| */ | ||
| async respondWith(response) { | ||
| /** | ||
| * @note Since `XMLHttpRequestController` delegates the handling of the responses | ||
| * to the "load" event listener that doesn't distinguish between the mocked and original | ||
| * responses, mark the request that had a mocked response with a corresponding symbol. | ||
| * | ||
| * Mark this request as having a mocked response immediately since | ||
| * calculating request/response total body length is asynchronous. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| /** | ||
| * Dispatch request upload events for requests with a body. | ||
| * @see https://github.com/mswjs/interceptors/issues/573 | ||
| */ | ||
| if (this[kFetchRequest]) { | ||
| const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]); | ||
| this.trigger("loadstart", this.request.upload, { | ||
| loaded: 0, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("progress", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("load", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| } | ||
| this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText); | ||
| define(this.request, "status", response.status); | ||
| define(this.request, "statusText", response.statusText); | ||
| define(this.request, "responseURL", this.url.href); | ||
| this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => { | ||
| this.logger.info("getResponseHeader", args[0]); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning null"); | ||
| return null; | ||
| } | ||
| const headerValue = response.headers.get(args[0]); | ||
| this.logger.info("resolved response header \"%s\" to", args[0], headerValue); | ||
| return headerValue; | ||
| } }); | ||
| this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => { | ||
| this.logger.info("getAllResponseHeaders"); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning empty string"); | ||
| return ""; | ||
| } | ||
| const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => { | ||
| return `${headerName}: ${headerValue}`; | ||
| }).join("\r\n"); | ||
| this.logger.info("resolved all response headers to", allHeaders); | ||
| return allHeaders; | ||
| } }); | ||
| Object.defineProperties(this.request, { | ||
| response: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.response | ||
| }, | ||
| responseText: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseText | ||
| }, | ||
| responseXML: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseXML | ||
| } | ||
| }); | ||
| const totalResponseBodyLength = await getBodyByteLength(response.clone()); | ||
| this.logger.info("calculated response body length", totalResponseBodyLength); | ||
| this.trigger("loadstart", this.request, { | ||
| loaded: 0, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.setReadyState(this.request.HEADERS_RECEIVED); | ||
| this.setReadyState(this.request.LOADING); | ||
| const finalizeResponse = () => { | ||
| this.logger.info("finalizing the mocked response..."); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("load", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| }; | ||
| if (response.body) { | ||
| this.logger.info("mocked response has body, streaming..."); | ||
| const reader = response.body.getReader(); | ||
| const readNextResponseBodyChunk = async () => { | ||
| const { value, done } = await reader.read(); | ||
| if (done) { | ||
| this.logger.info("response body stream done!"); | ||
| finalizeResponse(); | ||
| return; | ||
| } | ||
| if (value) { | ||
| this.logger.info("read response body chunk:", value); | ||
| this.responseBuffer = concatArrayBuffer(this.responseBuffer, value); | ||
| this.trigger("progress", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| } | ||
| readNextResponseBodyChunk(); | ||
| }; | ||
| readNextResponseBodyChunk(); | ||
| } else finalizeResponse(); | ||
| } | ||
| responseBufferToText() { | ||
| return require_bufferUtils.decodeBuffer(this.responseBuffer); | ||
| } | ||
| get response() { | ||
| this.logger.info("getResponse (responseType: %s)", this.request.responseType); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| switch (this.request.responseType) { | ||
| case "json": { | ||
| const responseJson = parseJson(this.responseBufferToText()); | ||
| this.logger.info("resolved response JSON", responseJson); | ||
| return responseJson; | ||
| } | ||
| case "arraybuffer": { | ||
| const arrayBuffer = require_bufferUtils.toArrayBuffer(this.responseBuffer); | ||
| this.logger.info("resolved response ArrayBuffer", arrayBuffer); | ||
| return arrayBuffer; | ||
| } | ||
| case "blob": { | ||
| const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain"; | ||
| const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType }); | ||
| this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType); | ||
| return responseBlob; | ||
| } | ||
| default: { | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText); | ||
| return responseText; | ||
| } | ||
| } | ||
| } | ||
| get responseText() { | ||
| /** | ||
| * Throw when trying to read the response body as text when the | ||
| * "responseType" doesn't expect text. This just respects the spec better. | ||
| * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute | ||
| */ | ||
| (0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return ""; | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("getResponseText: \"%s\"", responseText); | ||
| return responseText; | ||
| } | ||
| get responseXML() { | ||
| (0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| const contentType = this.request.getResponseHeader("Content-Type") || ""; | ||
| if (typeof DOMParser === "undefined") { | ||
| console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly."); | ||
| return null; | ||
| } | ||
| if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType); | ||
| return null; | ||
| } | ||
| errorWith(error) { | ||
| /** | ||
| * @note Mark this request as handled even if it received a mock error. | ||
| * This prevents the controller from trying to perform this request as-is. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| this.logger.info("responding with an error"); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("error", this.request); | ||
| this.trigger("loadend", this.request); | ||
| } | ||
| /** | ||
| * Transitions this request's `readyState` to the given one. | ||
| */ | ||
| setReadyState(nextReadyState) { | ||
| this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState); | ||
| if (this.request.readyState === nextReadyState) { | ||
| this.logger.info("ready state identical, skipping transition..."); | ||
| return; | ||
| } | ||
| define(this.request, "readyState", nextReadyState); | ||
| this.logger.info("set readyState to: %d", nextReadyState); | ||
| if (nextReadyState !== this.request.UNSENT) { | ||
| this.logger.info("triggering \"readystatechange\" event..."); | ||
| this.trigger("readystatechange", this.request); | ||
| } | ||
| } | ||
| /** | ||
| * Triggers given event on the `XMLHttpRequest` instance. | ||
| */ | ||
| trigger(eventName, target, options) { | ||
| const callback = target[`on${eventName}`]; | ||
| const event = createEvent(target, eventName, options); | ||
| this.logger.info("trigger \"%s\"", eventName, options || ""); | ||
| if (typeof callback === "function") { | ||
| this.logger.info("found a direct \"%s\" callback, calling...", eventName); | ||
| callback.call(target, event); | ||
| } | ||
| const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events; | ||
| for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) { | ||
| this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName); | ||
| listeners.forEach((listener) => listener.call(target, event)); | ||
| } | ||
| } | ||
| /** | ||
| * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance. | ||
| */ | ||
| toFetchApiRequest(body) { | ||
| this.logger.info("converting request to a Fetch API Request..."); | ||
| const resolvedBody = body instanceof Document ? body.documentElement.innerText : body; | ||
| const fetchRequest = new require_getRawRequest.FetchRequest(this.url.href, { | ||
| method: this.method, | ||
| headers: this.requestHeaders, | ||
| credentials: this.request.withCredentials ? "include" : "same-origin", | ||
| body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody | ||
| }); | ||
| define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "append": | ||
| case "set": { | ||
| const [headerName, headerValue] = args; | ||
| this.request.setRequestHeader(headerName, headerValue); | ||
| break; | ||
| } | ||
| case "delete": { | ||
| const [headerName] = args; | ||
| console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`); | ||
| break; | ||
| } | ||
| } | ||
| return invoke(); | ||
| } })); | ||
| require_getRawRequest.setRawRequest(fetchRequest, this.request); | ||
| this.logger.info("converted request to a Fetch API Request!", fetchRequest); | ||
| return fetchRequest; | ||
| } | ||
| }; | ||
| function toAbsoluteUrl(url) { | ||
| /** | ||
| * @note XMLHttpRequest interceptor may run in environments | ||
| * that implement XMLHttpRequest but don't implement "location" | ||
| * (for example, React Native). If that's the case, return the | ||
| * input URL as-is (nothing to be relative to). | ||
| * @see https://github.com/mswjs/msw/issues/1777 | ||
| */ | ||
| if (typeof location === "undefined") return new URL(url); | ||
| return new URL(url.toString(), location.href); | ||
| } | ||
| function define(target, property, value) { | ||
| Reflect.defineProperty(target, property, { | ||
| writable: true, | ||
| enumerable: true, | ||
| value | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts | ||
| /** | ||
| * Create a proxied `XMLHttpRequest` class. | ||
| * The proxied class establishes spies on certain methods, | ||
| * allowing us to intercept requests and respond to them. | ||
| */ | ||
| function createXMLHttpRequestProxy({ emitter, logger }) { | ||
| return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) { | ||
| logger.info("constructed new XMLHttpRequest"); | ||
| const originalRequest = Reflect.construct(target, args, newTarget); | ||
| /** | ||
| * @note Forward prototype descriptors onto the proxied object. | ||
| * XMLHttpRequest is implemented in JSDOM in a way that assigns | ||
| * a bunch of descriptors, like "set responseType()" on the prototype. | ||
| * With this propagation, we make sure that those descriptors trigger | ||
| * when the user operates with the proxied request instance. | ||
| */ | ||
| const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype); | ||
| for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]); | ||
| const xhrRequestController = new XMLHttpRequestController(originalRequest, logger); | ||
| xhrRequestController.onRequest = async function({ request, requestId }) { | ||
| const controller = new require_getRawRequest.RequestController(request, { | ||
| passthrough: () => { | ||
| this.logger.info("no mocked response received, performing request as-is..."); | ||
| }, | ||
| respondWith: async (response) => { | ||
| if (require_handleRequest.isResponseError(response)) { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| await this.respondWith(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request errored!", { error: reason }); | ||
| if (reason instanceof Error) this.errorWith(reason); | ||
| } | ||
| }); | ||
| this.logger.info("awaiting mocked response..."); | ||
| this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request")); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId, | ||
| controller, | ||
| emitter | ||
| }); | ||
| }; | ||
| xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) { | ||
| this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response")); | ||
| emitter.emit("response", { | ||
| response, | ||
| isMockedResponse, | ||
| request, | ||
| requestId | ||
| }); | ||
| }; | ||
| return xhrRequestController.request; | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/index.ts | ||
| var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends require_createRequestId.Interceptor { | ||
| static { | ||
| this.interceptorSymbol = Symbol("xhr"); | ||
| } | ||
| constructor() { | ||
| super(XMLHttpRequestInterceptor.interceptorSymbol); | ||
| } | ||
| checkEnvironment() { | ||
| return require_hasConfigurableGlobal.hasConfigurableGlobal("XMLHttpRequest"); | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("patching global XMLHttpRequest..."); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.globalsRegistry.replaceGlobal("XMLHttpRequest", createXMLHttpRequestProxy({ | ||
| emitter: this.emitter, | ||
| logger: this.logger | ||
| }))); | ||
| logger.info("global XMLHttpRequest patched!", globalThis.XMLHttpRequest.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'XMLHttpRequestInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return XMLHttpRequestInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=XMLHttpRequest-DNly-bu0.cjs.map |
| {"version":3,"file":"XMLHttpRequest-DNly-bu0.cjs","names":["handler: ProxyHandler<T>","next","FetchResponse","initialRequest: XMLHttpRequest","logger: Logger","createRequestId","encodeBuffer","INTERNAL_REQUEST_ID_HEADER_NAME","decodeBuffer","toArrayBuffer","FetchRequest","RequestController","isResponseError","handleRequest","Interceptor","hasConfigurableGlobal","globalsRegistry"],"sources":["../../src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts","../../src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts","../../src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts","../../src/interceptors/XMLHttpRequest/utils/createEvent.ts","../../src/utils/findPropertySource.ts","../../src/utils/createProxy.ts","../../src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts","../../src/utils/parseJson.ts","../../src/interceptors/XMLHttpRequest/utils/createResponse.ts","../../src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts","../../src/interceptors/XMLHttpRequest/index.ts"],"sourcesContent":["/**\n * Concatenate two `Uint8Array` buffers.\n */\nexport function concatArrayBuffer(\n left: Uint8Array,\n right: Uint8Array\n): Uint8Array {\n const result = new Uint8Array(left.byteLength + right.byteLength)\n result.set(left, 0)\n result.set(right, left.byteLength)\n return result\n}\n","export class EventPolyfill implements Event {\n readonly NONE = 0\n readonly CAPTURING_PHASE = 1\n readonly AT_TARGET = 2\n readonly BUBBLING_PHASE = 3\n\n public type: string = ''\n public srcElement: EventTarget | null = null\n public target: EventTarget | null\n public currentTarget: EventTarget | null = null\n public eventPhase: number = 0\n public timeStamp: number\n public isTrusted: boolean = true\n public composed: boolean = false\n public cancelable: boolean = true\n public defaultPrevented: boolean = false\n public bubbles: boolean = true\n public lengthComputable: boolean = true\n public loaded: number = 0\n public total: number = 0\n\n cancelBubble: boolean = false\n returnValue: boolean = true\n\n constructor(\n type: string,\n options?: { target: EventTarget; currentTarget: EventTarget }\n ) {\n this.type = type\n this.target = options?.target || null\n this.currentTarget = options?.currentTarget || null\n this.timeStamp = Date.now()\n }\n\n public composedPath(): EventTarget[] {\n return []\n }\n\n public initEvent(type: string, bubbles?: boolean, cancelable?: boolean) {\n this.type = type\n this.bubbles = !!bubbles\n this.cancelable = !!cancelable\n }\n\n public preventDefault() {\n this.defaultPrevented = true\n }\n\n public stopPropagation() {}\n public stopImmediatePropagation() {}\n}\n","import { EventPolyfill } from './EventPolyfill'\n\nexport class ProgressEventPolyfill extends EventPolyfill {\n readonly lengthComputable: boolean\n readonly composed: boolean\n readonly loaded: number\n readonly total: number\n\n constructor(type: string, init?: ProgressEventInit) {\n super(type)\n\n this.lengthComputable = init?.lengthComputable || false\n this.composed = init?.composed || false\n this.loaded = init?.loaded || 0\n this.total = init?.total || 0\n }\n}\n","import { EventPolyfill } from '../polyfills/EventPolyfill'\nimport { ProgressEventPolyfill } from '../polyfills/ProgressEventPolyfill'\n\nconst SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined'\n\nexport function createEvent(\n target: XMLHttpRequest | XMLHttpRequestUpload,\n type: string,\n init?: ProgressEventInit\n): EventPolyfill | ProgressEvent {\n const progressEvents = [\n 'error',\n 'progress',\n 'loadstart',\n 'loadend',\n 'load',\n 'timeout',\n 'abort',\n ]\n\n /**\n * `ProgressEvent` is not supported in React Native.\n * @see https://github.com/mswjs/interceptors/issues/40\n */\n const ProgressEventClass = SUPPORTS_PROGRESS_EVENT\n ? ProgressEvent\n : ProgressEventPolyfill\n\n const event = progressEvents.includes(type)\n ? new ProgressEventClass(type, {\n lengthComputable: true,\n loaded: init?.loaded || 0,\n total: init?.total || 0,\n })\n : new EventPolyfill(type, {\n target,\n currentTarget: target,\n })\n\n return event\n}\n","/**\n * Returns the source object of the given property on the target object\n * (the target itself, any parent in its prototype, or null).\n */\nexport function findPropertySource(\n target: object,\n propertyName: string | symbol\n): object | null {\n if (!(propertyName in target)) {\n return null\n }\n\n const hasProperty = Object.prototype.hasOwnProperty.call(target, propertyName)\n if (hasProperty) {\n return target\n }\n\n const prototype = Reflect.getPrototypeOf(target)\n return prototype ? findPropertySource(prototype, propertyName) : null\n}\n","import { findPropertySource } from './findPropertySource'\n\nexport interface ProxyOptions<Target extends Record<string, any>> {\n constructorCall?(args: Array<unknown>, next: NextFunction<Target>): Target\n\n methodCall?<F extends keyof Target>(\n this: Target,\n data: [methodName: F, args: Array<unknown>],\n next: NextFunction<void>\n ): void\n\n setProperty?(\n data: [propertyName: string | symbol, nextValue: unknown],\n next: NextFunction<boolean>\n ): boolean\n\n getProperty?(\n data: [propertyName: string | symbol, receiver: Target],\n next: NextFunction<void>\n ): void\n}\n\nexport type NextFunction<ReturnType> = () => ReturnType\n\nexport function createProxy<Target extends object>(\n target: Target,\n options: ProxyOptions<Target>\n): Target {\n const proxy = new Proxy(target, optionsToProxyHandler(options))\n\n return proxy\n}\n\nfunction optionsToProxyHandler<T extends Record<string, any>>(\n options: ProxyOptions<T>\n): ProxyHandler<T> {\n const { constructorCall, methodCall, getProperty, setProperty } = options\n const handler: ProxyHandler<T> = {}\n\n if (typeof constructorCall !== 'undefined') {\n handler.construct = function (target, args, newTarget) {\n const next = Reflect.construct.bind(null, target as any, args, newTarget)\n return constructorCall.call(newTarget, args, next)\n }\n }\n\n handler.set = function (target, propertyName, nextValue) {\n const next = () => {\n const propertySource = findPropertySource(target, propertyName) || target\n const ownDescriptors = Reflect.getOwnPropertyDescriptor(\n propertySource,\n propertyName\n )\n\n // Respect any custom setters present for this property.\n if (typeof ownDescriptors?.set !== 'undefined') {\n ownDescriptors.set.apply(target, [nextValue])\n return true\n }\n\n // Otherwise, set the property on the source.\n return Reflect.defineProperty(propertySource, propertyName, {\n writable: true,\n enumerable: true,\n configurable: true,\n value: nextValue,\n })\n }\n\n if (typeof setProperty !== 'undefined') {\n return setProperty.call(target, [propertyName, nextValue], next)\n }\n\n return next()\n }\n\n handler.get = function (target, propertyName, receiver) {\n /**\n * @note Using `Reflect.get()` here causes \"TypeError: Illegal invocation\".\n */\n const next = () => target[propertyName as any]\n\n const value =\n typeof getProperty !== 'undefined'\n ? getProperty.call(target, [propertyName, receiver], next)\n : next()\n\n if (typeof value === 'function') {\n return (...args: Array<any>) => {\n const next = value.bind(target, ...args)\n\n if (typeof methodCall !== 'undefined') {\n return methodCall.call(target, [propertyName as any, args], next)\n }\n\n return next()\n }\n }\n\n return value\n }\n\n return handler\n}\n","export function isDomParserSupportedType(\n type: string\n): type is DOMParserSupportedType {\n const supportedTypes: Array<DOMParserSupportedType> = [\n 'application/xhtml+xml',\n 'application/xml',\n 'image/svg+xml',\n 'text/html',\n 'text/xml',\n ]\n return supportedTypes.some((supportedType) => {\n return type.startsWith(supportedType)\n })\n}\n","/**\n * Parses a given string into JSON.\n * Gracefully handles invalid JSON by returning `null`.\n */\nexport function parseJson(data: string): Record<string, unknown> | null {\n try {\n const json = JSON.parse(data)\n return json\n } catch (_) {\n return null\n }\n}\n","import { FetchResponse } from '../../../utils/fetchUtils'\n\n/**\n * Creates a Fetch API `Response` instance from the given\n * `XMLHttpRequest` instance and a response body.\n */\nexport function createResponse(\n request: XMLHttpRequest,\n body: BodyInit | null\n): Response {\n /**\n * Handle XMLHttpRequest responses that must have null as the\n * response body when represented using Fetch API Response.\n * XMLHttpRequest response will always have an empty string\n * as the \"request.response\" in those cases, resulting in an error\n * when constructing a Response instance.\n * @see https://github.com/mswjs/interceptors/issues/379\n */\n const responseBodyOrNull = FetchResponse.isResponseWithBody(request.status)\n ? body\n : null\n\n return new FetchResponse(responseBodyOrNull, {\n url: request.responseURL,\n status: request.status,\n statusText: request.statusText,\n headers: createHeadersFromXMLHttpRequestHeaders(\n request.getAllResponseHeaders()\n ),\n })\n}\n\nfunction createHeadersFromXMLHttpRequestHeaders(headersString: string): Headers {\n const headers = new Headers()\n\n const lines = headersString.split(/[\\r\\n]+/)\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n const [name, ...parts] = line.split(': ')\n const value = parts.join(': ')\n\n headers.append(name, value)\n }\n\n return headers\n}\n","/**\n * Return a total byte length of the given request/response body.\n * If the `Content-Length` header is present, it will be used as the byte length.\n */\nexport async function getBodyByteLength(\n input: Request | Response\n): Promise<number> {\n const explicitContentLength = input.headers.get('content-length')\n\n if (explicitContentLength != null && explicitContentLength !== '') {\n return Number(explicitContentLength)\n }\n\n const buffer = await input.arrayBuffer()\n return buffer.byteLength\n}\n","import { invariant } from 'outvariant'\nimport { isNodeProcess } from 'is-node-process'\nimport type { Logger } from '@open-draft/logger'\nimport { concatArrayBuffer } from './utils/concatArrayBuffer'\nimport { createEvent } from './utils/createEvent'\nimport {\n decodeBuffer,\n encodeBuffer,\n toArrayBuffer,\n} from '../../utils/bufferUtils'\nimport { createProxy } from '../../utils/createProxy'\nimport { isDomParserSupportedType } from './utils/isDomParserSupportedType'\nimport { parseJson } from '../../utils/parseJson'\nimport { createResponse } from './utils/createResponse'\nimport { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'\nimport { createRequestId } from '../../createRequestId'\nimport { getBodyByteLength } from './utils/getBodyByteLength'\nimport { setRawRequest } from '../../getRawRequest'\nimport { FetchRequest } from '../../utils/fetchUtils'\n\nconst kIsRequestHandled = Symbol('kIsRequestHandled')\nconst IS_NODE = isNodeProcess()\nconst kFetchRequest = Symbol('kFetchRequest')\n\n/**\n * An `XMLHttpRequest` instance controller that allows us\n * to handle any given request instance (e.g. responding to it).\n */\nexport class XMLHttpRequestController {\n public request: XMLHttpRequest\n public requestId: string\n public onRequest?: (\n this: XMLHttpRequestController,\n args: {\n request: Request\n requestId: string\n }\n ) => Promise<void>\n public onResponse?: (\n this: XMLHttpRequestController,\n args: {\n response: Response\n isMockedResponse: boolean\n request: Request\n requestId: string\n }\n ) => void;\n\n [kIsRequestHandled]: boolean;\n [kFetchRequest]?: Request\n private method: string = 'GET'\n private url: URL = null as any\n private requestHeaders: Headers\n private responseBuffer: Uint8Array\n private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>\n private uploadEvents: Map<\n keyof XMLHttpRequestEventTargetEventMap,\n Array<Function>\n >\n\n constructor(\n readonly initialRequest: XMLHttpRequest,\n public logger: Logger\n ) {\n this[kIsRequestHandled] = false\n\n this.events = new Map()\n this.uploadEvents = new Map()\n this.requestId = createRequestId()\n this.requestHeaders = new Headers()\n this.responseBuffer = new Uint8Array()\n\n this.request = createProxy(initialRequest, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'ontimeout': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n /**\n * @note Proxy callbacks to event listeners because JSDOM has trouble\n * translating these properties to callbacks. It seemed to be operating\n * on events exclusively.\n */\n this.request.addEventListener(eventName, nextValue as any)\n\n return invoke()\n }\n\n default: {\n return invoke()\n }\n }\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'open': {\n const [method, url] = args as [string, string | undefined]\n\n if (typeof url === 'undefined') {\n this.method = 'GET'\n this.url = toAbsoluteUrl(method)\n } else {\n this.method = method\n this.url = toAbsoluteUrl(url)\n }\n\n this.logger = this.logger.extend(`${this.method} ${this.url.href}`)\n this.logger.info('open', this.method, this.url.href)\n\n return invoke()\n }\n\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n\n this.registerEvent(eventName, listener)\n this.logger.info('addEventListener', eventName, listener)\n\n return invoke()\n }\n\n case 'setRequestHeader': {\n const [name, value] = args as [string, string]\n this.requestHeaders.set(name, value)\n\n this.logger.info('setRequestHeader', name, value)\n\n return invoke()\n }\n\n case 'send': {\n const [body] = args as [\n body?: XMLHttpRequestBodyInit | Document | null,\n ]\n\n this.request.addEventListener('load', () => {\n if (typeof this.onResponse !== 'undefined') {\n // Create a Fetch API Response representation of whichever\n // response this XMLHttpRequest received. Note those may\n // be either a mocked and the original response.\n const fetchResponse = createResponse(\n this.request,\n /**\n * The `response` property is the right way to read\n * the ambiguous response body, as the request's \"responseType\" may differ.\n * @see https://xhr.spec.whatwg.org/#the-response-attribute\n */\n this.request.response\n )\n\n // Notify the consumer about the response.\n this.onResponse.call(this, {\n response: fetchResponse,\n isMockedResponse: this[kIsRequestHandled],\n request: fetchRequest,\n requestId: this.requestId!,\n })\n }\n })\n\n const requestBody =\n typeof body === 'string' ? encodeBuffer(body) : body\n\n // Delegate request handling to the consumer.\n const fetchRequest = this.toFetchApiRequest(requestBody)\n this[kFetchRequest] = fetchRequest.clone()\n\n /**\n * @note Start request handling on the next tick so that the user\n * could add event listeners for \"loadend\" before the interceptor fires it.\n */\n queueMicrotask(() => {\n const onceRequestSettled =\n this.onRequest?.call(this, {\n request: fetchRequest,\n requestId: this.requestId!,\n }) || Promise.resolve()\n\n onceRequestSettled.finally(() => {\n // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.\n if (!this[kIsRequestHandled]) {\n this.logger.info(\n 'request callback settled but request has not been handled (readystate %d), performing as-is...',\n this.request.readyState\n )\n\n /**\n * @note Set the intercepted request ID on the original request in Node.js\n * so that if it triggers any other interceptors, they don't attempt\n * to process it once again.\n *\n * For instance, XMLHttpRequest is often implemented via \"http.ClientRequest\"\n * and we don't want for both XHR and ClientRequest interceptors to\n * handle the same request at the same time (e.g. emit the \"response\" event twice).\n */\n if (IS_NODE) {\n this.request.setRequestHeader(\n INTERNAL_REQUEST_ID_HEADER_NAME,\n this.requestId!\n )\n }\n\n return invoke()\n }\n })\n })\n\n break\n }\n\n default: {\n return invoke()\n }\n }\n },\n })\n\n /**\n * Proxy the `.upload` property to gather the event listeners/callbacks.\n */\n define(\n this.request,\n 'upload',\n createProxy(this.request.upload, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'onloadstart':\n case 'onprogress':\n case 'onaboart':\n case 'onerror':\n case 'onload':\n case 'ontimeout':\n case 'onloadend': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n this.registerUploadEvent(eventName, nextValue as Function)\n }\n }\n\n return invoke()\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n this.registerUploadEvent(eventName, listener)\n this.logger.info('upload.addEventListener', eventName, listener)\n\n return invoke()\n }\n }\n },\n })\n )\n }\n\n private registerEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.events.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.events.set(eventName, nextEvents)\n\n this.logger.info('registered event \"%s\"', eventName, listener)\n }\n\n private registerUploadEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.uploadEvents.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.uploadEvents.set(eventName, nextEvents)\n\n this.logger.info('registered upload event \"%s\"', eventName, listener)\n }\n\n /**\n * Responds to the current request with the given\n * Fetch API `Response` instance.\n */\n public async respondWith(response: Response): Promise<void> {\n /**\n * @note Since `XMLHttpRequestController` delegates the handling of the responses\n * to the \"load\" event listener that doesn't distinguish between the mocked and original\n * responses, mark the request that had a mocked response with a corresponding symbol.\n *\n * Mark this request as having a mocked response immediately since\n * calculating request/response total body length is asynchronous.\n */\n this[kIsRequestHandled] = true\n\n /**\n * Dispatch request upload events for requests with a body.\n * @see https://github.com/mswjs/interceptors/issues/573\n */\n if (this[kFetchRequest]) {\n const totalRequestBodyLength = await getBodyByteLength(\n this[kFetchRequest]\n )\n\n this.trigger('loadstart', this.request.upload, {\n loaded: 0,\n total: totalRequestBodyLength,\n })\n this.trigger('progress', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n this.trigger('load', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n\n this.trigger('loadend', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n }\n\n this.logger.info(\n 'responding with a mocked response: %d %s',\n response.status,\n response.statusText\n )\n\n define(this.request, 'status', response.status)\n define(this.request, 'statusText', response.statusText)\n define(this.request, 'responseURL', this.url.href)\n\n this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {\n apply: (_, __, args: [name: string]) => {\n this.logger.info('getResponseHeader', args[0])\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning null')\n\n // Headers not received yet, nothing to return.\n return null\n }\n\n const headerValue = response.headers.get(args[0])\n this.logger.info(\n 'resolved response header \"%s\" to',\n args[0],\n headerValue\n )\n\n return headerValue\n },\n })\n\n this.request.getAllResponseHeaders = new Proxy(\n this.request.getAllResponseHeaders,\n {\n apply: () => {\n this.logger.info('getAllResponseHeaders')\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning empty string')\n\n // Headers not received yet, nothing to return.\n return ''\n }\n\n const headersList = Array.from(response.headers.entries())\n const allHeaders = headersList\n .map(([headerName, headerValue]) => {\n return `${headerName}: ${headerValue}`\n })\n .join('\\r\\n')\n\n this.logger.info('resolved all response headers to', allHeaders)\n\n return allHeaders\n },\n }\n )\n\n // Update the response getters to resolve against the mocked response.\n Object.defineProperties(this.request, {\n response: {\n enumerable: true,\n configurable: false,\n get: () => this.response,\n },\n responseText: {\n enumerable: true,\n configurable: false,\n get: () => this.responseText,\n },\n responseXML: {\n enumerable: true,\n configurable: false,\n get: () => this.responseXML,\n },\n })\n\n const totalResponseBodyLength = await getBodyByteLength(response.clone())\n\n this.logger.info('calculated response body length', totalResponseBodyLength)\n\n this.trigger('loadstart', this.request, {\n loaded: 0,\n total: totalResponseBodyLength,\n })\n\n this.setReadyState(this.request.HEADERS_RECEIVED)\n this.setReadyState(this.request.LOADING)\n\n const finalizeResponse = () => {\n this.logger.info('finalizing the mocked response...')\n\n this.setReadyState(this.request.DONE)\n\n this.trigger('load', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n\n this.trigger('loadend', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n if (response.body) {\n this.logger.info('mocked response has body, streaming...')\n\n const reader = response.body.getReader()\n\n const readNextResponseBodyChunk = async () => {\n const { value, done } = await reader.read()\n\n if (done) {\n this.logger.info('response body stream done!')\n finalizeResponse()\n return\n }\n\n if (value) {\n this.logger.info('read response body chunk:', value)\n this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)\n\n this.trigger('progress', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n readNextResponseBodyChunk()\n }\n\n readNextResponseBodyChunk()\n } else {\n finalizeResponse()\n }\n }\n\n private responseBufferToText(): string {\n return decodeBuffer(this.responseBuffer)\n }\n\n get response(): unknown {\n this.logger.info(\n 'getResponse (responseType: %s)',\n this.request.responseType\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n switch (this.request.responseType) {\n case 'json': {\n const responseJson = parseJson(this.responseBufferToText())\n this.logger.info('resolved response JSON', responseJson)\n\n return responseJson\n }\n\n case 'arraybuffer': {\n const arrayBuffer = toArrayBuffer(this.responseBuffer)\n this.logger.info('resolved response ArrayBuffer', arrayBuffer)\n\n return arrayBuffer\n }\n\n case 'blob': {\n const mimeType =\n this.request.getResponseHeader('Content-Type') || 'text/plain'\n const responseBlob = new Blob([this.responseBufferToText()], {\n type: mimeType,\n })\n\n this.logger.info(\n 'resolved response Blob (mime type: %s)',\n responseBlob,\n mimeType\n )\n\n return responseBlob\n }\n\n default: {\n const responseText = this.responseBufferToText()\n this.logger.info(\n 'resolving \"%s\" response type as text',\n this.request.responseType,\n responseText\n )\n\n return responseText\n }\n }\n }\n\n get responseText(): string {\n /**\n * Throw when trying to read the response body as text when the\n * \"responseType\" doesn't expect text. This just respects the spec better.\n * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute\n */\n invariant(\n this.request.responseType === '' || this.request.responseType === 'text',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (\n this.request.readyState !== this.request.LOADING &&\n this.request.readyState !== this.request.DONE\n ) {\n return ''\n }\n\n const responseText = this.responseBufferToText()\n this.logger.info('getResponseText: \"%s\"', responseText)\n\n return responseText\n }\n\n get responseXML(): Document | null {\n invariant(\n this.request.responseType === '' ||\n this.request.responseType === 'document',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n const contentType = this.request.getResponseHeader('Content-Type') || ''\n\n if (typeof DOMParser === 'undefined') {\n console.warn(\n 'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'\n )\n return null\n }\n\n if (isDomParserSupportedType(contentType)) {\n return new DOMParser().parseFromString(\n this.responseBufferToText(),\n contentType\n )\n }\n\n return null\n }\n\n public errorWith(error?: Error): void {\n /**\n * @note Mark this request as handled even if it received a mock error.\n * This prevents the controller from trying to perform this request as-is.\n */\n this[kIsRequestHandled] = true\n this.logger.info('responding with an error')\n\n this.setReadyState(this.request.DONE)\n this.trigger('error', this.request)\n this.trigger('loadend', this.request)\n }\n\n /**\n * Transitions this request's `readyState` to the given one.\n */\n private setReadyState(nextReadyState: number): void {\n this.logger.info(\n 'setReadyState: %d -> %d',\n this.request.readyState,\n nextReadyState\n )\n\n if (this.request.readyState === nextReadyState) {\n this.logger.info('ready state identical, skipping transition...')\n return\n }\n\n define(this.request, 'readyState', nextReadyState)\n\n this.logger.info('set readyState to: %d', nextReadyState)\n\n if (nextReadyState !== this.request.UNSENT) {\n this.logger.info('triggering \"readystatechange\" event...')\n\n this.trigger('readystatechange', this.request)\n }\n }\n\n /**\n * Triggers given event on the `XMLHttpRequest` instance.\n */\n private trigger<\n EventName extends keyof (XMLHttpRequestEventTargetEventMap & {\n readystatechange: ProgressEvent<XMLHttpRequestEventTarget>\n }),\n >(\n eventName: EventName,\n target: XMLHttpRequest | XMLHttpRequestUpload,\n options?: ProgressEventInit\n ): void {\n const callback = (target as XMLHttpRequest)[`on${eventName}`]\n const event = createEvent(target, eventName, options)\n\n this.logger.info('trigger \"%s\"', eventName, options || '')\n\n // Invoke direct callbacks.\n if (typeof callback === 'function') {\n this.logger.info('found a direct \"%s\" callback, calling...', eventName)\n callback.call(target as XMLHttpRequest, event)\n }\n\n // Invoke event listeners.\n const events =\n target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events\n\n for (const [registeredEventName, listeners] of events) {\n if (registeredEventName === eventName) {\n this.logger.info(\n 'found %d listener(s) for \"%s\" event, calling...',\n listeners.length,\n eventName\n )\n\n listeners.forEach((listener) => listener.call(target, event))\n }\n }\n }\n\n /**\n * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.\n */\n private toFetchApiRequest(\n body: XMLHttpRequestBodyInit | Document | null | undefined\n ): Request {\n this.logger.info('converting request to a Fetch API Request...')\n\n // If the `Document` is used as the body of this XMLHttpRequest,\n // set its inner text as the Fetch API Request body.\n const resolvedBody =\n body instanceof Document ? body.documentElement.innerText : body\n\n const fetchRequest = new FetchRequest(this.url.href, {\n method: this.method,\n headers: this.requestHeaders,\n /**\n * @see https://xhr.spec.whatwg.org/#cross-origin-credentials\n */\n credentials: this.request.withCredentials ? 'include' : 'same-origin',\n body: ['GET', 'HEAD'].includes(this.method.toUpperCase())\n ? null\n : resolvedBody,\n })\n\n const proxyHeaders = createProxy(fetchRequest.headers, {\n methodCall: ([methodName, args], invoke) => {\n // Forward the latest state of the internal request headers\n // because the interceptor might have modified them\n // without responding to the request.\n switch (methodName) {\n case 'append':\n case 'set': {\n const [headerName, headerValue] = args as [string, string]\n this.request.setRequestHeader(headerName, headerValue)\n break\n }\n\n case 'delete': {\n const [headerName] = args as [string]\n console.warn(\n `XMLHttpRequest: Cannot remove a \"${headerName}\" header from the Fetch API representation of the \"${fetchRequest.method} ${fetchRequest.url}\" request. XMLHttpRequest headers cannot be removed.`\n )\n break\n }\n }\n\n return invoke()\n },\n })\n define(fetchRequest, 'headers', proxyHeaders)\n setRawRequest(fetchRequest, this.request)\n\n this.logger.info('converted request to a Fetch API Request!', fetchRequest)\n\n return fetchRequest\n }\n}\n\nfunction toAbsoluteUrl(url: string | URL): URL {\n /**\n * @note XMLHttpRequest interceptor may run in environments\n * that implement XMLHttpRequest but don't implement \"location\"\n * (for example, React Native). If that's the case, return the\n * input URL as-is (nothing to be relative to).\n * @see https://github.com/mswjs/msw/issues/1777\n */\n if (typeof location === 'undefined') {\n return new URL(url)\n }\n\n return new URL(url.toString(), location.href)\n}\n\nfunction define(\n target: object,\n property: string | symbol,\n value: unknown\n): void {\n Reflect.defineProperty(target, property, {\n // Ensure writable properties to allow redefining readonly properties.\n writable: true,\n enumerable: true,\n value,\n })\n}\n","import type { Logger } from '@open-draft/logger'\nimport { XMLHttpRequestEmitter } from '.'\nimport { RequestController } from '../../RequestController'\nimport { XMLHttpRequestController } from './XMLHttpRequestController'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { isResponseError } from '../../utils/responseUtils'\n\nexport interface XMLHttpRequestProxyOptions {\n emitter: XMLHttpRequestEmitter\n logger: Logger\n}\n\n/**\n * Create a proxied `XMLHttpRequest` class.\n * The proxied class establishes spies on certain methods,\n * allowing us to intercept requests and respond to them.\n */\nexport function createXMLHttpRequestProxy({\n emitter,\n logger,\n}: XMLHttpRequestProxyOptions) {\n const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {\n construct(target, args, newTarget) {\n logger.info('constructed new XMLHttpRequest')\n\n const originalRequest = Reflect.construct(\n target,\n args,\n newTarget\n ) as XMLHttpRequest\n\n /**\n * @note Forward prototype descriptors onto the proxied object.\n * XMLHttpRequest is implemented in JSDOM in a way that assigns\n * a bunch of descriptors, like \"set responseType()\" on the prototype.\n * With this propagation, we make sure that those descriptors trigger\n * when the user operates with the proxied request instance.\n */\n const prototypeDescriptors = Object.getOwnPropertyDescriptors(\n target.prototype\n )\n for (const propertyName in prototypeDescriptors) {\n Reflect.defineProperty(\n originalRequest,\n propertyName,\n prototypeDescriptors[propertyName]\n )\n }\n\n const xhrRequestController = new XMLHttpRequestController(\n originalRequest,\n logger\n )\n\n xhrRequestController.onRequest = async function ({ request, requestId }) {\n const controller = new RequestController(request, {\n passthrough: () => {\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n },\n respondWith: async (response) => {\n if (isResponseError(response)) {\n this.errorWith(new TypeError('Network error'))\n return\n }\n\n await this.respondWith(response)\n },\n errorWith: (reason) => {\n this.logger.info('request errored!', { error: reason })\n\n if (reason instanceof Error) {\n this.errorWith(reason)\n }\n },\n })\n\n this.logger.info('awaiting mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n controller,\n emitter,\n })\n }\n\n xhrRequestController.onResponse = async function ({\n response,\n isMockedResponse,\n request,\n requestId,\n }) {\n this.logger.info(\n 'emitting the \"response\" event for %s listener(s)...',\n emitter.listenerCount('response')\n )\n\n emitter.emit('response', {\n response,\n isMockedResponse,\n request,\n requestId,\n })\n }\n\n // Return the proxied request from the controller\n // so that the controller can react to the consumer's interactions\n // with this request (opening/sending/etc).\n return xhrRequestController.request\n },\n })\n\n return XMLHttpRequestProxy\n}\n","import { Emitter } from 'strict-event-emitter'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>\n\nexport class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {\n static interceptorSymbol = Symbol('xhr')\n\n constructor() {\n super(XMLHttpRequestInterceptor.interceptorSymbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('XMLHttpRequest')\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('patching global XMLHttpRequest...')\n\n this.subscriptions.push(\n globalsRegistry.replaceGlobal(\n 'XMLHttpRequest',\n createXMLHttpRequestProxy({\n emitter: this.emitter,\n logger: this.logger,\n })\n )\n )\n\n logger.info(\n 'global XMLHttpRequest patched!',\n globalThis.XMLHttpRequest.name\n )\n }\n}\n"],"mappings":"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"} |
| import { c as Interceptor } from "./fetchUtils-Bq0Mdmkv.mjs"; | ||
| //#region src/BatchInterceptor.ts | ||
| /** | ||
| * A batch interceptor that exposes a single interface | ||
| * to apply and operate with multiple interceptors at once. | ||
| */ | ||
| var BatchInterceptor = class BatchInterceptor extends Interceptor { | ||
| constructor(options) { | ||
| BatchInterceptor.symbol = Symbol(options.name); | ||
| super(BatchInterceptor.symbol); | ||
| this.interceptors = options.interceptors; | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("applying all %d interceptors...", this.interceptors.length); | ||
| for (const interceptor of this.interceptors) { | ||
| logger.info("applying \"%s\" interceptor...", interceptor.constructor.name); | ||
| interceptor.apply(); | ||
| logger.info("adding interceptor dispose subscription"); | ||
| this.subscriptions.push(() => interceptor.dispose()); | ||
| } | ||
| } | ||
| on(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.on(event, listener); | ||
| return this; | ||
| } | ||
| once(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.once(event, listener); | ||
| return this; | ||
| } | ||
| off(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.off(event, listener); | ||
| return this; | ||
| } | ||
| removeAllListeners(event) { | ||
| for (const interceptors of this.interceptors) interceptors.removeAllListeners(event); | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { BatchInterceptor as t }; | ||
| //# sourceMappingURL=BatchInterceptor-0prNZ0wK.mjs.map |
| {"version":3,"file":"BatchInterceptor-0prNZ0wK.mjs","names":[],"sources":["../../src/BatchInterceptor.ts"],"sourcesContent":["import { EventMap, Listener } from 'strict-event-emitter'\nimport { Interceptor, ExtractEventNames } from './Interceptor'\n\nexport interface BatchInterceptorOptions<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> {\n name: string\n interceptors: InterceptorList\n}\n\nexport type ExtractEventMapType<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> = InterceptorList extends ReadonlyArray<infer InterceptorType>\n ? InterceptorType extends Interceptor<infer EventMap>\n ? EventMap\n : never\n : never\n\n/**\n * A batch interceptor that exposes a single interface\n * to apply and operate with multiple interceptors at once.\n */\nexport class BatchInterceptor<\n InterceptorList extends ReadonlyArray<Interceptor<any>>,\n Events extends EventMap = ExtractEventMapType<InterceptorList>\n> extends Interceptor<Events> {\n static symbol: symbol\n\n private interceptors: InterceptorList\n\n constructor(options: BatchInterceptorOptions<InterceptorList>) {\n BatchInterceptor.symbol = Symbol(options.name)\n super(BatchInterceptor.symbol)\n this.interceptors = options.interceptors\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('applying all %d interceptors...', this.interceptors.length)\n\n for (const interceptor of this.interceptors) {\n logger.info('applying \"%s\" interceptor...', interceptor.constructor.name)\n interceptor.apply()\n\n logger.info('adding interceptor dispose subscription')\n this.subscriptions.push(() => interceptor.dispose())\n }\n }\n\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n // Instead of adding a listener to the batch interceptor,\n // propagate the listener to each of the individual interceptors.\n for (const interceptor of this.interceptors) {\n interceptor.on(event, listener)\n }\n\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.once(event, listener)\n }\n\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.off(event, listener)\n }\n\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName | undefined\n ): this {\n for (const interceptors of this.interceptors) {\n interceptors.removeAllListeners(event)\n }\n\n return this\n }\n}\n"],"mappings":";;;;;;;AAsBA,IAAa,mBAAb,MAAa,yBAGH,YAAoB;CAK5B,YAAY,SAAmD;AAC7D,mBAAiB,SAAS,OAAO,QAAQ,KAAK;AAC9C,QAAM,iBAAiB,OAAO;AAC9B,OAAK,eAAe,QAAQ;;CAG9B,AAAU,QAAQ;EAChB,MAAM,SAAS,KAAK,OAAO,OAAO,QAAQ;AAE1C,SAAO,KAAK,mCAAmC,KAAK,aAAa,OAAO;AAExE,OAAK,MAAM,eAAe,KAAK,cAAc;AAC3C,UAAO,KAAK,kCAAgC,YAAY,YAAY,KAAK;AACzE,eAAY,OAAO;AAEnB,UAAO,KAAK,0CAA0C;AACtD,QAAK,cAAc,WAAW,YAAY,SAAS,CAAC;;;CAIxD,AAAO,GACL,OACA,UACM;AAGN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,GAAG,OAAO,SAAS;AAGjC,SAAO;;CAGT,AAAO,KACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,KAAK,OAAO,SAAS;AAGnC,SAAO;;CAGT,AAAO,IACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,IAAI,OAAO,SAAS;AAGlC,SAAO;;CAGT,AAAO,mBACL,OACM;AACN,OAAK,MAAM,gBAAgB,KAAK,aAC9B,cAAa,mBAAmB,MAAM;AAGxC,SAAO"} |
| const require_fetchUtils = require('./fetchUtils-BAlG765A.cjs'); | ||
| //#region src/BatchInterceptor.ts | ||
| /** | ||
| * A batch interceptor that exposes a single interface | ||
| * to apply and operate with multiple interceptors at once. | ||
| */ | ||
| var BatchInterceptor = class BatchInterceptor extends require_fetchUtils.Interceptor { | ||
| constructor(options) { | ||
| BatchInterceptor.symbol = Symbol(options.name); | ||
| super(BatchInterceptor.symbol); | ||
| this.interceptors = options.interceptors; | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("applying all %d interceptors...", this.interceptors.length); | ||
| for (const interceptor of this.interceptors) { | ||
| logger.info("applying \"%s\" interceptor...", interceptor.constructor.name); | ||
| interceptor.apply(); | ||
| logger.info("adding interceptor dispose subscription"); | ||
| this.subscriptions.push(() => interceptor.dispose()); | ||
| } | ||
| } | ||
| on(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.on(event, listener); | ||
| return this; | ||
| } | ||
| once(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.once(event, listener); | ||
| return this; | ||
| } | ||
| off(event, listener) { | ||
| for (const interceptor of this.interceptors) interceptor.off(event, listener); | ||
| return this; | ||
| } | ||
| removeAllListeners(event) { | ||
| for (const interceptors of this.interceptors) interceptors.removeAllListeners(event); | ||
| return this; | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'BatchInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return BatchInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=BatchInterceptor-Bo8JunSK.cjs.map |
| {"version":3,"file":"BatchInterceptor-Bo8JunSK.cjs","names":["Interceptor"],"sources":["../../src/BatchInterceptor.ts"],"sourcesContent":["import { EventMap, Listener } from 'strict-event-emitter'\nimport { Interceptor, ExtractEventNames } from './Interceptor'\n\nexport interface BatchInterceptorOptions<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> {\n name: string\n interceptors: InterceptorList\n}\n\nexport type ExtractEventMapType<\n InterceptorList extends ReadonlyArray<Interceptor<any>>\n> = InterceptorList extends ReadonlyArray<infer InterceptorType>\n ? InterceptorType extends Interceptor<infer EventMap>\n ? EventMap\n : never\n : never\n\n/**\n * A batch interceptor that exposes a single interface\n * to apply and operate with multiple interceptors at once.\n */\nexport class BatchInterceptor<\n InterceptorList extends ReadonlyArray<Interceptor<any>>,\n Events extends EventMap = ExtractEventMapType<InterceptorList>\n> extends Interceptor<Events> {\n static symbol: symbol\n\n private interceptors: InterceptorList\n\n constructor(options: BatchInterceptorOptions<InterceptorList>) {\n BatchInterceptor.symbol = Symbol(options.name)\n super(BatchInterceptor.symbol)\n this.interceptors = options.interceptors\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('applying all %d interceptors...', this.interceptors.length)\n\n for (const interceptor of this.interceptors) {\n logger.info('applying \"%s\" interceptor...', interceptor.constructor.name)\n interceptor.apply()\n\n logger.info('adding interceptor dispose subscription')\n this.subscriptions.push(() => interceptor.dispose())\n }\n }\n\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n // Instead of adding a listener to the batch interceptor,\n // propagate the listener to each of the individual interceptors.\n for (const interceptor of this.interceptors) {\n interceptor.on(event, listener)\n }\n\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.once(event, listener)\n }\n\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n for (const interceptor of this.interceptors) {\n interceptor.off(event, listener)\n }\n\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName | undefined\n ): this {\n for (const interceptors of this.interceptors) {\n interceptors.removeAllListeners(event)\n }\n\n return this\n }\n}\n"],"mappings":";;;;;;;AAsBA,IAAa,mBAAb,MAAa,yBAGHA,+BAAoB;CAK5B,YAAY,SAAmD;AAC7D,mBAAiB,SAAS,OAAO,QAAQ,KAAK;AAC9C,QAAM,iBAAiB,OAAO;AAC9B,OAAK,eAAe,QAAQ;;CAG9B,AAAU,QAAQ;EAChB,MAAM,SAAS,KAAK,OAAO,OAAO,QAAQ;AAE1C,SAAO,KAAK,mCAAmC,KAAK,aAAa,OAAO;AAExE,OAAK,MAAM,eAAe,KAAK,cAAc;AAC3C,UAAO,KAAK,kCAAgC,YAAY,YAAY,KAAK;AACzE,eAAY,OAAO;AAEnB,UAAO,KAAK,0CAA0C;AACtD,QAAK,cAAc,WAAW,YAAY,SAAS,CAAC;;;CAIxD,AAAO,GACL,OACA,UACM;AAGN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,GAAG,OAAO,SAAS;AAGjC,SAAO;;CAGT,AAAO,KACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,KAAK,OAAO,SAAS;AAGnC,SAAO;;CAGT,AAAO,IACL,OACA,UACM;AACN,OAAK,MAAM,eAAe,KAAK,aAC7B,aAAY,IAAI,OAAO,SAAS;AAGlC,SAAO;;CAGT,AAAO,mBACL,OACM;AACN,OAAK,MAAM,gBAAgB,KAAK,aAC9B,cAAa,mBAAmB,MAAM;AAGxC,SAAO"} |
| import { a as RequestController, c as Interceptor, i as createRequestId, n as FetchResponse, s as INTERNAL_REQUEST_ID_HEADER_NAME, t as FetchRequest } from "./fetchUtils-Bq0Mdmkv.mjs"; | ||
| import { n as setRawRequest } from "./getRawRequest-C2-1urzA.mjs"; | ||
| import { a as isPropertyAccessible, i as emitAsync, r as isObject, t as handleRequest } from "./handleRequest-u6jpjgrr.mjs"; | ||
| import { n as setRawRequestBodyStream } from "./node-lsdNwZEW.mjs"; | ||
| import { Logger } from "@open-draft/logger"; | ||
| import { invariant } from "outvariant"; | ||
| import http, { IncomingMessage, STATUS_CODES, ServerResponse, globalAgent } from "node:http"; | ||
| import https, { globalAgent as globalAgent$1 } from "node:https"; | ||
| import net from "node:net"; | ||
| import { HTTPParser } from "_http_common"; | ||
| import { Readable } from "node:stream"; | ||
| import { URL as URL$1, parse, urlToHttpOptions } from "node:url"; | ||
| import { Agent } from "http"; | ||
| //#region src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts | ||
| /** | ||
| * Normalizes the arguments provided to the `Writable.prototype.write()` | ||
| * and `Writable.prototype.end()`. | ||
| */ | ||
| function normalizeSocketWriteArgs(args) { | ||
| const normalized = [ | ||
| args[0], | ||
| void 0, | ||
| void 0 | ||
| ]; | ||
| if (typeof args[1] === "string") normalized[1] = args[1]; | ||
| else if (typeof args[1] === "function") normalized[2] = args[1]; | ||
| if (typeof args[2] === "function") normalized[2] = args[2]; | ||
| return normalized; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/Socket/MockSocket.ts | ||
| var MockSocket = class extends net.Socket { | ||
| constructor(options) { | ||
| super(); | ||
| this.options = options; | ||
| this.connecting = false; | ||
| this.connect(); | ||
| this._final = (callback) => { | ||
| callback(null); | ||
| }; | ||
| } | ||
| connect() { | ||
| this.connecting = true; | ||
| return this; | ||
| } | ||
| write(...args) { | ||
| const [chunk, encoding, callback] = normalizeSocketWriteArgs(args); | ||
| this.options.write(chunk, encoding, callback); | ||
| return true; | ||
| } | ||
| end(...args) { | ||
| const [chunk, encoding, callback] = normalizeSocketWriteArgs(args); | ||
| this.options.write(chunk, encoding, callback); | ||
| return super.end.apply(this, args); | ||
| } | ||
| push(chunk, encoding) { | ||
| this.options.read(chunk, encoding); | ||
| return super.push(chunk, encoding); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts | ||
| function baseUrlFromConnectionOptions(options) { | ||
| if ("href" in options) return new URL(options.href); | ||
| const protocol = options.port === 443 ? "https:" : "http:"; | ||
| const host = options.host; | ||
| const url = new URL(`${protocol}//${host}`); | ||
| if (options.port) url.port = options.port.toString(); | ||
| if (options.path) url.pathname = options.path; | ||
| if (options.auth) { | ||
| const [username, password] = options.auth.split(":"); | ||
| url.username = username; | ||
| url.password = password; | ||
| } | ||
| return url; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/recordRawHeaders.ts | ||
| const kRawHeaders = Symbol("kRawHeaders"); | ||
| const kRestorePatches = Symbol("kRestorePatches"); | ||
| function recordRawHeader(headers, args, behavior) { | ||
| ensureRawHeadersSymbol(headers, []); | ||
| const rawHeaders = Reflect.get(headers, kRawHeaders); | ||
| if (behavior === "set") { | ||
| for (let index = rawHeaders.length - 1; index >= 0; index--) if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) rawHeaders.splice(index, 1); | ||
| } | ||
| rawHeaders.push(args); | ||
| } | ||
| /** | ||
| * Define the raw headers symbol on the given `Headers` instance. | ||
| * If the symbol already exists, this function does nothing. | ||
| */ | ||
| function ensureRawHeadersSymbol(headers, rawHeaders) { | ||
| if (Reflect.has(headers, kRawHeaders)) return; | ||
| defineRawHeadersSymbol(headers, rawHeaders); | ||
| } | ||
| /** | ||
| * Define the raw headers symbol on the given `Headers` instance. | ||
| * If the symbol already exists, it gets overridden. | ||
| */ | ||
| function defineRawHeadersSymbol(headers, rawHeaders) { | ||
| Object.defineProperty(headers, kRawHeaders, { | ||
| value: rawHeaders, | ||
| enumerable: false, | ||
| configurable: true | ||
| }); | ||
| } | ||
| /** | ||
| * Patch the global `Headers` class to store raw headers. | ||
| * This is for compatibility with `IncomingMessage.prototype.rawHeaders`. | ||
| * | ||
| * @note Node.js has their own raw headers symbol but it | ||
| * only records the first header name in case of multi-value headers. | ||
| * Any other headers are normalized before comparing. This makes it | ||
| * incompatible with the `rawHeaders` format. | ||
| * | ||
| * let h = new Headers() | ||
| * h.append('X-Custom', 'one') | ||
| * h.append('x-custom', 'two') | ||
| * h[Symbol('headers map')] // Map { 'X-Custom' => 'one, two' } | ||
| */ | ||
| function recordRawFetchHeaders() { | ||
| if (Reflect.get(Headers, kRestorePatches)) return Reflect.get(Headers, kRestorePatches); | ||
| const { Headers: OriginalHeaders, Request: OriginalRequest, Response: OriginalResponse } = globalThis; | ||
| const { set, append, delete: headersDeleteMethod } = Headers.prototype; | ||
| Object.defineProperty(Headers, kRestorePatches, { | ||
| value: () => { | ||
| Headers.prototype.set = set; | ||
| Headers.prototype.append = append; | ||
| Headers.prototype.delete = headersDeleteMethod; | ||
| globalThis.Headers = OriginalHeaders; | ||
| globalThis.Request = OriginalRequest; | ||
| globalThis.Response = OriginalResponse; | ||
| Object.setPrototypeOf(FetchRequest, OriginalRequest); | ||
| Object.setPrototypeOf(FetchRequest.prototype, OriginalRequest.prototype); | ||
| Object.setPrototypeOf(FetchResponse, OriginalResponse); | ||
| Object.setPrototypeOf(FetchResponse.prototype, OriginalResponse.prototype); | ||
| Reflect.deleteProperty(Headers, kRestorePatches); | ||
| }, | ||
| enumerable: false, | ||
| configurable: true | ||
| }); | ||
| Object.defineProperty(globalThis, "Headers", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Headers, { construct(target, args, newTarget) { | ||
| const headersInit = args[0] || []; | ||
| if (headersInit instanceof Headers && Reflect.has(headersInit, kRawHeaders)) { | ||
| const headers$1 = Reflect.construct(target, [Reflect.get(headersInit, kRawHeaders)], newTarget); | ||
| ensureRawHeadersSymbol(headers$1, [...Reflect.get(headersInit, kRawHeaders)]); | ||
| return headers$1; | ||
| } | ||
| const headers = Reflect.construct(target, args, newTarget); | ||
| if (!Reflect.has(headers, kRawHeaders)) ensureRawHeadersSymbol(headers, Array.isArray(headersInit) ? headersInit : Object.entries(headersInit)); | ||
| return headers; | ||
| } }) | ||
| }); | ||
| Headers.prototype.set = new Proxy(Headers.prototype.set, { apply(target, thisArg, args) { | ||
| recordRawHeader(thisArg, args, "set"); | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Headers.prototype.append = new Proxy(Headers.prototype.append, { apply(target, thisArg, args) { | ||
| recordRawHeader(thisArg, args, "append"); | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Headers.prototype.delete = new Proxy(Headers.prototype.delete, { apply(target, thisArg, args) { | ||
| const rawHeaders = Reflect.get(thisArg, kRawHeaders); | ||
| if (rawHeaders) { | ||
| for (let index = rawHeaders.length - 1; index >= 0; index--) if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) rawHeaders.splice(index, 1); | ||
| } | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Object.defineProperty(globalThis, "Request", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Request, { construct(target, args, newTarget) { | ||
| const request = Reflect.construct(target, args, newTarget); | ||
| const inferredRawHeaders = []; | ||
| if (typeof args[0] === "object" && args[0].headers != null) inferredRawHeaders.push(...inferRawHeaders(args[0].headers)); | ||
| if (typeof args[1] === "object" && args[1].headers != null) inferredRawHeaders.push(...inferRawHeaders(args[1].headers)); | ||
| if (inferredRawHeaders.length > 0) ensureRawHeadersSymbol(request.headers, inferredRawHeaders); | ||
| return request; | ||
| } }) | ||
| }); | ||
| Object.defineProperty(globalThis, "Response", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Response, { construct(target, args, newTarget) { | ||
| const response = Reflect.construct(target, args, newTarget); | ||
| if (typeof args[1] === "object" && args[1].headers != null) ensureRawHeadersSymbol(response.headers, inferRawHeaders(args[1].headers)); | ||
| return response; | ||
| } }) | ||
| }); | ||
| /** | ||
| * Re-parent FetchRequest/FetchResponse so their `super()` calls go | ||
| * through the proxied globalThis.Request/Response above. Without this, | ||
| * FetchRequest extends the statically-captured (original) Request, | ||
| * bypassing the construct proxy that records raw headers. | ||
| */ | ||
| Object.setPrototypeOf(FetchRequest, globalThis.Request); | ||
| Object.setPrototypeOf(FetchRequest.prototype, globalThis.Request.prototype); | ||
| Object.setPrototypeOf(FetchResponse, globalThis.Response); | ||
| Object.setPrototypeOf(FetchResponse.prototype, globalThis.Response.prototype); | ||
| } | ||
| function restoreHeadersPrototype() { | ||
| if (!Reflect.get(Headers, kRestorePatches)) return; | ||
| Reflect.get(Headers, kRestorePatches)(); | ||
| } | ||
| function getRawFetchHeaders(headers) { | ||
| if (!Reflect.has(headers, kRawHeaders)) return Array.from(headers.entries()); | ||
| const rawHeaders = Reflect.get(headers, kRawHeaders); | ||
| return rawHeaders.length > 0 ? rawHeaders : Array.from(headers.entries()); | ||
| } | ||
| /** | ||
| * Infers the raw headers from the given `HeadersInit` provided | ||
| * to the Request/Response constructor. | ||
| * | ||
| * If the `init.headers` is a Headers instance, use it directly. | ||
| * That means the headers were created standalone and already have | ||
| * the raw headers stored. | ||
| * If the `init.headers` is a HeadersInit, create a new Headers | ||
| * instance out of it. | ||
| */ | ||
| function inferRawHeaders(headers) { | ||
| if (headers instanceof Headers) return Reflect.get(headers, kRawHeaders) || []; | ||
| return Reflect.get(new Headers(headers), kRawHeaders); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/parserUtils.ts | ||
| /** | ||
| * @see https://github.com/nodejs/node/blob/f3adc11e37b8bfaaa026ea85c1cf22e3a0e29ae9/lib/_http_common.js#L180 | ||
| */ | ||
| function freeParser(parser, socket) { | ||
| if (parser._consumed) parser.unconsume(); | ||
| parser._headers = []; | ||
| parser._url = ""; | ||
| parser.socket = null; | ||
| parser.incoming = null; | ||
| parser.outgoing = null; | ||
| parser.maxHeaderPairs = 2e3; | ||
| parser._consumed = false; | ||
| parser.onIncoming = null; | ||
| parser[HTTPParser.kOnHeaders] = null; | ||
| parser[HTTPParser.kOnHeadersComplete] = null; | ||
| parser[HTTPParser.kOnMessageBegin] = null; | ||
| parser[HTTPParser.kOnMessageComplete] = null; | ||
| parser[HTTPParser.kOnBody] = null; | ||
| parser[HTTPParser.kOnExecute] = null; | ||
| parser[HTTPParser.kOnTimeout] = null; | ||
| parser.remove(); | ||
| parser.free(); | ||
| if (socket) | ||
| /** | ||
| * @note Unassigning the socket's parser will fail this assertion | ||
| * if there's still some data being processed on the socket: | ||
| * @see https://github.com/nodejs/node/blob/4e1f39b678b37017ac9baa0971e3aeecd3b67b51/lib/_http_client.js#L613 | ||
| */ | ||
| if (socket.destroyed) socket.parser = null; | ||
| else socket.once("end", () => { | ||
| socket.parser = null; | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/MockHttpSocket.ts | ||
| const kRequestId = Symbol("kRequestId"); | ||
| var MockHttpSocket = class extends MockSocket { | ||
| constructor(options) { | ||
| super({ | ||
| write: (chunk, encoding, callback) => { | ||
| if (this.socketState !== "passthrough") this.writeBuffer.push([ | ||
| chunk, | ||
| encoding, | ||
| callback | ||
| ]); | ||
| if (chunk) { | ||
| /** | ||
| * Forward any writes to the mock socket to the underlying original socket. | ||
| * This ensures functional duplex connections, like WebSocket. | ||
| * @see https://github.com/mswjs/interceptors/issues/682 | ||
| */ | ||
| if (this.socketState === "passthrough") this.originalSocket?.write(chunk, encoding, callback); | ||
| this.requestParser.execute(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding)); | ||
| } | ||
| }, | ||
| read: (chunk) => { | ||
| if (chunk !== null) | ||
| /** | ||
| * @todo We need to free the parser if the connection has been | ||
| * upgraded to a non-HTTP protocol. It won't be able to parse data | ||
| * from that point onward anyway. No need to keep it in memory. | ||
| */ | ||
| this.responseParser.execute(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); | ||
| } | ||
| }); | ||
| this.requestRawHeadersBuffer = []; | ||
| this.responseRawHeadersBuffer = []; | ||
| this.writeBuffer = []; | ||
| this.socketState = "unknown"; | ||
| this.onRequestHeaders = (rawHeaders) => { | ||
| this.requestRawHeadersBuffer.push(...rawHeaders); | ||
| }; | ||
| this.onRequestStart = (versionMajor, versionMinor, rawHeaders, _, path, __, ___, ____, shouldKeepAlive) => { | ||
| this.shouldKeepAlive = shouldKeepAlive; | ||
| const url = new URL(path || "", this.baseUrl); | ||
| const method = this.connectionOptions.method?.toUpperCase() || "GET"; | ||
| const headers = FetchResponse.parseRawHeaders([...this.requestRawHeadersBuffer, ...rawHeaders || []]); | ||
| this.requestRawHeadersBuffer.length = 0; | ||
| const canHaveBody = method !== "GET" && method !== "HEAD"; | ||
| if (url.username || url.password) { | ||
| if (!headers.has("authorization")) headers.set("authorization", `Basic ${url.username}:${url.password}`); | ||
| url.username = ""; | ||
| url.password = ""; | ||
| } | ||
| this.requestStream = new Readable({ read: () => { | ||
| this.flushWriteBuffer(); | ||
| } }); | ||
| const requestId = createRequestId(); | ||
| this.request = new FetchRequest(url, { | ||
| method, | ||
| headers, | ||
| credentials: "same-origin", | ||
| duplex: canHaveBody ? "half" : void 0, | ||
| body: canHaveBody ? Readable.toWeb(this.requestStream) : null | ||
| }); | ||
| Reflect.set(this.request, kRequestId, requestId); | ||
| setRawRequest(this.request, Reflect.get(this, "_httpMessage")); | ||
| setRawRequestBodyStream(this.request, this.requestStream); | ||
| /** | ||
| * @fixme Stop relying on the "X-Request-Id" request header | ||
| * to figure out if one interceptor has been invoked within another. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) { | ||
| this.passthrough(); | ||
| return; | ||
| } | ||
| this.onRequest({ | ||
| requestId, | ||
| request: this.request, | ||
| socket: this | ||
| }); | ||
| }; | ||
| this.onResponseHeaders = (rawHeaders) => { | ||
| this.responseRawHeadersBuffer.push(...rawHeaders); | ||
| }; | ||
| this.onResponseStart = (versionMajor, versionMinor, rawHeaders, method, url, status, statusText) => { | ||
| const headers = FetchResponse.parseRawHeaders([...this.responseRawHeadersBuffer, ...rawHeaders || []]); | ||
| this.responseRawHeadersBuffer.length = 0; | ||
| const response = new FetchResponse( | ||
| /** | ||
| * @note The Fetch API response instance exposed to the consumer | ||
| * is created over the response stream of the HTTP parser. It is NOT | ||
| * related to the Socket instance. This way, you can read response body | ||
| * in response listener while the Socket instance delays the emission | ||
| * of "end" and other events until those response listeners are finished. | ||
| */ | ||
| FetchResponse.isResponseWithBody(status) ? Readable.toWeb(this.responseStream = new Readable({ read() {} })) : null, | ||
| { | ||
| url, | ||
| status, | ||
| statusText, | ||
| headers | ||
| } | ||
| ); | ||
| invariant(this.request, "Failed to handle a response: request does not exist"); | ||
| FetchResponse.setUrl(this.request.url, response); | ||
| /** | ||
| * @fixme Stop relying on the "X-Request-Id" request header | ||
| * to figure out if one interceptor has been invoked within another. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) return; | ||
| this.responseListenersPromise = this.onResponse({ | ||
| response, | ||
| isMockedResponse: this.socketState === "mock", | ||
| requestId: Reflect.get(this.request, kRequestId), | ||
| request: this.request, | ||
| socket: this | ||
| }); | ||
| }; | ||
| this.connectionOptions = options.connectionOptions; | ||
| this.createConnection = options.createConnection; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions); | ||
| this.requestParser = new HTTPParser(); | ||
| this.requestParser.initialize(HTTPParser.REQUEST, {}); | ||
| this.requestParser[HTTPParser.kOnHeaders] = this.onRequestHeaders.bind(this); | ||
| this.requestParser[HTTPParser.kOnHeadersComplete] = this.onRequestStart.bind(this); | ||
| this.requestParser[HTTPParser.kOnBody] = this.onRequestBody.bind(this); | ||
| this.requestParser[HTTPParser.kOnMessageComplete] = this.onRequestEnd.bind(this); | ||
| this.responseParser = new HTTPParser(); | ||
| this.responseParser.initialize(HTTPParser.RESPONSE, {}); | ||
| this.responseParser[HTTPParser.kOnHeaders] = this.onResponseHeaders.bind(this); | ||
| this.responseParser[HTTPParser.kOnHeadersComplete] = this.onResponseStart.bind(this); | ||
| this.responseParser[HTTPParser.kOnBody] = this.onResponseBody.bind(this); | ||
| this.responseParser[HTTPParser.kOnMessageComplete] = this.onResponseEnd.bind(this); | ||
| this.once("finish", () => freeParser(this.requestParser, this)); | ||
| if (this.baseUrl.protocol === "https:") { | ||
| Reflect.set(this, "encrypted", true); | ||
| Reflect.set(this, "authorized", false); | ||
| Reflect.set(this, "getProtocol", () => "TLSv1.3"); | ||
| Reflect.set(this, "getSession", () => void 0); | ||
| Reflect.set(this, "isSessionReused", () => false); | ||
| Reflect.set(this, "getCipher", () => ({ | ||
| name: "AES256-SHA", | ||
| standardName: "TLS_RSA_WITH_AES_256_CBC_SHA", | ||
| version: "TLSv1.3" | ||
| })); | ||
| } | ||
| } | ||
| emit(event, ...args) { | ||
| const emitEvent = super.emit.bind(this, event, ...args); | ||
| if (this.responseListenersPromise) { | ||
| this.responseListenersPromise.finally(emitEvent); | ||
| return this.listenerCount(event) > 0; | ||
| } | ||
| return emitEvent(); | ||
| } | ||
| destroy(error) { | ||
| freeParser(this.responseParser, this); | ||
| if (error) this.emit("error", error); | ||
| return super.destroy(error); | ||
| } | ||
| /** | ||
| * Establish this Socket connection as-is and pipe | ||
| * its data/events through this Socket. | ||
| */ | ||
| passthrough() { | ||
| this.socketState = "passthrough"; | ||
| if (this.destroyed) return; | ||
| const socket = this.createConnection(); | ||
| this.originalSocket = socket; | ||
| /** | ||
| * @note Inherit the original socket's connection handle. | ||
| * Without this, each push to the mock socket results in a | ||
| * new "connection" listener being added (i.e. buffering pushes). | ||
| * @see https://github.com/nodejs/node/blob/b18153598b25485ce4f54d0c5cb830a9457691ee/lib/net.js#L734 | ||
| */ | ||
| if ("_handle" in socket) Object.defineProperty(this, "_handle", { | ||
| value: socket._handle, | ||
| enumerable: true, | ||
| writable: true | ||
| }); | ||
| this.once("close", () => { | ||
| socket.removeAllListeners(); | ||
| if (!socket.destroyed) socket.destroy(); | ||
| this.originalSocket = void 0; | ||
| }); | ||
| this.address = socket.address.bind(socket); | ||
| let writeArgs; | ||
| let headersWritten = false; | ||
| while (writeArgs = this.writeBuffer.shift()) if (writeArgs !== void 0) { | ||
| if (!headersWritten) { | ||
| const [chunk, encoding, callback] = writeArgs; | ||
| const chunkString = chunk.toString(); | ||
| const chunkBeforeRequestHeaders = chunkString.slice(0, chunkString.indexOf("\r\n") + 2); | ||
| const chunkAfterRequestHeaders = chunkString.slice(chunk.indexOf("\r\n\r\n")); | ||
| const headersChunk = `${chunkBeforeRequestHeaders}${getRawFetchHeaders(this.request.headers).filter(([name]) => { | ||
| return name.toLowerCase() !== INTERNAL_REQUEST_ID_HEADER_NAME; | ||
| }).map(([name, value]) => `${name}: ${value}`).join("\r\n")}${chunkAfterRequestHeaders}`; | ||
| socket.write(headersChunk, encoding, callback); | ||
| headersWritten = true; | ||
| continue; | ||
| } | ||
| socket.write(...writeArgs); | ||
| } | ||
| if (Reflect.get(socket, "encrypted")) [ | ||
| "encrypted", | ||
| "authorized", | ||
| "getProtocol", | ||
| "getSession", | ||
| "isSessionReused", | ||
| "getCipher" | ||
| ].forEach((propertyName) => { | ||
| Object.defineProperty(this, propertyName, { | ||
| enumerable: true, | ||
| get: () => { | ||
| const value = Reflect.get(socket, propertyName); | ||
| return typeof value === "function" ? value.bind(socket) : value; | ||
| } | ||
| }); | ||
| }); | ||
| socket.on("lookup", (...args) => this.emit("lookup", ...args)).on("connect", () => { | ||
| this.connecting = socket.connecting; | ||
| this.emit("connect"); | ||
| }).on("secureConnect", () => this.emit("secureConnect")).on("secure", () => this.emit("secure")).on("session", (session) => this.emit("session", session)).on("ready", () => this.emit("ready")).on("drain", () => this.emit("drain")).on("data", (chunk) => { | ||
| this.push(chunk); | ||
| }).on("error", (error) => { | ||
| Reflect.set(this, "_hadError", Reflect.get(socket, "_hadError")); | ||
| this.emit("error", error); | ||
| }).on("resume", () => this.emit("resume")).on("timeout", () => this.emit("timeout")).on("prefinish", () => this.emit("prefinish")).on("finish", () => this.emit("finish")).on("close", (hadError) => this.emit("close", hadError)).on("end", () => this.emit("end")); | ||
| } | ||
| /** | ||
| * Convert the given Fetch API `Response` instance to an | ||
| * HTTP message and push it to the socket. | ||
| */ | ||
| async respondWith(response) { | ||
| if (this.destroyed) return; | ||
| invariant(this.socketState !== "mock", "[MockHttpSocket] Failed to respond to the \"%s %s\" request with \"%s %s\": the request has already been handled", this.request?.method, this.request?.url, response.status, response.statusText); | ||
| if (isPropertyAccessible(response, "type") && response.type === "error") { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| this.mockConnect(); | ||
| this.socketState = "mock"; | ||
| this.flushWriteBuffer(); | ||
| const serverResponse = new ServerResponse(new IncomingMessage(this)); | ||
| /** | ||
| * Assign a mock socket instance to the server response to | ||
| * spy on the response chunk writes. Push the transformed response chunks | ||
| * to this `MockHttpSocket` instance to trigger the "data" event. | ||
| * @note Providing the same `MockSocket` instance when creating `ServerResponse` | ||
| * does not have the same effect. | ||
| * @see https://github.com/nodejs/node/blob/10099bb3f7fd97bb9dd9667188426866b3098e07/test/parallel/test-http-server-response-standalone.js#L32 | ||
| */ | ||
| serverResponse.assignSocket(new MockSocket({ | ||
| write: (chunk, encoding, callback) => { | ||
| this.push(chunk, encoding); | ||
| callback?.(); | ||
| }, | ||
| read() {} | ||
| })); | ||
| /** | ||
| * @note Remove the `Connection` and `Date` response headers | ||
| * injected by `ServerResponse` by default. Those are required | ||
| * from the server but the interceptor is NOT technically a server. | ||
| * It's confusing to add response headers that the developer didn't | ||
| * specify themselves. They can always add these if they wish. | ||
| * @see https://www.rfc-editor.org/rfc/rfc9110#field.date | ||
| * @see https://www.rfc-editor.org/rfc/rfc9110#field.connection | ||
| */ | ||
| serverResponse.removeHeader("connection"); | ||
| serverResponse.removeHeader("date"); | ||
| const rawResponseHeaders = getRawFetchHeaders(response.headers); | ||
| /** | ||
| * @note Call `.writeHead` in order to set the raw response headers | ||
| * in the same case as they were provided by the developer. Using | ||
| * `.setHeader()`/`.appendHeader()` normalizes header names. | ||
| */ | ||
| serverResponse.writeHead(response.status, response.statusText || STATUS_CODES[response.status], rawResponseHeaders); | ||
| this.once("error", () => { | ||
| serverResponse.destroy(); | ||
| }); | ||
| if (response.body) try { | ||
| const reader = response.body.getReader(); | ||
| while (true) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) { | ||
| serverResponse.end(); | ||
| break; | ||
| } | ||
| serverResponse.write(value); | ||
| } | ||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| serverResponse.destroy(); | ||
| /** | ||
| * @note Destroy the request socket gracefully. | ||
| * Response stream errors do NOT produce request errors. | ||
| */ | ||
| this.destroy(); | ||
| return; | ||
| } | ||
| serverResponse.destroy(); | ||
| throw error; | ||
| } | ||
| else serverResponse.end(); | ||
| if (!this.shouldKeepAlive) { | ||
| this.emit("readable"); | ||
| /** | ||
| * @todo @fixme This is likely a hack. | ||
| * Since we push null to the socket, it never propagates to the | ||
| * parser, and the parser never calls "onResponseEnd" to close | ||
| * the response stream. We are closing the stream here manually | ||
| * but that shouldn't be the case. | ||
| */ | ||
| this.responseStream?.push(null); | ||
| this.push(null); | ||
| } | ||
| } | ||
| /** | ||
| * Close this socket connection with the given error. | ||
| */ | ||
| errorWith(error) { | ||
| this.destroy(error); | ||
| } | ||
| mockConnect() { | ||
| this.connecting = false; | ||
| const isIPv6 = net.isIPv6(this.connectionOptions.hostname) || this.connectionOptions.family === 6; | ||
| const addressInfo = { | ||
| address: isIPv6 ? "::1" : "127.0.0.1", | ||
| family: isIPv6 ? "IPv6" : "IPv4", | ||
| port: this.connectionOptions.port | ||
| }; | ||
| this.address = () => addressInfo; | ||
| this.emit("lookup", null, addressInfo.address, addressInfo.family === "IPv6" ? 6 : 4, this.connectionOptions.host); | ||
| this.emit("connect"); | ||
| this.emit("ready"); | ||
| if (this.baseUrl.protocol === "https:") { | ||
| this.emit("secure"); | ||
| this.emit("secureConnect"); | ||
| this.emit("session", this.connectionOptions.session || Buffer.from("mock-session-renegotiate")); | ||
| this.emit("session", Buffer.from("mock-session-resume")); | ||
| } | ||
| } | ||
| flushWriteBuffer() { | ||
| for (const writeCall of this.writeBuffer) if (typeof writeCall[2] === "function") { | ||
| writeCall[2](); | ||
| /** | ||
| * @note Remove the callback from the write call | ||
| * so it doesn't get called twice on passthrough | ||
| * if `request.end()` was called within `request.write()`. | ||
| * @see https://github.com/mswjs/interceptors/issues/684 | ||
| */ | ||
| writeCall[2] = void 0; | ||
| } | ||
| } | ||
| onRequestBody(chunk) { | ||
| invariant(this.requestStream, "Failed to write to a request stream: stream does not exist"); | ||
| this.requestStream.push(chunk); | ||
| } | ||
| onRequestEnd() { | ||
| if (this.requestStream) this.requestStream.push(null); | ||
| } | ||
| onResponseBody(chunk) { | ||
| invariant(this.responseStream, "Failed to write to a response stream: stream does not exist"); | ||
| this.responseStream.push(chunk); | ||
| } | ||
| onResponseEnd() { | ||
| if (this.responseStream) this.responseStream.push(null); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/agents.ts | ||
| var MockAgent = class extends http.Agent { | ||
| constructor(options) { | ||
| super(); | ||
| this.customAgent = options.customAgent; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| } | ||
| createConnection(options, callback) { | ||
| const createConnection = this.customAgent instanceof http.Agent ? this.customAgent.createConnection : super.createConnection; | ||
| const createConnectionOptions = this.customAgent instanceof http.Agent ? { | ||
| ...options, | ||
| ...this.customAgent.options | ||
| } : options; | ||
| return new MockHttpSocket({ | ||
| connectionOptions: options, | ||
| createConnection: createConnection.bind(this.customAgent || this, createConnectionOptions, callback), | ||
| onRequest: this.onRequest.bind(this), | ||
| onResponse: this.onResponse.bind(this) | ||
| }); | ||
| } | ||
| }; | ||
| var MockHttpsAgent = class extends https.Agent { | ||
| constructor(options) { | ||
| super(); | ||
| this.customAgent = options.customAgent; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| } | ||
| createConnection(options, callback) { | ||
| const createConnection = this.customAgent instanceof http.Agent ? this.customAgent.createConnection : super.createConnection; | ||
| const createConnectionOptions = this.customAgent instanceof http.Agent ? { | ||
| ...options, | ||
| ...this.customAgent.options | ||
| } : options; | ||
| return new MockHttpSocket({ | ||
| connectionOptions: options, | ||
| createConnection: createConnection.bind(this.customAgent || this, createConnectionOptions, callback), | ||
| onRequest: this.onRequest.bind(this), | ||
| onResponse: this.onResponse.bind(this) | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/utils/getUrlByRequestOptions.ts | ||
| const logger$2 = new Logger("utils getUrlByRequestOptions"); | ||
| const DEFAULT_PATH = "/"; | ||
| const DEFAULT_PROTOCOL = "http:"; | ||
| const DEFAULT_HOSTNAME = "localhost"; | ||
| const SSL_PORT = 443; | ||
| function getAgent(options) { | ||
| return options.agent instanceof Agent ? options.agent : void 0; | ||
| } | ||
| function getProtocolByRequestOptions(options) { | ||
| if (options.protocol) return options.protocol; | ||
| const agentProtocol = getAgent(options)?.protocol; | ||
| if (agentProtocol) return agentProtocol; | ||
| const port = getPortByRequestOptions(options); | ||
| return options.cert || port === SSL_PORT ? "https:" : options.uri?.protocol || DEFAULT_PROTOCOL; | ||
| } | ||
| function getPortByRequestOptions(options) { | ||
| if (options.port) return Number(options.port); | ||
| const agent = getAgent(options); | ||
| if (agent?.options.port) return Number(agent.options.port); | ||
| if (agent?.defaultPort) return Number(agent.defaultPort); | ||
| } | ||
| function getAuthByRequestOptions(options) { | ||
| if (options.auth) { | ||
| const [username, password] = options.auth.split(":"); | ||
| return { | ||
| username, | ||
| password | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Returns true if host looks like an IPv6 address without surrounding brackets | ||
| * It assumes any host containing `:` is definitely not IPv4 and probably IPv6, | ||
| * but note that this could include invalid IPv6 addresses as well. | ||
| */ | ||
| function isRawIPv6Address(host) { | ||
| return host.includes(":") && !host.startsWith("[") && !host.endsWith("]"); | ||
| } | ||
| function getHostname(options) { | ||
| let host = options.hostname || options.host; | ||
| if (host) { | ||
| if (isRawIPv6Address(host)) host = `[${host}]`; | ||
| return new URL(`http://${host}`).hostname; | ||
| } | ||
| return DEFAULT_HOSTNAME; | ||
| } | ||
| /** | ||
| * Creates a `URL` instance from a given `RequestOptions` object. | ||
| */ | ||
| function getUrlByRequestOptions(options) { | ||
| logger$2.info("request options", options); | ||
| if (options.uri) { | ||
| logger$2.info("constructing url from explicitly provided \"options.uri\": %s", options.uri); | ||
| return new URL(options.uri.href); | ||
| } | ||
| logger$2.info("figuring out url from request options..."); | ||
| const protocol = getProtocolByRequestOptions(options); | ||
| logger$2.info("protocol", protocol); | ||
| const port = getPortByRequestOptions(options); | ||
| logger$2.info("port", port); | ||
| const hostname = getHostname(options); | ||
| logger$2.info("hostname", hostname); | ||
| const path = options.path || DEFAULT_PATH; | ||
| logger$2.info("path", path); | ||
| const credentials = getAuthByRequestOptions(options); | ||
| logger$2.info("credentials", credentials); | ||
| const authString = credentials ? `${credentials.username}:${credentials.password}@` : ""; | ||
| logger$2.info("auth string:", authString); | ||
| const portString = typeof port !== "undefined" ? `:${port}` : ""; | ||
| const url = new URL(`${protocol}//${hostname}${portString}${path}`); | ||
| url.username = credentials?.username || ""; | ||
| url.password = credentials?.password || ""; | ||
| logger$2.info("created url:", url); | ||
| return url; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/cloneObject.ts | ||
| const logger$1 = new Logger("cloneObject"); | ||
| function isPlainObject(obj) { | ||
| logger$1.info("is plain object?", obj); | ||
| if (obj == null || !obj.constructor?.name) { | ||
| logger$1.info("given object is undefined, not a plain object..."); | ||
| return false; | ||
| } | ||
| logger$1.info("checking the object constructor:", obj.constructor.name); | ||
| return obj.constructor.name === "Object"; | ||
| } | ||
| function cloneObject(obj) { | ||
| logger$1.info("cloning object:", obj); | ||
| const enumerableProperties = Object.entries(obj).reduce((acc, [key, value]) => { | ||
| logger$1.info("analyzing key-value pair:", key, value); | ||
| acc[key] = isPlainObject(value) ? cloneObject(value) : value; | ||
| return acc; | ||
| }, {}); | ||
| return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts | ||
| const logger = new Logger("http normalizeClientRequestArgs"); | ||
| function resolveRequestOptions(args, url) { | ||
| if (typeof args[1] === "undefined" || typeof args[1] === "function") { | ||
| logger.info("request options not provided, deriving from the url", url); | ||
| return urlToHttpOptions(url); | ||
| } | ||
| if (args[1]) { | ||
| logger.info("has custom RequestOptions!", args[1]); | ||
| const requestOptionsFromUrl = urlToHttpOptions(url); | ||
| logger.info("derived RequestOptions from the URL:", requestOptionsFromUrl); | ||
| /** | ||
| * Clone the request options to lock their state | ||
| * at the moment they are provided to `ClientRequest`. | ||
| * @see https://github.com/mswjs/interceptors/issues/86 | ||
| */ | ||
| logger.info("cloning RequestOptions..."); | ||
| const clonedRequestOptions = cloneObject(args[1]); | ||
| logger.info("successfully cloned RequestOptions!", clonedRequestOptions); | ||
| return { | ||
| ...requestOptionsFromUrl, | ||
| ...clonedRequestOptions | ||
| }; | ||
| } | ||
| logger.info("using an empty object as request options"); | ||
| return {}; | ||
| } | ||
| /** | ||
| * Overrides the given `URL` instance with the explicit properties provided | ||
| * on the `RequestOptions` object. The options object takes precedence, | ||
| * and will replace URL properties like "host", "path", and "port", if specified. | ||
| */ | ||
| function overrideUrlByRequestOptions(url, options) { | ||
| url.host = options.host || url.host; | ||
| url.hostname = options.hostname || url.hostname; | ||
| url.port = options.port ? options.port.toString() : url.port; | ||
| if (options.path) { | ||
| const parsedOptionsPath = parse(options.path, false); | ||
| url.pathname = parsedOptionsPath.pathname || ""; | ||
| url.search = parsedOptionsPath.search || ""; | ||
| } | ||
| return url; | ||
| } | ||
| function resolveCallback(args) { | ||
| return typeof args[1] === "function" ? args[1] : args[2]; | ||
| } | ||
| /** | ||
| * Normalizes parameters given to a `http.request` call | ||
| * so it always has a `URL` and `RequestOptions`. | ||
| */ | ||
| function normalizeClientRequestArgs(defaultProtocol, args) { | ||
| let url; | ||
| let options; | ||
| let callback; | ||
| logger.info("arguments", args); | ||
| logger.info("using default protocol:", defaultProtocol); | ||
| if (args.length === 0) { | ||
| const url$1 = new URL$1("http://localhost"); | ||
| return [url$1, resolveRequestOptions(args, url$1)]; | ||
| } | ||
| if (typeof args[0] === "string") { | ||
| logger.info("first argument is a location string:", args[0]); | ||
| url = new URL$1(args[0]); | ||
| logger.info("created a url:", url); | ||
| const requestOptionsFromUrl = urlToHttpOptions(url); | ||
| logger.info("request options from url:", requestOptionsFromUrl); | ||
| options = resolveRequestOptions(args, url); | ||
| logger.info("resolved request options:", options); | ||
| callback = resolveCallback(args); | ||
| } else if (args[0] instanceof URL$1) { | ||
| url = args[0]; | ||
| logger.info("first argument is a URL:", url); | ||
| if (typeof args[1] !== "undefined" && isObject(args[1])) url = overrideUrlByRequestOptions(url, args[1]); | ||
| options = resolveRequestOptions(args, url); | ||
| logger.info("derived request options:", options); | ||
| callback = resolveCallback(args); | ||
| } else if ("hash" in args[0] && !("method" in args[0])) { | ||
| const [legacyUrl] = args; | ||
| logger.info("first argument is a legacy URL:", legacyUrl); | ||
| if (legacyUrl.hostname === null) { | ||
| /** | ||
| * We are dealing with a relative url, so use the path as an "option" and | ||
| * merge in any existing options, giving priority to existing options -- i.e. a path in any | ||
| * existing options will take precedence over the one contained in the url. This is consistent | ||
| * with the behaviour in ClientRequest. | ||
| * @see https://github.com/nodejs/node/blob/d84f1312915fe45fe0febe888db692c74894c382/lib/_http_client.js#L122 | ||
| */ | ||
| logger.info("given legacy URL is relative (no hostname)"); | ||
| return isObject(args[1]) ? normalizeClientRequestArgs(defaultProtocol, [{ | ||
| path: legacyUrl.path, | ||
| ...args[1] | ||
| }, args[2]]) : normalizeClientRequestArgs(defaultProtocol, [{ path: legacyUrl.path }, args[1]]); | ||
| } | ||
| logger.info("given legacy url is absolute"); | ||
| const resolvedUrl = new URL$1(legacyUrl.href); | ||
| return args[1] === void 0 ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl]) : typeof args[1] === "function" ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl, args[1]]) : normalizeClientRequestArgs(defaultProtocol, [ | ||
| resolvedUrl, | ||
| args[1], | ||
| args[2] | ||
| ]); | ||
| } else if (isObject(args[0])) { | ||
| options = { ...args[0] }; | ||
| logger.info("first argument is RequestOptions:", options); | ||
| options.protocol = options.protocol || defaultProtocol; | ||
| logger.info("normalized request options:", options); | ||
| url = getUrlByRequestOptions(options); | ||
| logger.info("created a URL from RequestOptions:", url.href); | ||
| callback = resolveCallback(args); | ||
| } else throw new Error(`Failed to construct ClientRequest with these parameters: ${args}`); | ||
| options.protocol = options.protocol || url.protocol; | ||
| options.method = options.method || "GET"; | ||
| /** | ||
| * Ensure that the default Agent is always set. | ||
| * This prevents the protocol mismatch for requests with { agent: false }, | ||
| * where the global Agent is inferred. | ||
| * @see https://github.com/mswjs/msw/issues/1150 | ||
| * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L130 | ||
| * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L157-L159 | ||
| */ | ||
| if (!options._defaultAgent) { | ||
| logger.info("has no default agent, setting the default agent for \"%s\"", options.protocol); | ||
| options._defaultAgent = options.protocol === "https:" ? globalAgent$1 : globalAgent; | ||
| } | ||
| logger.info("successfully resolved url:", url.href); | ||
| logger.info("successfully resolved options:", options); | ||
| logger.info("successfully resolved callback:", callback); | ||
| /** | ||
| * @note If the user-provided URL is not a valid URL in Node.js, | ||
| * (e.g. the one provided by the JSDOM polyfills), case it to | ||
| * string. Otherwise, this throws on Node.js incompatibility | ||
| * (`ERR_INVALID_ARG_TYPE` on the connection listener) | ||
| * @see https://github.com/node-fetch/node-fetch/issues/1376#issuecomment-966435555 | ||
| */ | ||
| if (!(url instanceof URL$1)) url = url.toString(); | ||
| return [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/index.ts | ||
| var ClientRequestInterceptor = class ClientRequestInterceptor extends Interceptor { | ||
| static { | ||
| this.symbol = Symbol("client-request-interceptor"); | ||
| } | ||
| constructor() { | ||
| super(ClientRequestInterceptor.symbol); | ||
| this.onRequest = async ({ request, socket }) => { | ||
| const controller = new RequestController(request, { | ||
| passthrough() { | ||
| socket.passthrough(); | ||
| }, | ||
| async respondWith(response) { | ||
| await socket.respondWith(response); | ||
| }, | ||
| errorWith(reason) { | ||
| if (reason instanceof Error) socket.errorWith(reason); | ||
| } | ||
| }); | ||
| await handleRequest({ | ||
| request, | ||
| requestId: Reflect.get(request, kRequestId), | ||
| controller, | ||
| emitter: this.emitter | ||
| }); | ||
| }; | ||
| this.onResponse = async ({ requestId, request, response, isMockedResponse }) => { | ||
| return emitAsync(this.emitter, "response", { | ||
| requestId, | ||
| request, | ||
| response, | ||
| isMockedResponse | ||
| }); | ||
| }; | ||
| } | ||
| setup() { | ||
| const { ClientRequest: OriginalClientRequest, get: originalGet, request: originalRequest } = http; | ||
| const { get: originalHttpsGet, request: originalHttpsRequest } = https; | ||
| const onRequest = this.onRequest.bind(this); | ||
| const onResponse = this.onResponse.bind(this); | ||
| http.ClientRequest = new Proxy(http.ClientRequest, { construct: (target, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new (options.protocol === "https:" ? MockHttpsAgent : MockAgent)({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.construct(target, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| http.request = new Proxy(http.request, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new MockAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| http.get = new Proxy(http.get, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new MockAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| https.request = new Proxy(https.request, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("https:", args); | ||
| options.agent = new MockHttpsAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| https.get = new Proxy(https.get, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("https:", args); | ||
| options.agent = new MockHttpsAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| recordRawFetchHeaders(); | ||
| this.subscriptions.push(() => { | ||
| http.ClientRequest = OriginalClientRequest; | ||
| http.get = originalGet; | ||
| http.request = originalRequest; | ||
| https.get = originalHttpsGet; | ||
| https.request = originalHttpsRequest; | ||
| restoreHeadersPrototype(); | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { ClientRequestInterceptor as t }; | ||
| //# sourceMappingURL=ClientRequest-BquJl6T-.mjs.map |
Sorry, the diff of this file is too big to display
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-BAlG765A.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-B6znU6il.cjs'); | ||
| const require_handleRequest = require('./handleRequest-DUPo40Df.cjs'); | ||
| const require_node = require('./node-DIKcnzhK.cjs'); | ||
| let _open_draft_logger = require("@open-draft/logger"); | ||
| let outvariant = require("outvariant"); | ||
| let node_http = require("node:http"); | ||
| node_http = require_chunk.__toESM(node_http); | ||
| let node_https = require("node:https"); | ||
| node_https = require_chunk.__toESM(node_https); | ||
| let node_net = require("node:net"); | ||
| node_net = require_chunk.__toESM(node_net); | ||
| let _http_common = require("_http_common"); | ||
| let node_stream = require("node:stream"); | ||
| let node_url = require("node:url"); | ||
| let http = require("http"); | ||
| //#region src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts | ||
| /** | ||
| * Normalizes the arguments provided to the `Writable.prototype.write()` | ||
| * and `Writable.prototype.end()`. | ||
| */ | ||
| function normalizeSocketWriteArgs(args) { | ||
| const normalized = [ | ||
| args[0], | ||
| void 0, | ||
| void 0 | ||
| ]; | ||
| if (typeof args[1] === "string") normalized[1] = args[1]; | ||
| else if (typeof args[1] === "function") normalized[2] = args[1]; | ||
| if (typeof args[2] === "function") normalized[2] = args[2]; | ||
| return normalized; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/Socket/MockSocket.ts | ||
| var MockSocket = class extends node_net.default.Socket { | ||
| constructor(options) { | ||
| super(); | ||
| this.options = options; | ||
| this.connecting = false; | ||
| this.connect(); | ||
| this._final = (callback) => { | ||
| callback(null); | ||
| }; | ||
| } | ||
| connect() { | ||
| this.connecting = true; | ||
| return this; | ||
| } | ||
| write(...args) { | ||
| const [chunk, encoding, callback] = normalizeSocketWriteArgs(args); | ||
| this.options.write(chunk, encoding, callback); | ||
| return true; | ||
| } | ||
| end(...args) { | ||
| const [chunk, encoding, callback] = normalizeSocketWriteArgs(args); | ||
| this.options.write(chunk, encoding, callback); | ||
| return super.end.apply(this, args); | ||
| } | ||
| push(chunk, encoding) { | ||
| this.options.read(chunk, encoding); | ||
| return super.push(chunk, encoding); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts | ||
| function baseUrlFromConnectionOptions(options) { | ||
| if ("href" in options) return new URL(options.href); | ||
| const protocol = options.port === 443 ? "https:" : "http:"; | ||
| const host = options.host; | ||
| const url = new URL(`${protocol}//${host}`); | ||
| if (options.port) url.port = options.port.toString(); | ||
| if (options.path) url.pathname = options.path; | ||
| if (options.auth) { | ||
| const [username, password] = options.auth.split(":"); | ||
| url.username = username; | ||
| url.password = password; | ||
| } | ||
| return url; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/recordRawHeaders.ts | ||
| const kRawHeaders = Symbol("kRawHeaders"); | ||
| const kRestorePatches = Symbol("kRestorePatches"); | ||
| function recordRawHeader(headers, args, behavior) { | ||
| ensureRawHeadersSymbol(headers, []); | ||
| const rawHeaders = Reflect.get(headers, kRawHeaders); | ||
| if (behavior === "set") { | ||
| for (let index = rawHeaders.length - 1; index >= 0; index--) if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) rawHeaders.splice(index, 1); | ||
| } | ||
| rawHeaders.push(args); | ||
| } | ||
| /** | ||
| * Define the raw headers symbol on the given `Headers` instance. | ||
| * If the symbol already exists, this function does nothing. | ||
| */ | ||
| function ensureRawHeadersSymbol(headers, rawHeaders) { | ||
| if (Reflect.has(headers, kRawHeaders)) return; | ||
| defineRawHeadersSymbol(headers, rawHeaders); | ||
| } | ||
| /** | ||
| * Define the raw headers symbol on the given `Headers` instance. | ||
| * If the symbol already exists, it gets overridden. | ||
| */ | ||
| function defineRawHeadersSymbol(headers, rawHeaders) { | ||
| Object.defineProperty(headers, kRawHeaders, { | ||
| value: rawHeaders, | ||
| enumerable: false, | ||
| configurable: true | ||
| }); | ||
| } | ||
| /** | ||
| * Patch the global `Headers` class to store raw headers. | ||
| * This is for compatibility with `IncomingMessage.prototype.rawHeaders`. | ||
| * | ||
| * @note Node.js has their own raw headers symbol but it | ||
| * only records the first header name in case of multi-value headers. | ||
| * Any other headers are normalized before comparing. This makes it | ||
| * incompatible with the `rawHeaders` format. | ||
| * | ||
| * let h = new Headers() | ||
| * h.append('X-Custom', 'one') | ||
| * h.append('x-custom', 'two') | ||
| * h[Symbol('headers map')] // Map { 'X-Custom' => 'one, two' } | ||
| */ | ||
| function recordRawFetchHeaders() { | ||
| if (Reflect.get(Headers, kRestorePatches)) return Reflect.get(Headers, kRestorePatches); | ||
| const { Headers: OriginalHeaders, Request: OriginalRequest, Response: OriginalResponse } = globalThis; | ||
| const { set, append, delete: headersDeleteMethod } = Headers.prototype; | ||
| Object.defineProperty(Headers, kRestorePatches, { | ||
| value: () => { | ||
| Headers.prototype.set = set; | ||
| Headers.prototype.append = append; | ||
| Headers.prototype.delete = headersDeleteMethod; | ||
| globalThis.Headers = OriginalHeaders; | ||
| globalThis.Request = OriginalRequest; | ||
| globalThis.Response = OriginalResponse; | ||
| Object.setPrototypeOf(require_fetchUtils.FetchRequest, OriginalRequest); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchRequest.prototype, OriginalRequest.prototype); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchResponse, OriginalResponse); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchResponse.prototype, OriginalResponse.prototype); | ||
| Reflect.deleteProperty(Headers, kRestorePatches); | ||
| }, | ||
| enumerable: false, | ||
| configurable: true | ||
| }); | ||
| Object.defineProperty(globalThis, "Headers", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Headers, { construct(target, args, newTarget) { | ||
| const headersInit = args[0] || []; | ||
| if (headersInit instanceof Headers && Reflect.has(headersInit, kRawHeaders)) { | ||
| const headers$1 = Reflect.construct(target, [Reflect.get(headersInit, kRawHeaders)], newTarget); | ||
| ensureRawHeadersSymbol(headers$1, [...Reflect.get(headersInit, kRawHeaders)]); | ||
| return headers$1; | ||
| } | ||
| const headers = Reflect.construct(target, args, newTarget); | ||
| if (!Reflect.has(headers, kRawHeaders)) ensureRawHeadersSymbol(headers, Array.isArray(headersInit) ? headersInit : Object.entries(headersInit)); | ||
| return headers; | ||
| } }) | ||
| }); | ||
| Headers.prototype.set = new Proxy(Headers.prototype.set, { apply(target, thisArg, args) { | ||
| recordRawHeader(thisArg, args, "set"); | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Headers.prototype.append = new Proxy(Headers.prototype.append, { apply(target, thisArg, args) { | ||
| recordRawHeader(thisArg, args, "append"); | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Headers.prototype.delete = new Proxy(Headers.prototype.delete, { apply(target, thisArg, args) { | ||
| const rawHeaders = Reflect.get(thisArg, kRawHeaders); | ||
| if (rawHeaders) { | ||
| for (let index = rawHeaders.length - 1; index >= 0; index--) if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) rawHeaders.splice(index, 1); | ||
| } | ||
| return Reflect.apply(target, thisArg, args); | ||
| } }); | ||
| Object.defineProperty(globalThis, "Request", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Request, { construct(target, args, newTarget) { | ||
| const request = Reflect.construct(target, args, newTarget); | ||
| const inferredRawHeaders = []; | ||
| if (typeof args[0] === "object" && args[0].headers != null) inferredRawHeaders.push(...inferRawHeaders(args[0].headers)); | ||
| if (typeof args[1] === "object" && args[1].headers != null) inferredRawHeaders.push(...inferRawHeaders(args[1].headers)); | ||
| if (inferredRawHeaders.length > 0) ensureRawHeadersSymbol(request.headers, inferredRawHeaders); | ||
| return request; | ||
| } }) | ||
| }); | ||
| Object.defineProperty(globalThis, "Response", { | ||
| enumerable: true, | ||
| writable: true, | ||
| value: new Proxy(Response, { construct(target, args, newTarget) { | ||
| const response = Reflect.construct(target, args, newTarget); | ||
| if (typeof args[1] === "object" && args[1].headers != null) ensureRawHeadersSymbol(response.headers, inferRawHeaders(args[1].headers)); | ||
| return response; | ||
| } }) | ||
| }); | ||
| /** | ||
| * Re-parent FetchRequest/FetchResponse so their `super()` calls go | ||
| * through the proxied globalThis.Request/Response above. Without this, | ||
| * FetchRequest extends the statically-captured (original) Request, | ||
| * bypassing the construct proxy that records raw headers. | ||
| */ | ||
| Object.setPrototypeOf(require_fetchUtils.FetchRequest, globalThis.Request); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchRequest.prototype, globalThis.Request.prototype); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchResponse, globalThis.Response); | ||
| Object.setPrototypeOf(require_fetchUtils.FetchResponse.prototype, globalThis.Response.prototype); | ||
| } | ||
| function restoreHeadersPrototype() { | ||
| if (!Reflect.get(Headers, kRestorePatches)) return; | ||
| Reflect.get(Headers, kRestorePatches)(); | ||
| } | ||
| function getRawFetchHeaders(headers) { | ||
| if (!Reflect.has(headers, kRawHeaders)) return Array.from(headers.entries()); | ||
| const rawHeaders = Reflect.get(headers, kRawHeaders); | ||
| return rawHeaders.length > 0 ? rawHeaders : Array.from(headers.entries()); | ||
| } | ||
| /** | ||
| * Infers the raw headers from the given `HeadersInit` provided | ||
| * to the Request/Response constructor. | ||
| * | ||
| * If the `init.headers` is a Headers instance, use it directly. | ||
| * That means the headers were created standalone and already have | ||
| * the raw headers stored. | ||
| * If the `init.headers` is a HeadersInit, create a new Headers | ||
| * instance out of it. | ||
| */ | ||
| function inferRawHeaders(headers) { | ||
| if (headers instanceof Headers) return Reflect.get(headers, kRawHeaders) || []; | ||
| return Reflect.get(new Headers(headers), kRawHeaders); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/parserUtils.ts | ||
| /** | ||
| * @see https://github.com/nodejs/node/blob/f3adc11e37b8bfaaa026ea85c1cf22e3a0e29ae9/lib/_http_common.js#L180 | ||
| */ | ||
| function freeParser(parser, socket) { | ||
| if (parser._consumed) parser.unconsume(); | ||
| parser._headers = []; | ||
| parser._url = ""; | ||
| parser.socket = null; | ||
| parser.incoming = null; | ||
| parser.outgoing = null; | ||
| parser.maxHeaderPairs = 2e3; | ||
| parser._consumed = false; | ||
| parser.onIncoming = null; | ||
| parser[_http_common.HTTPParser.kOnHeaders] = null; | ||
| parser[_http_common.HTTPParser.kOnHeadersComplete] = null; | ||
| parser[_http_common.HTTPParser.kOnMessageBegin] = null; | ||
| parser[_http_common.HTTPParser.kOnMessageComplete] = null; | ||
| parser[_http_common.HTTPParser.kOnBody] = null; | ||
| parser[_http_common.HTTPParser.kOnExecute] = null; | ||
| parser[_http_common.HTTPParser.kOnTimeout] = null; | ||
| parser.remove(); | ||
| parser.free(); | ||
| if (socket) | ||
| /** | ||
| * @note Unassigning the socket's parser will fail this assertion | ||
| * if there's still some data being processed on the socket: | ||
| * @see https://github.com/nodejs/node/blob/4e1f39b678b37017ac9baa0971e3aeecd3b67b51/lib/_http_client.js#L613 | ||
| */ | ||
| if (socket.destroyed) socket.parser = null; | ||
| else socket.once("end", () => { | ||
| socket.parser = null; | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/MockHttpSocket.ts | ||
| const kRequestId = Symbol("kRequestId"); | ||
| var MockHttpSocket = class extends MockSocket { | ||
| constructor(options) { | ||
| super({ | ||
| write: (chunk, encoding, callback) => { | ||
| if (this.socketState !== "passthrough") this.writeBuffer.push([ | ||
| chunk, | ||
| encoding, | ||
| callback | ||
| ]); | ||
| if (chunk) { | ||
| /** | ||
| * Forward any writes to the mock socket to the underlying original socket. | ||
| * This ensures functional duplex connections, like WebSocket. | ||
| * @see https://github.com/mswjs/interceptors/issues/682 | ||
| */ | ||
| if (this.socketState === "passthrough") this.originalSocket?.write(chunk, encoding, callback); | ||
| this.requestParser.execute(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding)); | ||
| } | ||
| }, | ||
| read: (chunk) => { | ||
| if (chunk !== null) | ||
| /** | ||
| * @todo We need to free the parser if the connection has been | ||
| * upgraded to a non-HTTP protocol. It won't be able to parse data | ||
| * from that point onward anyway. No need to keep it in memory. | ||
| */ | ||
| this.responseParser.execute(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); | ||
| } | ||
| }); | ||
| this.requestRawHeadersBuffer = []; | ||
| this.responseRawHeadersBuffer = []; | ||
| this.writeBuffer = []; | ||
| this.socketState = "unknown"; | ||
| this.onRequestHeaders = (rawHeaders) => { | ||
| this.requestRawHeadersBuffer.push(...rawHeaders); | ||
| }; | ||
| this.onRequestStart = (versionMajor, versionMinor, rawHeaders, _, path, __, ___, ____, shouldKeepAlive) => { | ||
| this.shouldKeepAlive = shouldKeepAlive; | ||
| const url = new URL(path || "", this.baseUrl); | ||
| const method = this.connectionOptions.method?.toUpperCase() || "GET"; | ||
| const headers = require_fetchUtils.FetchResponse.parseRawHeaders([...this.requestRawHeadersBuffer, ...rawHeaders || []]); | ||
| this.requestRawHeadersBuffer.length = 0; | ||
| const canHaveBody = method !== "GET" && method !== "HEAD"; | ||
| if (url.username || url.password) { | ||
| if (!headers.has("authorization")) headers.set("authorization", `Basic ${url.username}:${url.password}`); | ||
| url.username = ""; | ||
| url.password = ""; | ||
| } | ||
| this.requestStream = new node_stream.Readable({ read: () => { | ||
| this.flushWriteBuffer(); | ||
| } }); | ||
| const requestId = require_fetchUtils.createRequestId(); | ||
| this.request = new require_fetchUtils.FetchRequest(url, { | ||
| method, | ||
| headers, | ||
| credentials: "same-origin", | ||
| duplex: canHaveBody ? "half" : void 0, | ||
| body: canHaveBody ? node_stream.Readable.toWeb(this.requestStream) : null | ||
| }); | ||
| Reflect.set(this.request, kRequestId, requestId); | ||
| require_getRawRequest.setRawRequest(this.request, Reflect.get(this, "_httpMessage")); | ||
| require_node.setRawRequestBodyStream(this.request, this.requestStream); | ||
| /** | ||
| * @fixme Stop relying on the "X-Request-Id" request header | ||
| * to figure out if one interceptor has been invoked within another. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| if (this.request.headers.has(require_fetchUtils.INTERNAL_REQUEST_ID_HEADER_NAME)) { | ||
| this.passthrough(); | ||
| return; | ||
| } | ||
| this.onRequest({ | ||
| requestId, | ||
| request: this.request, | ||
| socket: this | ||
| }); | ||
| }; | ||
| this.onResponseHeaders = (rawHeaders) => { | ||
| this.responseRawHeadersBuffer.push(...rawHeaders); | ||
| }; | ||
| this.onResponseStart = (versionMajor, versionMinor, rawHeaders, method, url, status, statusText) => { | ||
| const headers = require_fetchUtils.FetchResponse.parseRawHeaders([...this.responseRawHeadersBuffer, ...rawHeaders || []]); | ||
| this.responseRawHeadersBuffer.length = 0; | ||
| const response = new require_fetchUtils.FetchResponse( | ||
| /** | ||
| * @note The Fetch API response instance exposed to the consumer | ||
| * is created over the response stream of the HTTP parser. It is NOT | ||
| * related to the Socket instance. This way, you can read response body | ||
| * in response listener while the Socket instance delays the emission | ||
| * of "end" and other events until those response listeners are finished. | ||
| */ | ||
| require_fetchUtils.FetchResponse.isResponseWithBody(status) ? node_stream.Readable.toWeb(this.responseStream = new node_stream.Readable({ read() {} })) : null, | ||
| { | ||
| url, | ||
| status, | ||
| statusText, | ||
| headers | ||
| } | ||
| ); | ||
| (0, outvariant.invariant)(this.request, "Failed to handle a response: request does not exist"); | ||
| require_fetchUtils.FetchResponse.setUrl(this.request.url, response); | ||
| /** | ||
| * @fixme Stop relying on the "X-Request-Id" request header | ||
| * to figure out if one interceptor has been invoked within another. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| if (this.request.headers.has(require_fetchUtils.INTERNAL_REQUEST_ID_HEADER_NAME)) return; | ||
| this.responseListenersPromise = this.onResponse({ | ||
| response, | ||
| isMockedResponse: this.socketState === "mock", | ||
| requestId: Reflect.get(this.request, kRequestId), | ||
| request: this.request, | ||
| socket: this | ||
| }); | ||
| }; | ||
| this.connectionOptions = options.connectionOptions; | ||
| this.createConnection = options.createConnection; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions); | ||
| this.requestParser = new _http_common.HTTPParser(); | ||
| this.requestParser.initialize(_http_common.HTTPParser.REQUEST, {}); | ||
| this.requestParser[_http_common.HTTPParser.kOnHeaders] = this.onRequestHeaders.bind(this); | ||
| this.requestParser[_http_common.HTTPParser.kOnHeadersComplete] = this.onRequestStart.bind(this); | ||
| this.requestParser[_http_common.HTTPParser.kOnBody] = this.onRequestBody.bind(this); | ||
| this.requestParser[_http_common.HTTPParser.kOnMessageComplete] = this.onRequestEnd.bind(this); | ||
| this.responseParser = new _http_common.HTTPParser(); | ||
| this.responseParser.initialize(_http_common.HTTPParser.RESPONSE, {}); | ||
| this.responseParser[_http_common.HTTPParser.kOnHeaders] = this.onResponseHeaders.bind(this); | ||
| this.responseParser[_http_common.HTTPParser.kOnHeadersComplete] = this.onResponseStart.bind(this); | ||
| this.responseParser[_http_common.HTTPParser.kOnBody] = this.onResponseBody.bind(this); | ||
| this.responseParser[_http_common.HTTPParser.kOnMessageComplete] = this.onResponseEnd.bind(this); | ||
| this.once("finish", () => freeParser(this.requestParser, this)); | ||
| if (this.baseUrl.protocol === "https:") { | ||
| Reflect.set(this, "encrypted", true); | ||
| Reflect.set(this, "authorized", false); | ||
| Reflect.set(this, "getProtocol", () => "TLSv1.3"); | ||
| Reflect.set(this, "getSession", () => void 0); | ||
| Reflect.set(this, "isSessionReused", () => false); | ||
| Reflect.set(this, "getCipher", () => ({ | ||
| name: "AES256-SHA", | ||
| standardName: "TLS_RSA_WITH_AES_256_CBC_SHA", | ||
| version: "TLSv1.3" | ||
| })); | ||
| } | ||
| } | ||
| emit(event, ...args) { | ||
| const emitEvent = super.emit.bind(this, event, ...args); | ||
| if (this.responseListenersPromise) { | ||
| this.responseListenersPromise.finally(emitEvent); | ||
| return this.listenerCount(event) > 0; | ||
| } | ||
| return emitEvent(); | ||
| } | ||
| destroy(error) { | ||
| freeParser(this.responseParser, this); | ||
| if (error) this.emit("error", error); | ||
| return super.destroy(error); | ||
| } | ||
| /** | ||
| * Establish this Socket connection as-is and pipe | ||
| * its data/events through this Socket. | ||
| */ | ||
| passthrough() { | ||
| this.socketState = "passthrough"; | ||
| if (this.destroyed) return; | ||
| const socket = this.createConnection(); | ||
| this.originalSocket = socket; | ||
| /** | ||
| * @note Inherit the original socket's connection handle. | ||
| * Without this, each push to the mock socket results in a | ||
| * new "connection" listener being added (i.e. buffering pushes). | ||
| * @see https://github.com/nodejs/node/blob/b18153598b25485ce4f54d0c5cb830a9457691ee/lib/net.js#L734 | ||
| */ | ||
| if ("_handle" in socket) Object.defineProperty(this, "_handle", { | ||
| value: socket._handle, | ||
| enumerable: true, | ||
| writable: true | ||
| }); | ||
| this.once("close", () => { | ||
| socket.removeAllListeners(); | ||
| if (!socket.destroyed) socket.destroy(); | ||
| this.originalSocket = void 0; | ||
| }); | ||
| this.address = socket.address.bind(socket); | ||
| let writeArgs; | ||
| let headersWritten = false; | ||
| while (writeArgs = this.writeBuffer.shift()) if (writeArgs !== void 0) { | ||
| if (!headersWritten) { | ||
| const [chunk, encoding, callback] = writeArgs; | ||
| const chunkString = chunk.toString(); | ||
| const chunkBeforeRequestHeaders = chunkString.slice(0, chunkString.indexOf("\r\n") + 2); | ||
| const chunkAfterRequestHeaders = chunkString.slice(chunk.indexOf("\r\n\r\n")); | ||
| const headersChunk = `${chunkBeforeRequestHeaders}${getRawFetchHeaders(this.request.headers).filter(([name]) => { | ||
| return name.toLowerCase() !== require_fetchUtils.INTERNAL_REQUEST_ID_HEADER_NAME; | ||
| }).map(([name, value]) => `${name}: ${value}`).join("\r\n")}${chunkAfterRequestHeaders}`; | ||
| socket.write(headersChunk, encoding, callback); | ||
| headersWritten = true; | ||
| continue; | ||
| } | ||
| socket.write(...writeArgs); | ||
| } | ||
| if (Reflect.get(socket, "encrypted")) [ | ||
| "encrypted", | ||
| "authorized", | ||
| "getProtocol", | ||
| "getSession", | ||
| "isSessionReused", | ||
| "getCipher" | ||
| ].forEach((propertyName) => { | ||
| Object.defineProperty(this, propertyName, { | ||
| enumerable: true, | ||
| get: () => { | ||
| const value = Reflect.get(socket, propertyName); | ||
| return typeof value === "function" ? value.bind(socket) : value; | ||
| } | ||
| }); | ||
| }); | ||
| socket.on("lookup", (...args) => this.emit("lookup", ...args)).on("connect", () => { | ||
| this.connecting = socket.connecting; | ||
| this.emit("connect"); | ||
| }).on("secureConnect", () => this.emit("secureConnect")).on("secure", () => this.emit("secure")).on("session", (session) => this.emit("session", session)).on("ready", () => this.emit("ready")).on("drain", () => this.emit("drain")).on("data", (chunk) => { | ||
| this.push(chunk); | ||
| }).on("error", (error) => { | ||
| Reflect.set(this, "_hadError", Reflect.get(socket, "_hadError")); | ||
| this.emit("error", error); | ||
| }).on("resume", () => this.emit("resume")).on("timeout", () => this.emit("timeout")).on("prefinish", () => this.emit("prefinish")).on("finish", () => this.emit("finish")).on("close", (hadError) => this.emit("close", hadError)).on("end", () => this.emit("end")); | ||
| } | ||
| /** | ||
| * Convert the given Fetch API `Response` instance to an | ||
| * HTTP message and push it to the socket. | ||
| */ | ||
| async respondWith(response) { | ||
| if (this.destroyed) return; | ||
| (0, outvariant.invariant)(this.socketState !== "mock", "[MockHttpSocket] Failed to respond to the \"%s %s\" request with \"%s %s\": the request has already been handled", this.request?.method, this.request?.url, response.status, response.statusText); | ||
| if (require_handleRequest.isPropertyAccessible(response, "type") && response.type === "error") { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| this.mockConnect(); | ||
| this.socketState = "mock"; | ||
| this.flushWriteBuffer(); | ||
| const serverResponse = new node_http.ServerResponse(new node_http.IncomingMessage(this)); | ||
| /** | ||
| * Assign a mock socket instance to the server response to | ||
| * spy on the response chunk writes. Push the transformed response chunks | ||
| * to this `MockHttpSocket` instance to trigger the "data" event. | ||
| * @note Providing the same `MockSocket` instance when creating `ServerResponse` | ||
| * does not have the same effect. | ||
| * @see https://github.com/nodejs/node/blob/10099bb3f7fd97bb9dd9667188426866b3098e07/test/parallel/test-http-server-response-standalone.js#L32 | ||
| */ | ||
| serverResponse.assignSocket(new MockSocket({ | ||
| write: (chunk, encoding, callback) => { | ||
| this.push(chunk, encoding); | ||
| callback?.(); | ||
| }, | ||
| read() {} | ||
| })); | ||
| /** | ||
| * @note Remove the `Connection` and `Date` response headers | ||
| * injected by `ServerResponse` by default. Those are required | ||
| * from the server but the interceptor is NOT technically a server. | ||
| * It's confusing to add response headers that the developer didn't | ||
| * specify themselves. They can always add these if they wish. | ||
| * @see https://www.rfc-editor.org/rfc/rfc9110#field.date | ||
| * @see https://www.rfc-editor.org/rfc/rfc9110#field.connection | ||
| */ | ||
| serverResponse.removeHeader("connection"); | ||
| serverResponse.removeHeader("date"); | ||
| const rawResponseHeaders = getRawFetchHeaders(response.headers); | ||
| /** | ||
| * @note Call `.writeHead` in order to set the raw response headers | ||
| * in the same case as they were provided by the developer. Using | ||
| * `.setHeader()`/`.appendHeader()` normalizes header names. | ||
| */ | ||
| serverResponse.writeHead(response.status, response.statusText || node_http.STATUS_CODES[response.status], rawResponseHeaders); | ||
| this.once("error", () => { | ||
| serverResponse.destroy(); | ||
| }); | ||
| if (response.body) try { | ||
| const reader = response.body.getReader(); | ||
| while (true) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) { | ||
| serverResponse.end(); | ||
| break; | ||
| } | ||
| serverResponse.write(value); | ||
| } | ||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| serverResponse.destroy(); | ||
| /** | ||
| * @note Destroy the request socket gracefully. | ||
| * Response stream errors do NOT produce request errors. | ||
| */ | ||
| this.destroy(); | ||
| return; | ||
| } | ||
| serverResponse.destroy(); | ||
| throw error; | ||
| } | ||
| else serverResponse.end(); | ||
| if (!this.shouldKeepAlive) { | ||
| this.emit("readable"); | ||
| /** | ||
| * @todo @fixme This is likely a hack. | ||
| * Since we push null to the socket, it never propagates to the | ||
| * parser, and the parser never calls "onResponseEnd" to close | ||
| * the response stream. We are closing the stream here manually | ||
| * but that shouldn't be the case. | ||
| */ | ||
| this.responseStream?.push(null); | ||
| this.push(null); | ||
| } | ||
| } | ||
| /** | ||
| * Close this socket connection with the given error. | ||
| */ | ||
| errorWith(error) { | ||
| this.destroy(error); | ||
| } | ||
| mockConnect() { | ||
| this.connecting = false; | ||
| const isIPv6 = node_net.default.isIPv6(this.connectionOptions.hostname) || this.connectionOptions.family === 6; | ||
| const addressInfo = { | ||
| address: isIPv6 ? "::1" : "127.0.0.1", | ||
| family: isIPv6 ? "IPv6" : "IPv4", | ||
| port: this.connectionOptions.port | ||
| }; | ||
| this.address = () => addressInfo; | ||
| this.emit("lookup", null, addressInfo.address, addressInfo.family === "IPv6" ? 6 : 4, this.connectionOptions.host); | ||
| this.emit("connect"); | ||
| this.emit("ready"); | ||
| if (this.baseUrl.protocol === "https:") { | ||
| this.emit("secure"); | ||
| this.emit("secureConnect"); | ||
| this.emit("session", this.connectionOptions.session || Buffer.from("mock-session-renegotiate")); | ||
| this.emit("session", Buffer.from("mock-session-resume")); | ||
| } | ||
| } | ||
| flushWriteBuffer() { | ||
| for (const writeCall of this.writeBuffer) if (typeof writeCall[2] === "function") { | ||
| writeCall[2](); | ||
| /** | ||
| * @note Remove the callback from the write call | ||
| * so it doesn't get called twice on passthrough | ||
| * if `request.end()` was called within `request.write()`. | ||
| * @see https://github.com/mswjs/interceptors/issues/684 | ||
| */ | ||
| writeCall[2] = void 0; | ||
| } | ||
| } | ||
| onRequestBody(chunk) { | ||
| (0, outvariant.invariant)(this.requestStream, "Failed to write to a request stream: stream does not exist"); | ||
| this.requestStream.push(chunk); | ||
| } | ||
| onRequestEnd() { | ||
| if (this.requestStream) this.requestStream.push(null); | ||
| } | ||
| onResponseBody(chunk) { | ||
| (0, outvariant.invariant)(this.responseStream, "Failed to write to a response stream: stream does not exist"); | ||
| this.responseStream.push(chunk); | ||
| } | ||
| onResponseEnd() { | ||
| if (this.responseStream) this.responseStream.push(null); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/agents.ts | ||
| var MockAgent = class extends node_http.default.Agent { | ||
| constructor(options) { | ||
| super(); | ||
| this.customAgent = options.customAgent; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| } | ||
| createConnection(options, callback) { | ||
| const createConnection = this.customAgent instanceof node_http.default.Agent ? this.customAgent.createConnection : super.createConnection; | ||
| const createConnectionOptions = this.customAgent instanceof node_http.default.Agent ? { | ||
| ...options, | ||
| ...this.customAgent.options | ||
| } : options; | ||
| return new MockHttpSocket({ | ||
| connectionOptions: options, | ||
| createConnection: createConnection.bind(this.customAgent || this, createConnectionOptions, callback), | ||
| onRequest: this.onRequest.bind(this), | ||
| onResponse: this.onResponse.bind(this) | ||
| }); | ||
| } | ||
| }; | ||
| var MockHttpsAgent = class extends node_https.default.Agent { | ||
| constructor(options) { | ||
| super(); | ||
| this.customAgent = options.customAgent; | ||
| this.onRequest = options.onRequest; | ||
| this.onResponse = options.onResponse; | ||
| } | ||
| createConnection(options, callback) { | ||
| const createConnection = this.customAgent instanceof node_http.default.Agent ? this.customAgent.createConnection : super.createConnection; | ||
| const createConnectionOptions = this.customAgent instanceof node_http.default.Agent ? { | ||
| ...options, | ||
| ...this.customAgent.options | ||
| } : options; | ||
| return new MockHttpSocket({ | ||
| connectionOptions: options, | ||
| createConnection: createConnection.bind(this.customAgent || this, createConnectionOptions, callback), | ||
| onRequest: this.onRequest.bind(this), | ||
| onResponse: this.onResponse.bind(this) | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/utils/getUrlByRequestOptions.ts | ||
| const logger$2 = new _open_draft_logger.Logger("utils getUrlByRequestOptions"); | ||
| const DEFAULT_PATH = "/"; | ||
| const DEFAULT_PROTOCOL = "http:"; | ||
| const DEFAULT_HOSTNAME = "localhost"; | ||
| const SSL_PORT = 443; | ||
| function getAgent(options) { | ||
| return options.agent instanceof http.Agent ? options.agent : void 0; | ||
| } | ||
| function getProtocolByRequestOptions(options) { | ||
| if (options.protocol) return options.protocol; | ||
| const agentProtocol = getAgent(options)?.protocol; | ||
| if (agentProtocol) return agentProtocol; | ||
| const port = getPortByRequestOptions(options); | ||
| return options.cert || port === SSL_PORT ? "https:" : options.uri?.protocol || DEFAULT_PROTOCOL; | ||
| } | ||
| function getPortByRequestOptions(options) { | ||
| if (options.port) return Number(options.port); | ||
| const agent = getAgent(options); | ||
| if (agent?.options.port) return Number(agent.options.port); | ||
| if (agent?.defaultPort) return Number(agent.defaultPort); | ||
| } | ||
| function getAuthByRequestOptions(options) { | ||
| if (options.auth) { | ||
| const [username, password] = options.auth.split(":"); | ||
| return { | ||
| username, | ||
| password | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Returns true if host looks like an IPv6 address without surrounding brackets | ||
| * It assumes any host containing `:` is definitely not IPv4 and probably IPv6, | ||
| * but note that this could include invalid IPv6 addresses as well. | ||
| */ | ||
| function isRawIPv6Address(host) { | ||
| return host.includes(":") && !host.startsWith("[") && !host.endsWith("]"); | ||
| } | ||
| function getHostname(options) { | ||
| let host = options.hostname || options.host; | ||
| if (host) { | ||
| if (isRawIPv6Address(host)) host = `[${host}]`; | ||
| return new URL(`http://${host}`).hostname; | ||
| } | ||
| return DEFAULT_HOSTNAME; | ||
| } | ||
| /** | ||
| * Creates a `URL` instance from a given `RequestOptions` object. | ||
| */ | ||
| function getUrlByRequestOptions(options) { | ||
| logger$2.info("request options", options); | ||
| if (options.uri) { | ||
| logger$2.info("constructing url from explicitly provided \"options.uri\": %s", options.uri); | ||
| return new URL(options.uri.href); | ||
| } | ||
| logger$2.info("figuring out url from request options..."); | ||
| const protocol = getProtocolByRequestOptions(options); | ||
| logger$2.info("protocol", protocol); | ||
| const port = getPortByRequestOptions(options); | ||
| logger$2.info("port", port); | ||
| const hostname = getHostname(options); | ||
| logger$2.info("hostname", hostname); | ||
| const path = options.path || DEFAULT_PATH; | ||
| logger$2.info("path", path); | ||
| const credentials = getAuthByRequestOptions(options); | ||
| logger$2.info("credentials", credentials); | ||
| const authString = credentials ? `${credentials.username}:${credentials.password}@` : ""; | ||
| logger$2.info("auth string:", authString); | ||
| const portString = typeof port !== "undefined" ? `:${port}` : ""; | ||
| const url = new URL(`${protocol}//${hostname}${portString}${path}`); | ||
| url.username = credentials?.username || ""; | ||
| url.password = credentials?.password || ""; | ||
| logger$2.info("created url:", url); | ||
| return url; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/cloneObject.ts | ||
| const logger$1 = new _open_draft_logger.Logger("cloneObject"); | ||
| function isPlainObject(obj) { | ||
| logger$1.info("is plain object?", obj); | ||
| if (obj == null || !obj.constructor?.name) { | ||
| logger$1.info("given object is undefined, not a plain object..."); | ||
| return false; | ||
| } | ||
| logger$1.info("checking the object constructor:", obj.constructor.name); | ||
| return obj.constructor.name === "Object"; | ||
| } | ||
| function cloneObject(obj) { | ||
| logger$1.info("cloning object:", obj); | ||
| const enumerableProperties = Object.entries(obj).reduce((acc, [key, value]) => { | ||
| logger$1.info("analyzing key-value pair:", key, value); | ||
| acc[key] = isPlainObject(value) ? cloneObject(value) : value; | ||
| return acc; | ||
| }, {}); | ||
| return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts | ||
| const logger = new _open_draft_logger.Logger("http normalizeClientRequestArgs"); | ||
| function resolveRequestOptions(args, url) { | ||
| if (typeof args[1] === "undefined" || typeof args[1] === "function") { | ||
| logger.info("request options not provided, deriving from the url", url); | ||
| return (0, node_url.urlToHttpOptions)(url); | ||
| } | ||
| if (args[1]) { | ||
| logger.info("has custom RequestOptions!", args[1]); | ||
| const requestOptionsFromUrl = (0, node_url.urlToHttpOptions)(url); | ||
| logger.info("derived RequestOptions from the URL:", requestOptionsFromUrl); | ||
| /** | ||
| * Clone the request options to lock their state | ||
| * at the moment they are provided to `ClientRequest`. | ||
| * @see https://github.com/mswjs/interceptors/issues/86 | ||
| */ | ||
| logger.info("cloning RequestOptions..."); | ||
| const clonedRequestOptions = cloneObject(args[1]); | ||
| logger.info("successfully cloned RequestOptions!", clonedRequestOptions); | ||
| return { | ||
| ...requestOptionsFromUrl, | ||
| ...clonedRequestOptions | ||
| }; | ||
| } | ||
| logger.info("using an empty object as request options"); | ||
| return {}; | ||
| } | ||
| /** | ||
| * Overrides the given `URL` instance with the explicit properties provided | ||
| * on the `RequestOptions` object. The options object takes precedence, | ||
| * and will replace URL properties like "host", "path", and "port", if specified. | ||
| */ | ||
| function overrideUrlByRequestOptions(url, options) { | ||
| url.host = options.host || url.host; | ||
| url.hostname = options.hostname || url.hostname; | ||
| url.port = options.port ? options.port.toString() : url.port; | ||
| if (options.path) { | ||
| const parsedOptionsPath = (0, node_url.parse)(options.path, false); | ||
| url.pathname = parsedOptionsPath.pathname || ""; | ||
| url.search = parsedOptionsPath.search || ""; | ||
| } | ||
| return url; | ||
| } | ||
| function resolveCallback(args) { | ||
| return typeof args[1] === "function" ? args[1] : args[2]; | ||
| } | ||
| /** | ||
| * Normalizes parameters given to a `http.request` call | ||
| * so it always has a `URL` and `RequestOptions`. | ||
| */ | ||
| function normalizeClientRequestArgs(defaultProtocol, args) { | ||
| let url; | ||
| let options; | ||
| let callback; | ||
| logger.info("arguments", args); | ||
| logger.info("using default protocol:", defaultProtocol); | ||
| if (args.length === 0) { | ||
| const url$1 = new node_url.URL("http://localhost"); | ||
| return [url$1, resolveRequestOptions(args, url$1)]; | ||
| } | ||
| if (typeof args[0] === "string") { | ||
| logger.info("first argument is a location string:", args[0]); | ||
| url = new node_url.URL(args[0]); | ||
| logger.info("created a url:", url); | ||
| const requestOptionsFromUrl = (0, node_url.urlToHttpOptions)(url); | ||
| logger.info("request options from url:", requestOptionsFromUrl); | ||
| options = resolveRequestOptions(args, url); | ||
| logger.info("resolved request options:", options); | ||
| callback = resolveCallback(args); | ||
| } else if (args[0] instanceof node_url.URL) { | ||
| url = args[0]; | ||
| logger.info("first argument is a URL:", url); | ||
| if (typeof args[1] !== "undefined" && require_handleRequest.isObject(args[1])) url = overrideUrlByRequestOptions(url, args[1]); | ||
| options = resolveRequestOptions(args, url); | ||
| logger.info("derived request options:", options); | ||
| callback = resolveCallback(args); | ||
| } else if ("hash" in args[0] && !("method" in args[0])) { | ||
| const [legacyUrl] = args; | ||
| logger.info("first argument is a legacy URL:", legacyUrl); | ||
| if (legacyUrl.hostname === null) { | ||
| /** | ||
| * We are dealing with a relative url, so use the path as an "option" and | ||
| * merge in any existing options, giving priority to existing options -- i.e. a path in any | ||
| * existing options will take precedence over the one contained in the url. This is consistent | ||
| * with the behaviour in ClientRequest. | ||
| * @see https://github.com/nodejs/node/blob/d84f1312915fe45fe0febe888db692c74894c382/lib/_http_client.js#L122 | ||
| */ | ||
| logger.info("given legacy URL is relative (no hostname)"); | ||
| return require_handleRequest.isObject(args[1]) ? normalizeClientRequestArgs(defaultProtocol, [{ | ||
| path: legacyUrl.path, | ||
| ...args[1] | ||
| }, args[2]]) : normalizeClientRequestArgs(defaultProtocol, [{ path: legacyUrl.path }, args[1]]); | ||
| } | ||
| logger.info("given legacy url is absolute"); | ||
| const resolvedUrl = new node_url.URL(legacyUrl.href); | ||
| return args[1] === void 0 ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl]) : typeof args[1] === "function" ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl, args[1]]) : normalizeClientRequestArgs(defaultProtocol, [ | ||
| resolvedUrl, | ||
| args[1], | ||
| args[2] | ||
| ]); | ||
| } else if (require_handleRequest.isObject(args[0])) { | ||
| options = { ...args[0] }; | ||
| logger.info("first argument is RequestOptions:", options); | ||
| options.protocol = options.protocol || defaultProtocol; | ||
| logger.info("normalized request options:", options); | ||
| url = getUrlByRequestOptions(options); | ||
| logger.info("created a URL from RequestOptions:", url.href); | ||
| callback = resolveCallback(args); | ||
| } else throw new Error(`Failed to construct ClientRequest with these parameters: ${args}`); | ||
| options.protocol = options.protocol || url.protocol; | ||
| options.method = options.method || "GET"; | ||
| /** | ||
| * Ensure that the default Agent is always set. | ||
| * This prevents the protocol mismatch for requests with { agent: false }, | ||
| * where the global Agent is inferred. | ||
| * @see https://github.com/mswjs/msw/issues/1150 | ||
| * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L130 | ||
| * @see https://github.com/nodejs/node/blob/418ff70b810f0e7112d48baaa72932a56cfa213b/lib/_http_client.js#L157-L159 | ||
| */ | ||
| if (!options._defaultAgent) { | ||
| logger.info("has no default agent, setting the default agent for \"%s\"", options.protocol); | ||
| options._defaultAgent = options.protocol === "https:" ? node_https.globalAgent : node_http.globalAgent; | ||
| } | ||
| logger.info("successfully resolved url:", url.href); | ||
| logger.info("successfully resolved options:", options); | ||
| logger.info("successfully resolved callback:", callback); | ||
| /** | ||
| * @note If the user-provided URL is not a valid URL in Node.js, | ||
| * (e.g. the one provided by the JSDOM polyfills), case it to | ||
| * string. Otherwise, this throws on Node.js incompatibility | ||
| * (`ERR_INVALID_ARG_TYPE` on the connection listener) | ||
| * @see https://github.com/node-fetch/node-fetch/issues/1376#issuecomment-966435555 | ||
| */ | ||
| if (!(url instanceof node_url.URL)) url = url.toString(); | ||
| return [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/ClientRequest/index.ts | ||
| var ClientRequestInterceptor = class ClientRequestInterceptor extends require_fetchUtils.Interceptor { | ||
| static { | ||
| this.symbol = Symbol("client-request-interceptor"); | ||
| } | ||
| constructor() { | ||
| super(ClientRequestInterceptor.symbol); | ||
| this.onRequest = async ({ request, socket }) => { | ||
| const controller = new require_fetchUtils.RequestController(request, { | ||
| passthrough() { | ||
| socket.passthrough(); | ||
| }, | ||
| async respondWith(response) { | ||
| await socket.respondWith(response); | ||
| }, | ||
| errorWith(reason) { | ||
| if (reason instanceof Error) socket.errorWith(reason); | ||
| } | ||
| }); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId: Reflect.get(request, kRequestId), | ||
| controller, | ||
| emitter: this.emitter | ||
| }); | ||
| }; | ||
| this.onResponse = async ({ requestId, request, response, isMockedResponse }) => { | ||
| return require_handleRequest.emitAsync(this.emitter, "response", { | ||
| requestId, | ||
| request, | ||
| response, | ||
| isMockedResponse | ||
| }); | ||
| }; | ||
| } | ||
| setup() { | ||
| const { ClientRequest: OriginalClientRequest, get: originalGet, request: originalRequest } = node_http.default; | ||
| const { get: originalHttpsGet, request: originalHttpsRequest } = node_https.default; | ||
| const onRequest = this.onRequest.bind(this); | ||
| const onResponse = this.onResponse.bind(this); | ||
| node_http.default.ClientRequest = new Proxy(node_http.default.ClientRequest, { construct: (target, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new (options.protocol === "https:" ? MockHttpsAgent : MockAgent)({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.construct(target, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| node_http.default.request = new Proxy(node_http.default.request, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new MockAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| node_http.default.get = new Proxy(node_http.default.get, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("http:", args); | ||
| options.agent = new MockAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| node_https.default.request = new Proxy(node_https.default.request, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("https:", args); | ||
| options.agent = new MockHttpsAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| node_https.default.get = new Proxy(node_https.default.get, { apply: (target, thisArg, args) => { | ||
| const [url, options, callback] = normalizeClientRequestArgs("https:", args); | ||
| options.agent = new MockHttpsAgent({ | ||
| customAgent: options.agent, | ||
| onRequest, | ||
| onResponse | ||
| }); | ||
| return Reflect.apply(target, thisArg, [ | ||
| url, | ||
| options, | ||
| callback | ||
| ]); | ||
| } }); | ||
| recordRawFetchHeaders(); | ||
| this.subscriptions.push(() => { | ||
| node_http.default.ClientRequest = OriginalClientRequest; | ||
| node_http.default.get = originalGet; | ||
| node_http.default.request = originalRequest; | ||
| node_https.default.get = originalHttpsGet; | ||
| node_https.default.request = originalHttpsRequest; | ||
| restoreHeadersPrototype(); | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'ClientRequestInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return ClientRequestInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=ClientRequest-OY5NnrOh.cjs.map |
Sorry, the diff of this file is too big to display
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-BAlG765A.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-B6znU6il.cjs'); | ||
| const require_handleRequest = require('./handleRequest-DUPo40Df.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-Befl_QJD.cjs'); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let _open_draft_until = require("@open-draft/until"); | ||
| let node_zlib = require("node:zlib"); | ||
| node_zlib = require_chunk.__toESM(node_zlib); | ||
| //#region src/interceptors/fetch/utils/createNetworkError.ts | ||
| function createNetworkError(cause) { | ||
| return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/followRedirect.ts | ||
| const REQUEST_BODY_HEADERS = [ | ||
| "content-encoding", | ||
| "content-language", | ||
| "content-location", | ||
| "content-type", | ||
| "content-length" | ||
| ]; | ||
| const kRedirectCount = Symbol("kRedirectCount"); | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210 | ||
| */ | ||
| 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"); | ||
| } | ||
| /** | ||
| * @note Undici "safely" extracts the request body. | ||
| * I suspect we cannot dispatch this request again | ||
| * since its body has been read and the stream is locked. | ||
| */ | ||
| requestInit.headers = request.headers; | ||
| const finalResponse = await fetch(new Request(locationUrl, requestInit)); | ||
| Object.defineProperty(finalResponse, "redirected", { | ||
| value: true, | ||
| configurable: true | ||
| }); | ||
| return finalResponse; | ||
| } | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761 | ||
| */ | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/brotli-decompress.ts | ||
| var BrotliDecompressionStream = class extends TransformStream { | ||
| constructor() { | ||
| const decompress = node_zlib.default.createBrotliDecompress({ | ||
| flush: node_zlib.default.constants.BROTLI_OPERATION_FLUSH, | ||
| finishFlush: node_zlib.default.constants.BROTLI_OPERATION_FLUSH | ||
| }); | ||
| super({ async transform(chunk, controller) { | ||
| const buffer = Buffer.from(chunk); | ||
| const decompressed = await new Promise((resolve, reject) => { | ||
| decompress.write(buffer, (error) => { | ||
| if (error) reject(error); | ||
| }); | ||
| decompress.flush(); | ||
| decompress.once("data", (data) => resolve(data)); | ||
| decompress.once("error", (error) => reject(error)); | ||
| decompress.once("end", () => controller.terminate()); | ||
| }).catch((error) => { | ||
| controller.error(error); | ||
| }); | ||
| controller.enqueue(decompressed); | ||
| } }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/decompression.ts | ||
| var PipelineStream = class extends TransformStream { | ||
| constructor(transformStreams, ...strategies) { | ||
| super({}, ...strategies); | ||
| const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform)); | ||
| Object.defineProperty(this, "readable", { get() { | ||
| return readable; | ||
| } }); | ||
| } | ||
| }; | ||
| function parseContentEncoding(contentEncoding) { | ||
| return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim()); | ||
| } | ||
| function createDecompressionStream(contentEncoding) { | ||
| if (contentEncoding === "") return null; | ||
| const codings = parseContentEncoding(contentEncoding); | ||
| if (codings.length === 0) return null; | ||
| return new PipelineStream(codings.reduceRight((transformers, coding) => { | ||
| if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip")); | ||
| else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate")); | ||
| else if (coding === "br") return transformers.concat(new BrotliDecompressionStream()); | ||
| else transformers.length = 0; | ||
| return transformers; | ||
| }, [])); | ||
| } | ||
| function decompressResponse(response) { | ||
| if (response.body === null) return null; | ||
| const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || ""); | ||
| if (!decompressionStream) return null; | ||
| response.body.pipeTo(decompressionStream.writable); | ||
| return decompressionStream.readable; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/index.ts | ||
| var FetchInterceptor = class FetchInterceptor extends require_fetchUtils.Interceptor { | ||
| static { | ||
| this.symbol = Symbol("fetch"); | ||
| } | ||
| constructor() { | ||
| super(FetchInterceptor.symbol); | ||
| } | ||
| checkEnvironment() { | ||
| return require_hasConfigurableGlobal.hasConfigurableGlobal("fetch"); | ||
| } | ||
| async setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| const pureFetch = globalThis.fetch; | ||
| const fetchProxy = async (input, init) => { | ||
| const requestId = require_fetchUtils.createRequestId(); | ||
| const request = new require_fetchUtils.FetchRequest(typeof input === "string" && typeof location !== "undefined" && !require_fetchUtils.canParseUrl(input) ? new URL(input, location.href) : input, init); | ||
| /** | ||
| * @note Set the raw request only if a Request instance was provided to fetch. | ||
| */ | ||
| if (input instanceof Request) require_getRawRequest.setRawRequest(request, input); | ||
| const responsePromise = new _open_draft_deferred_promise.DeferredPromise(); | ||
| const controller = new require_fetchUtils.RequestController(request, { | ||
| passthrough: async () => { | ||
| this.logger.info("request has not been handled, passthrough..."); | ||
| /** | ||
| * @note Clone the request instance right before performing it. | ||
| * This preserves any modifications made to the intercepted request | ||
| * in the "request" listener. This also allows the user to read the | ||
| * request body in the "response" listener (otherwise "unusable"). | ||
| */ | ||
| const requestCloneForResponseEvent = request.clone(); | ||
| const { error: responseError, data: originalResponse } = await (0, _open_draft_until.until)(() => pureFetch(request)); | ||
| if (responseError) return responsePromise.reject(responseError); | ||
| this.logger.info("original fetch performed", originalResponse); | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| const responseClone = require_fetchUtils.FetchResponse.clone(originalResponse); | ||
| await require_handleRequest.emitAsync(this.emitter, "response", { | ||
| response: responseClone, | ||
| isMockedResponse: false, | ||
| request: requestCloneForResponseEvent, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(originalResponse); | ||
| }, | ||
| respondWith: async (rawResponse) => { | ||
| if (require_handleRequest.isResponseError(rawResponse)) { | ||
| this.logger.info("request has errored!", { response: rawResponse }); | ||
| responsePromise.reject(createNetworkError(rawResponse)); | ||
| return; | ||
| } | ||
| this.logger.info("received mocked response!", { rawResponse }); | ||
| const decompressedStream = decompressResponse(rawResponse); | ||
| const response = decompressedStream === null ? rawResponse : new require_fetchUtils.FetchResponse(decompressedStream, rawResponse); | ||
| require_fetchUtils.FetchResponse.setUrl(request.url, response); | ||
| /** | ||
| * Undici's handling of following redirect responses. | ||
| * Treat the "manual" redirect mode as a regular mocked response. | ||
| * This way, the client can manually follow the redirect it receives. | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173 | ||
| */ | ||
| if (require_fetchUtils.FetchResponse.isRedirectResponse(response.status)) { | ||
| if (request.redirect === "error") { | ||
| responsePromise.reject(createNetworkError("unexpected redirect")); | ||
| return; | ||
| } | ||
| if (request.redirect === "follow") { | ||
| followFetchRedirect(request, response).then((response$1) => { | ||
| responsePromise.resolve(response$1); | ||
| }, (reason) => { | ||
| responsePromise.reject(reason); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| await require_handleRequest.emitAsync(this.emitter, "response", { | ||
| response: require_fetchUtils.FetchResponse.clone(response), | ||
| isMockedResponse: true, | ||
| request, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request has been aborted!", { reason }); | ||
| responsePromise.reject(reason); | ||
| } | ||
| }); | ||
| 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")); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId, | ||
| emitter: this.emitter, | ||
| controller | ||
| }); | ||
| return responsePromise; | ||
| }; | ||
| logger.info("patching global fetch..."); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.globalsRegistry.replaceGlobal("fetch", fetchProxy)); | ||
| logger.info("global fetch patched!", globalThis.fetch.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'FetchInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=fetch-B9UiBg-M.cjs.map |
| {"version":3,"file":"fetch-B9UiBg-M.cjs","names":["locationUrl: URL","requestInit: RequestInit","zlib","readable","Interceptor","hasConfigurableGlobal","fetchProxy: typeof fetch","createRequestId","FetchRequest","canParseUrl","DeferredPromise","RequestController","FetchResponse","emitAsync","isResponseError","response","handleRequest","globalsRegistry"],"sources":["../../src/interceptors/fetch/utils/createNetworkError.ts","../../src/interceptors/fetch/utils/followRedirect.ts","../../src/interceptors/fetch/utils/brotli-decompress.ts","../../src/interceptors/fetch/utils/decompression.ts","../../src/interceptors/fetch/index.ts"],"sourcesContent":["export function createNetworkError(cause?: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","import { createNetworkError } from './createNetworkError'\n\nconst REQUEST_BODY_HEADERS = [\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-type',\n 'content-length',\n]\n\nconst kRedirectCount = Symbol('kRedirectCount')\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210\n */\nexport async function followFetchRedirect(\n request: Request,\n response: Response\n): Promise<Response> {\n if (response.status !== 303 && request.body != null) {\n return Promise.reject(createNetworkError())\n }\n\n const requestUrl = new URL(request.url)\n\n let locationUrl: URL\n try {\n // If the location is a relative URL, use the request URL as the base URL.\n locationUrl = new URL(response.headers.get('location')!, request.url) \n } catch (error) {\n return Promise.reject(createNetworkError(error))\n }\n\n if (\n !(locationUrl.protocol === 'http:' || locationUrl.protocol === 'https:')\n ) {\n return Promise.reject(\n createNetworkError('URL scheme must be a HTTP(S) scheme')\n )\n }\n\n if (Reflect.get(request, kRedirectCount) > 20) {\n return Promise.reject(createNetworkError('redirect count exceeded'))\n }\n\n Object.defineProperty(request, kRedirectCount, {\n value: (Reflect.get(request, kRedirectCount) || 0) + 1,\n })\n\n if (\n request.mode === 'cors' &&\n (locationUrl.username || locationUrl.password) &&\n !sameOrigin(requestUrl, locationUrl)\n ) {\n return Promise.reject(\n createNetworkError('cross origin not allowed for request mode \"cors\"')\n )\n }\n\n const requestInit: RequestInit = {}\n\n if (\n ([301, 302].includes(response.status) && request.method === 'POST') ||\n (response.status === 303 && !['HEAD', 'GET'].includes(request.method))\n ) {\n requestInit.method = 'GET'\n requestInit.body = null\n\n REQUEST_BODY_HEADERS.forEach((headerName) => {\n request.headers.delete(headerName)\n })\n }\n\n if (!sameOrigin(requestUrl, locationUrl)) {\n request.headers.delete('authorization')\n request.headers.delete('proxy-authorization')\n request.headers.delete('cookie')\n request.headers.delete('host')\n }\n\n /**\n * @note Undici \"safely\" extracts the request body.\n * I suspect we cannot dispatch this request again\n * since its body has been read and the stream is locked.\n */\n\n requestInit.headers = request.headers\n const finalResponse = await fetch(new Request(locationUrl, requestInit))\n Object.defineProperty(finalResponse, 'redirected', {\n value: true,\n configurable: true,\n })\n\n return finalResponse\n}\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761\n */\nfunction sameOrigin(left: URL, right: URL): boolean {\n if (left.origin === right.origin && left.origin === 'null') {\n return true\n }\n\n if (\n left.protocol === right.protocol &&\n left.hostname === right.hostname &&\n left.port === right.port\n ) {\n return true\n }\n\n return false\n}\n","import zlib from 'node:zlib'\n\nexport class BrotliDecompressionStream extends TransformStream {\n constructor() {\n const decompress = zlib.createBrotliDecompress({\n flush: zlib.constants.BROTLI_OPERATION_FLUSH,\n finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,\n })\n\n super({\n async transform(chunk, controller) {\n const buffer = Buffer.from(chunk)\n\n const decompressed = await new Promise<Buffer>((resolve, reject) => {\n decompress.write(buffer, (error) => {\n if (error) reject(error)\n })\n\n decompress.flush()\n decompress.once('data', (data) => resolve(data))\n decompress.once('error', (error) => reject(error))\n decompress.once('end', () => controller.terminate())\n }).catch((error) => {\n controller.error(error)\n })\n\n controller.enqueue(decompressed)\n },\n })\n }\n}\n","// Import from an internal alias that resolves to different modules\n// depending on the environment. This way, we can keep the fetch interceptor\n// intact while using different strategies for Brotli decompression.\nimport { BrotliDecompressionStream } from 'internal:brotli-decompress'\n\nclass PipelineStream extends TransformStream {\n constructor(\n transformStreams: Array<TransformStream>,\n ...strategies: Array<QueuingStrategy>\n ) {\n super({}, ...strategies)\n\n const readable = [super.readable as any, ...transformStreams].reduce(\n (readable, transform) => readable.pipeThrough(transform)\n )\n\n Object.defineProperty(this, 'readable', {\n get() {\n return readable\n },\n })\n }\n}\n\nexport function parseContentEncoding(contentEncoding: string): Array<string> {\n return contentEncoding\n .toLowerCase()\n .split(',')\n .map((coding) => coding.trim())\n}\n\nfunction createDecompressionStream(\n contentEncoding: string\n): TransformStream | null {\n if (contentEncoding === '') {\n return null\n }\n\n const codings = parseContentEncoding(contentEncoding)\n\n if (codings.length === 0) {\n return null\n }\n\n const transformers = codings.reduceRight<Array<TransformStream>>(\n (transformers, coding) => {\n if (coding === 'gzip' || coding === 'x-gzip') {\n return transformers.concat(new DecompressionStream('gzip'))\n } else if (coding === 'deflate') {\n return transformers.concat(new DecompressionStream('deflate'))\n } else if (coding === 'br') {\n return transformers.concat(new BrotliDecompressionStream())\n } else {\n transformers.length = 0\n }\n\n return transformers\n },\n []\n )\n\n return new PipelineStream(transformers)\n}\n\nexport function decompressResponse(\n response: Response\n): ReadableStream<any> | null {\n if (response.body === null) {\n return null\n }\n\n const decompressionStream = createDecompressionStream(\n response.headers.get('content-encoding') || ''\n )\n\n if (!decompressionStream) {\n return null\n }\n\n // Use `pipeTo` and return the decompression stream's readable\n // instead of `pipeThrough` because that will lock the original\n // response stream, making it unusable as the input to Response.\n response.body.pipeTo(decompressionStream.writable)\n return decompressionStream.readable\n}\n","import { until } from '@open-draft/until'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport { createNetworkError } from './utils/createNetworkError'\nimport { followFetchRedirect } from './utils/followRedirect'\nimport { decompressResponse } from './utils/decompression'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { FetchRequest, FetchResponse } from '../../utils/fetchUtils'\nimport { setRawRequest } from '../../getRawRequest'\nimport { isResponseError } from '../../utils/responseUtils'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('fetch')\n }\n\n protected async setup() {\n const logger = this.logger.extend('setup')\n\n const pureFetch = globalThis.fetch\n\n const fetchProxy: typeof fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.href)\n : input\n\n const request = new FetchRequest(resolvedInput, init)\n\n /**\n * @note Set the raw request only if a Request instance was provided to fetch.\n */\n if (input instanceof Request) {\n setRawRequest(request, input)\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const controller = new RequestController(request, {\n passthrough: async () => {\n this.logger.info('request has not been handled, passthrough...')\n\n /**\n * @note Clone the request instance right before performing it.\n * This preserves any modifications made to the intercepted request\n * in the \"request\" listener. This also allows the user to read the\n * request body in the \"response\" listener (otherwise \"unusable\").\n */\n const requestCloneForResponseEvent = request.clone()\n\n // Perform the intercepted request as-is.\n const { error: responseError, data: originalResponse } = await until(\n () => pureFetch(request)\n )\n\n if (responseError) {\n return responsePromise.reject(responseError)\n }\n\n this.logger.info('original fetch performed', originalResponse)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = FetchResponse.clone(originalResponse)\n await emitAsync(this.emitter, 'response', {\n response: responseClone,\n isMockedResponse: false,\n request: requestCloneForResponseEvent,\n requestId,\n })\n }\n\n // Resolve the response promise with the original response\n // since the `fetch()` return this internal promise.\n responsePromise.resolve(originalResponse)\n },\n respondWith: async (rawResponse) => {\n // Handle mocked `Response.error()` (i.e. request errors).\n if (isResponseError(rawResponse)) {\n this.logger.info('request has errored!', { response: rawResponse })\n responsePromise.reject(createNetworkError(rawResponse))\n return\n }\n\n this.logger.info('received mocked response!', {\n rawResponse,\n })\n\n // Decompress the mocked response body, if applicable.\n const decompressedStream = decompressResponse(rawResponse)\n const response =\n decompressedStream === null\n ? rawResponse\n : new FetchResponse(decompressedStream, rawResponse)\n\n FetchResponse.setUrl(request.url, response)\n\n /**\n * Undici's handling of following redirect responses.\n * Treat the \"manual\" redirect mode as a regular mocked response.\n * This way, the client can manually follow the redirect it receives.\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173\n */\n if (FetchResponse.isRedirectResponse(response.status)) {\n // Reject the request promise if its `redirect` is set to `error`\n // and it receives a mocked redirect response.\n if (request.redirect === 'error') {\n responsePromise.reject(createNetworkError('unexpected redirect'))\n return\n }\n\n if (request.redirect === 'follow') {\n followFetchRedirect(request, response).then(\n (response) => {\n responsePromise.resolve(response)\n },\n (reason) => {\n responsePromise.reject(reason)\n }\n )\n return\n }\n }\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: FetchResponse.clone(response),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n responsePromise.resolve(response)\n },\n errorWith: (reason) => {\n this.logger.info('request has been aborted!', { reason })\n responsePromise.reject(reason)\n },\n })\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n })\n\n return responsePromise\n }\n\n logger.info('patching global fetch...')\n\n this.subscriptions.push(globalsRegistry.replaceGlobal('fetch', fetchProxy))\n\n logger.info('global fetch patched!', globalThis.fetch.name)\n }\n}\n"],"mappings":"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"} |
| import { a as RequestController, c as Interceptor, i as createRequestId, n as FetchResponse, r as canParseUrl, t as FetchRequest } from "./fetchUtils-Bq0Mdmkv.mjs"; | ||
| import { n as setRawRequest } from "./getRawRequest-C2-1urzA.mjs"; | ||
| import { i as emitAsync, n as isResponseError, t as handleRequest } from "./handleRequest-u6jpjgrr.mjs"; | ||
| import { n as globalsRegistry, t as hasConfigurableGlobal } from "./hasConfigurableGlobal-jN_-v3gp.mjs"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { until } from "@open-draft/until"; | ||
| import zlib from "node:zlib"; | ||
| //#region src/interceptors/fetch/utils/createNetworkError.ts | ||
| function createNetworkError(cause) { | ||
| return Object.assign(/* @__PURE__ */ new TypeError("Failed to fetch"), { cause }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/followRedirect.ts | ||
| const REQUEST_BODY_HEADERS = [ | ||
| "content-encoding", | ||
| "content-language", | ||
| "content-location", | ||
| "content-type", | ||
| "content-length" | ||
| ]; | ||
| const kRedirectCount = Symbol("kRedirectCount"); | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210 | ||
| */ | ||
| 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"); | ||
| } | ||
| /** | ||
| * @note Undici "safely" extracts the request body. | ||
| * I suspect we cannot dispatch this request again | ||
| * since its body has been read and the stream is locked. | ||
| */ | ||
| requestInit.headers = request.headers; | ||
| const finalResponse = await fetch(new Request(locationUrl, requestInit)); | ||
| Object.defineProperty(finalResponse, "redirected", { | ||
| value: true, | ||
| configurable: true | ||
| }); | ||
| return finalResponse; | ||
| } | ||
| /** | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761 | ||
| */ | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/brotli-decompress.ts | ||
| var BrotliDecompressionStream = class extends TransformStream { | ||
| constructor() { | ||
| const decompress = zlib.createBrotliDecompress({ | ||
| flush: zlib.constants.BROTLI_OPERATION_FLUSH, | ||
| finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH | ||
| }); | ||
| super({ async transform(chunk, controller) { | ||
| const buffer = Buffer.from(chunk); | ||
| const decompressed = await new Promise((resolve, reject) => { | ||
| decompress.write(buffer, (error) => { | ||
| if (error) reject(error); | ||
| }); | ||
| decompress.flush(); | ||
| decompress.once("data", (data) => resolve(data)); | ||
| decompress.once("error", (error) => reject(error)); | ||
| decompress.once("end", () => controller.terminate()); | ||
| }).catch((error) => { | ||
| controller.error(error); | ||
| }); | ||
| controller.enqueue(decompressed); | ||
| } }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/fetch/utils/decompression.ts | ||
| var PipelineStream = class extends TransformStream { | ||
| constructor(transformStreams, ...strategies) { | ||
| super({}, ...strategies); | ||
| const readable = [super.readable, ...transformStreams].reduce((readable$1, transform) => readable$1.pipeThrough(transform)); | ||
| Object.defineProperty(this, "readable", { get() { | ||
| return readable; | ||
| } }); | ||
| } | ||
| }; | ||
| function parseContentEncoding(contentEncoding) { | ||
| return contentEncoding.toLowerCase().split(",").map((coding) => coding.trim()); | ||
| } | ||
| function createDecompressionStream(contentEncoding) { | ||
| if (contentEncoding === "") return null; | ||
| const codings = parseContentEncoding(contentEncoding); | ||
| if (codings.length === 0) return null; | ||
| return new PipelineStream(codings.reduceRight((transformers, coding) => { | ||
| if (coding === "gzip" || coding === "x-gzip") return transformers.concat(new DecompressionStream("gzip")); | ||
| else if (coding === "deflate") return transformers.concat(new DecompressionStream("deflate")); | ||
| else if (coding === "br") return transformers.concat(new BrotliDecompressionStream()); | ||
| else transformers.length = 0; | ||
| return transformers; | ||
| }, [])); | ||
| } | ||
| function decompressResponse(response) { | ||
| if (response.body === null) return null; | ||
| const decompressionStream = createDecompressionStream(response.headers.get("content-encoding") || ""); | ||
| if (!decompressionStream) return null; | ||
| response.body.pipeTo(decompressionStream.writable); | ||
| return decompressionStream.readable; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/fetch/index.ts | ||
| var FetchInterceptor = class FetchInterceptor extends Interceptor { | ||
| static { | ||
| this.symbol = Symbol("fetch"); | ||
| } | ||
| constructor() { | ||
| super(FetchInterceptor.symbol); | ||
| } | ||
| checkEnvironment() { | ||
| return hasConfigurableGlobal("fetch"); | ||
| } | ||
| async setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| const pureFetch = globalThis.fetch; | ||
| const fetchProxy = async (input, init) => { | ||
| const requestId = createRequestId(); | ||
| const request = new FetchRequest(typeof input === "string" && typeof location !== "undefined" && !canParseUrl(input) ? new URL(input, location.href) : input, init); | ||
| /** | ||
| * @note Set the raw request only if a Request instance was provided to fetch. | ||
| */ | ||
| if (input instanceof Request) setRawRequest(request, input); | ||
| const responsePromise = new DeferredPromise(); | ||
| const controller = new RequestController(request, { | ||
| passthrough: async () => { | ||
| this.logger.info("request has not been handled, passthrough..."); | ||
| /** | ||
| * @note Clone the request instance right before performing it. | ||
| * This preserves any modifications made to the intercepted request | ||
| * in the "request" listener. This also allows the user to read the | ||
| * request body in the "response" listener (otherwise "unusable"). | ||
| */ | ||
| const requestCloneForResponseEvent = request.clone(); | ||
| const { error: responseError, data: originalResponse } = await until(() => pureFetch(request)); | ||
| if (responseError) return responsePromise.reject(responseError); | ||
| this.logger.info("original fetch performed", originalResponse); | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| const responseClone = FetchResponse.clone(originalResponse); | ||
| await emitAsync(this.emitter, "response", { | ||
| response: responseClone, | ||
| isMockedResponse: false, | ||
| request: requestCloneForResponseEvent, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(originalResponse); | ||
| }, | ||
| respondWith: async (rawResponse) => { | ||
| if (isResponseError(rawResponse)) { | ||
| this.logger.info("request has errored!", { response: rawResponse }); | ||
| responsePromise.reject(createNetworkError(rawResponse)); | ||
| return; | ||
| } | ||
| this.logger.info("received mocked response!", { rawResponse }); | ||
| const decompressedStream = decompressResponse(rawResponse); | ||
| const response = decompressedStream === null ? rawResponse : new FetchResponse(decompressedStream, rawResponse); | ||
| FetchResponse.setUrl(request.url, response); | ||
| /** | ||
| * Undici's handling of following redirect responses. | ||
| * Treat the "manual" redirect mode as a regular mocked response. | ||
| * This way, the client can manually follow the redirect it receives. | ||
| * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173 | ||
| */ | ||
| if (FetchResponse.isRedirectResponse(response.status)) { | ||
| if (request.redirect === "error") { | ||
| responsePromise.reject(createNetworkError("unexpected redirect")); | ||
| return; | ||
| } | ||
| if (request.redirect === "follow") { | ||
| followFetchRedirect(request, response).then((response$1) => { | ||
| responsePromise.resolve(response$1); | ||
| }, (reason) => { | ||
| responsePromise.reject(reason); | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| if (this.emitter.listenerCount("response") > 0) { | ||
| this.logger.info("emitting the \"response\" event..."); | ||
| await emitAsync(this.emitter, "response", { | ||
| response: FetchResponse.clone(response), | ||
| isMockedResponse: true, | ||
| request, | ||
| requestId | ||
| }); | ||
| } | ||
| responsePromise.resolve(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request has been aborted!", { reason }); | ||
| responsePromise.reject(reason); | ||
| } | ||
| }); | ||
| 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")); | ||
| await handleRequest({ | ||
| request, | ||
| requestId, | ||
| emitter: this.emitter, | ||
| controller | ||
| }); | ||
| return responsePromise; | ||
| }; | ||
| logger.info("patching global fetch..."); | ||
| this.subscriptions.push(globalsRegistry.replaceGlobal("fetch", fetchProxy)); | ||
| logger.info("global fetch patched!", globalThis.fetch.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { FetchInterceptor as t }; | ||
| //# sourceMappingURL=fetch-UxWd1aQX.mjs.map |
| {"version":3,"file":"fetch-UxWd1aQX.mjs","names":["locationUrl: URL","requestInit: RequestInit","readable","fetchProxy: typeof fetch","response"],"sources":["../../src/interceptors/fetch/utils/createNetworkError.ts","../../src/interceptors/fetch/utils/followRedirect.ts","../../src/interceptors/fetch/utils/brotli-decompress.ts","../../src/interceptors/fetch/utils/decompression.ts","../../src/interceptors/fetch/index.ts"],"sourcesContent":["export function createNetworkError(cause?: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","import { createNetworkError } from './createNetworkError'\n\nconst REQUEST_BODY_HEADERS = [\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-type',\n 'content-length',\n]\n\nconst kRedirectCount = Symbol('kRedirectCount')\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210\n */\nexport async function followFetchRedirect(\n request: Request,\n response: Response\n): Promise<Response> {\n if (response.status !== 303 && request.body != null) {\n return Promise.reject(createNetworkError())\n }\n\n const requestUrl = new URL(request.url)\n\n let locationUrl: URL\n try {\n // If the location is a relative URL, use the request URL as the base URL.\n locationUrl = new URL(response.headers.get('location')!, request.url) \n } catch (error) {\n return Promise.reject(createNetworkError(error))\n }\n\n if (\n !(locationUrl.protocol === 'http:' || locationUrl.protocol === 'https:')\n ) {\n return Promise.reject(\n createNetworkError('URL scheme must be a HTTP(S) scheme')\n )\n }\n\n if (Reflect.get(request, kRedirectCount) > 20) {\n return Promise.reject(createNetworkError('redirect count exceeded'))\n }\n\n Object.defineProperty(request, kRedirectCount, {\n value: (Reflect.get(request, kRedirectCount) || 0) + 1,\n })\n\n if (\n request.mode === 'cors' &&\n (locationUrl.username || locationUrl.password) &&\n !sameOrigin(requestUrl, locationUrl)\n ) {\n return Promise.reject(\n createNetworkError('cross origin not allowed for request mode \"cors\"')\n )\n }\n\n const requestInit: RequestInit = {}\n\n if (\n ([301, 302].includes(response.status) && request.method === 'POST') ||\n (response.status === 303 && !['HEAD', 'GET'].includes(request.method))\n ) {\n requestInit.method = 'GET'\n requestInit.body = null\n\n REQUEST_BODY_HEADERS.forEach((headerName) => {\n request.headers.delete(headerName)\n })\n }\n\n if (!sameOrigin(requestUrl, locationUrl)) {\n request.headers.delete('authorization')\n request.headers.delete('proxy-authorization')\n request.headers.delete('cookie')\n request.headers.delete('host')\n }\n\n /**\n * @note Undici \"safely\" extracts the request body.\n * I suspect we cannot dispatch this request again\n * since its body has been read and the stream is locked.\n */\n\n requestInit.headers = request.headers\n const finalResponse = await fetch(new Request(locationUrl, requestInit))\n Object.defineProperty(finalResponse, 'redirected', {\n value: true,\n configurable: true,\n })\n\n return finalResponse\n}\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761\n */\nfunction sameOrigin(left: URL, right: URL): boolean {\n if (left.origin === right.origin && left.origin === 'null') {\n return true\n }\n\n if (\n left.protocol === right.protocol &&\n left.hostname === right.hostname &&\n left.port === right.port\n ) {\n return true\n }\n\n return false\n}\n","import zlib from 'node:zlib'\n\nexport class BrotliDecompressionStream extends TransformStream {\n constructor() {\n const decompress = zlib.createBrotliDecompress({\n flush: zlib.constants.BROTLI_OPERATION_FLUSH,\n finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,\n })\n\n super({\n async transform(chunk, controller) {\n const buffer = Buffer.from(chunk)\n\n const decompressed = await new Promise<Buffer>((resolve, reject) => {\n decompress.write(buffer, (error) => {\n if (error) reject(error)\n })\n\n decompress.flush()\n decompress.once('data', (data) => resolve(data))\n decompress.once('error', (error) => reject(error))\n decompress.once('end', () => controller.terminate())\n }).catch((error) => {\n controller.error(error)\n })\n\n controller.enqueue(decompressed)\n },\n })\n }\n}\n","// Import from an internal alias that resolves to different modules\n// depending on the environment. This way, we can keep the fetch interceptor\n// intact while using different strategies for Brotli decompression.\nimport { BrotliDecompressionStream } from 'internal:brotli-decompress'\n\nclass PipelineStream extends TransformStream {\n constructor(\n transformStreams: Array<TransformStream>,\n ...strategies: Array<QueuingStrategy>\n ) {\n super({}, ...strategies)\n\n const readable = [super.readable as any, ...transformStreams].reduce(\n (readable, transform) => readable.pipeThrough(transform)\n )\n\n Object.defineProperty(this, 'readable', {\n get() {\n return readable\n },\n })\n }\n}\n\nexport function parseContentEncoding(contentEncoding: string): Array<string> {\n return contentEncoding\n .toLowerCase()\n .split(',')\n .map((coding) => coding.trim())\n}\n\nfunction createDecompressionStream(\n contentEncoding: string\n): TransformStream | null {\n if (contentEncoding === '') {\n return null\n }\n\n const codings = parseContentEncoding(contentEncoding)\n\n if (codings.length === 0) {\n return null\n }\n\n const transformers = codings.reduceRight<Array<TransformStream>>(\n (transformers, coding) => {\n if (coding === 'gzip' || coding === 'x-gzip') {\n return transformers.concat(new DecompressionStream('gzip'))\n } else if (coding === 'deflate') {\n return transformers.concat(new DecompressionStream('deflate'))\n } else if (coding === 'br') {\n return transformers.concat(new BrotliDecompressionStream())\n } else {\n transformers.length = 0\n }\n\n return transformers\n },\n []\n )\n\n return new PipelineStream(transformers)\n}\n\nexport function decompressResponse(\n response: Response\n): ReadableStream<any> | null {\n if (response.body === null) {\n return null\n }\n\n const decompressionStream = createDecompressionStream(\n response.headers.get('content-encoding') || ''\n )\n\n if (!decompressionStream) {\n return null\n }\n\n // Use `pipeTo` and return the decompression stream's readable\n // instead of `pipeThrough` because that will lock the original\n // response stream, making it unusable as the input to Response.\n response.body.pipeTo(decompressionStream.writable)\n return decompressionStream.readable\n}\n","import { until } from '@open-draft/until'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { RequestController } from '../../RequestController'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport { createNetworkError } from './utils/createNetworkError'\nimport { followFetchRedirect } from './utils/followRedirect'\nimport { decompressResponse } from './utils/decompression'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { FetchRequest, FetchResponse } from '../../utils/fetchUtils'\nimport { setRawRequest } from '../../getRawRequest'\nimport { isResponseError } from '../../utils/responseUtils'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol('fetch')\n\n constructor() {\n super(FetchInterceptor.symbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('fetch')\n }\n\n protected async setup() {\n const logger = this.logger.extend('setup')\n\n const pureFetch = globalThis.fetch\n\n const fetchProxy: typeof fetch = async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !canParseUrl(input)\n ? new URL(input, location.href)\n : input\n\n const request = new FetchRequest(resolvedInput, init)\n\n /**\n * @note Set the raw request only if a Request instance was provided to fetch.\n */\n if (input instanceof Request) {\n setRawRequest(request, input)\n }\n\n const responsePromise = new DeferredPromise<Response>()\n\n const controller = new RequestController(request, {\n passthrough: async () => {\n this.logger.info('request has not been handled, passthrough...')\n\n /**\n * @note Clone the request instance right before performing it.\n * This preserves any modifications made to the intercepted request\n * in the \"request\" listener. This also allows the user to read the\n * request body in the \"response\" listener (otherwise \"unusable\").\n */\n const requestCloneForResponseEvent = request.clone()\n\n // Perform the intercepted request as-is.\n const { error: responseError, data: originalResponse } = await until(\n () => pureFetch(request)\n )\n\n if (responseError) {\n return responsePromise.reject(responseError)\n }\n\n this.logger.info('original fetch performed', originalResponse)\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n const responseClone = FetchResponse.clone(originalResponse)\n await emitAsync(this.emitter, 'response', {\n response: responseClone,\n isMockedResponse: false,\n request: requestCloneForResponseEvent,\n requestId,\n })\n }\n\n // Resolve the response promise with the original response\n // since the `fetch()` return this internal promise.\n responsePromise.resolve(originalResponse)\n },\n respondWith: async (rawResponse) => {\n // Handle mocked `Response.error()` (i.e. request errors).\n if (isResponseError(rawResponse)) {\n this.logger.info('request has errored!', { response: rawResponse })\n responsePromise.reject(createNetworkError(rawResponse))\n return\n }\n\n this.logger.info('received mocked response!', {\n rawResponse,\n })\n\n // Decompress the mocked response body, if applicable.\n const decompressedStream = decompressResponse(rawResponse)\n const response =\n decompressedStream === null\n ? rawResponse\n : new FetchResponse(decompressedStream, rawResponse)\n\n FetchResponse.setUrl(request.url, response)\n\n /**\n * Undici's handling of following redirect responses.\n * Treat the \"manual\" redirect mode as a regular mocked response.\n * This way, the client can manually follow the redirect it receives.\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173\n */\n if (FetchResponse.isRedirectResponse(response.status)) {\n // Reject the request promise if its `redirect` is set to `error`\n // and it receives a mocked redirect response.\n if (request.redirect === 'error') {\n responsePromise.reject(createNetworkError('unexpected redirect'))\n return\n }\n\n if (request.redirect === 'follow') {\n followFetchRedirect(request, response).then(\n (response) => {\n responsePromise.resolve(response)\n },\n (reason) => {\n responsePromise.reject(reason)\n }\n )\n return\n }\n }\n\n if (this.emitter.listenerCount('response') > 0) {\n this.logger.info('emitting the \"response\" event...')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await emitAsync(this.emitter, 'response', {\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: FetchResponse.clone(response),\n isMockedResponse: true,\n request,\n requestId,\n })\n }\n\n responsePromise.resolve(response)\n },\n errorWith: (reason) => {\n this.logger.info('request has been aborted!', { reason })\n responsePromise.reject(reason)\n },\n })\n\n this.logger.info('[%s] %s', request.method, request.url)\n this.logger.info('awaiting for the mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n emitter: this.emitter,\n controller,\n })\n\n return responsePromise\n }\n\n logger.info('patching global fetch...')\n\n this.subscriptions.push(globalsRegistry.replaceGlobal('fetch', fetchProxy))\n\n logger.info('global fetch patched!', globalThis.fetch.name)\n }\n}\n"],"mappings":"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"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| let _open_draft_logger = require("@open-draft/logger"); | ||
| let strict_event_emitter = require("strict-event-emitter"); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let outvariant = require("outvariant"); | ||
| //#region src/Interceptor.ts | ||
| /** | ||
| * Request header name to detect when a single request | ||
| * is being handled by nested interceptors (XHR -> ClientRequest). | ||
| * Obscure by design to prevent collisions with user-defined headers. | ||
| * Ideally, come up with the Interceptor-level mechanism for this. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| const INTERNAL_REQUEST_ID_HEADER_NAME = "x-interceptors-internal-request-id"; | ||
| function getGlobalSymbol(symbol) { | ||
| return globalThis[symbol] || void 0; | ||
| } | ||
| function setGlobalSymbol(symbol, value) { | ||
| globalThis[symbol] = value; | ||
| } | ||
| function deleteGlobalSymbol(symbol) { | ||
| delete globalThis[symbol]; | ||
| } | ||
| let InterceptorReadyState = /* @__PURE__ */ function(InterceptorReadyState$1) { | ||
| InterceptorReadyState$1["INACTIVE"] = "INACTIVE"; | ||
| InterceptorReadyState$1["APPLYING"] = "APPLYING"; | ||
| InterceptorReadyState$1["APPLIED"] = "APPLIED"; | ||
| InterceptorReadyState$1["DISPOSING"] = "DISPOSING"; | ||
| InterceptorReadyState$1["DISPOSED"] = "DISPOSED"; | ||
| return InterceptorReadyState$1; | ||
| }({}); | ||
| var Interceptor = class { | ||
| constructor(symbol) { | ||
| this.symbol = symbol; | ||
| this.readyState = InterceptorReadyState.INACTIVE; | ||
| this.emitter = new strict_event_emitter.Emitter(); | ||
| this.subscriptions = []; | ||
| this.logger = new _open_draft_logger.Logger(symbol.description); | ||
| this.emitter.setMaxListeners(0); | ||
| this.logger.info("constructing the interceptor..."); | ||
| } | ||
| /** | ||
| * Determine if this interceptor can be applied | ||
| * in the current environment. | ||
| */ | ||
| checkEnvironment() { | ||
| return true; | ||
| } | ||
| /** | ||
| * Apply this interceptor to the current process. | ||
| * Returns an already running interceptor instance if it's present. | ||
| */ | ||
| apply() { | ||
| const logger = this.logger.extend("apply"); | ||
| logger.info("applying the interceptor..."); | ||
| if (this.readyState === InterceptorReadyState.APPLIED) { | ||
| logger.info("intercepted already applied!"); | ||
| return; | ||
| } | ||
| if (!this.checkEnvironment()) { | ||
| logger.info("the interceptor cannot be applied in this environment!"); | ||
| return; | ||
| } | ||
| this.readyState = InterceptorReadyState.APPLYING; | ||
| const runningInstance = this.getInstance(); | ||
| if (runningInstance) { | ||
| logger.info("found a running instance, reusing..."); | ||
| this.on = (event, listener) => { | ||
| logger.info("proxying the \"%s\" listener", event); | ||
| runningInstance.emitter.addListener(event, listener); | ||
| this.subscriptions.push(() => { | ||
| runningInstance.emitter.removeListener(event, listener); | ||
| logger.info("removed proxied \"%s\" listener!", event); | ||
| }); | ||
| return this; | ||
| }; | ||
| this.readyState = InterceptorReadyState.APPLIED; | ||
| return; | ||
| } | ||
| logger.info("no running instance found, setting up a new instance..."); | ||
| this.setup(); | ||
| this.setInstance(); | ||
| this.readyState = InterceptorReadyState.APPLIED; | ||
| } | ||
| /** | ||
| * Setup the module augments and stubs necessary for this interceptor. | ||
| * This method is not run if there's a running interceptor instance | ||
| * to prevent instantiating an interceptor multiple times. | ||
| */ | ||
| setup() {} | ||
| /** | ||
| * Listen to the interceptor's public events. | ||
| */ | ||
| on(event, listener) { | ||
| const logger = this.logger.extend("on"); | ||
| if (this.readyState === InterceptorReadyState.DISPOSING || this.readyState === InterceptorReadyState.DISPOSED) { | ||
| logger.info("cannot listen to events, already disposed!"); | ||
| return this; | ||
| } | ||
| logger.info("adding \"%s\" event listener:", event, listener); | ||
| this.emitter.on(event, listener); | ||
| return this; | ||
| } | ||
| once(event, listener) { | ||
| this.emitter.once(event, listener); | ||
| return this; | ||
| } | ||
| off(event, listener) { | ||
| this.emitter.off(event, listener); | ||
| return this; | ||
| } | ||
| removeAllListeners(event) { | ||
| this.emitter.removeAllListeners(event); | ||
| return this; | ||
| } | ||
| /** | ||
| * Disposes of any side-effects this interceptor has introduced. | ||
| */ | ||
| dispose() { | ||
| const logger = this.logger.extend("dispose"); | ||
| if (this.readyState === InterceptorReadyState.DISPOSED) { | ||
| logger.info("cannot dispose, already disposed!"); | ||
| return; | ||
| } | ||
| logger.info("disposing the interceptor..."); | ||
| this.readyState = InterceptorReadyState.DISPOSING; | ||
| if (!this.getInstance()) { | ||
| logger.info("no interceptors running, skipping dispose..."); | ||
| return; | ||
| } | ||
| this.clearInstance(); | ||
| logger.info("global symbol deleted:", getGlobalSymbol(this.symbol)); | ||
| if (this.subscriptions.length > 0) { | ||
| logger.info("disposing of %d subscriptions...", this.subscriptions.length); | ||
| for (const dispose of this.subscriptions) dispose(); | ||
| this.subscriptions = []; | ||
| logger.info("disposed of all subscriptions!", this.subscriptions.length); | ||
| } | ||
| this.emitter.removeAllListeners(); | ||
| logger.info("destroyed the listener!"); | ||
| this.readyState = InterceptorReadyState.DISPOSED; | ||
| } | ||
| getInstance() { | ||
| const instance = getGlobalSymbol(this.symbol); | ||
| this.logger.info("retrieved global instance:", instance?.constructor?.name); | ||
| return instance; | ||
| } | ||
| setInstance() { | ||
| setGlobalSymbol(this.symbol, this); | ||
| this.logger.info("set global instance!", this.symbol.description); | ||
| } | ||
| clearInstance() { | ||
| deleteGlobalSymbol(this.symbol); | ||
| this.logger.info("cleared global instance!", this.symbol.description); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/InterceptorError.ts | ||
| var InterceptorError = class InterceptorError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "InterceptorError"; | ||
| Object.setPrototypeOf(this, InterceptorError.prototype); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/RequestController.ts | ||
| var RequestController = class RequestController { | ||
| static { | ||
| this.PENDING = 0; | ||
| } | ||
| static { | ||
| this.PASSTHROUGH = 1; | ||
| } | ||
| static { | ||
| this.RESPONSE = 2; | ||
| } | ||
| static { | ||
| this.ERROR = 3; | ||
| } | ||
| constructor(request, source) { | ||
| this.request = request; | ||
| this.source = source; | ||
| this.readyState = RequestController.PENDING; | ||
| this.handled = new _open_draft_deferred_promise.DeferredPromise(); | ||
| } | ||
| get #handled() { | ||
| return this.handled; | ||
| } | ||
| /** | ||
| * Perform this request as-is. | ||
| */ | ||
| async passthrough() { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url); | ||
| this.readyState = RequestController.PASSTHROUGH; | ||
| await this.source.passthrough(); | ||
| this.#handled.resolve(); | ||
| } | ||
| /** | ||
| * Respond to this request with the given `Response` instance. | ||
| * | ||
| * @example | ||
| * controller.respondWith(new Response()) | ||
| * controller.respondWith(Response.json({ id })) | ||
| * controller.respondWith(Response.error()) | ||
| */ | ||
| respondWith(response) { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState); | ||
| this.readyState = RequestController.RESPONSE; | ||
| this.#handled.resolve(); | ||
| /** | ||
| * @note Although `source.respondWith()` is potentially asynchronous, | ||
| * do NOT await it for backward-compatibility. Awaiting it will short-circuit | ||
| * the request listener invocation as soon as a listener responds to a request. | ||
| * Ideally, that's what we want, but that's not what we promise the user. | ||
| */ | ||
| this.source.respondWith(response); | ||
| } | ||
| /** | ||
| * Error this request with the given reason. | ||
| * | ||
| * @example | ||
| * controller.errorWith() | ||
| * controller.errorWith(new Error('Oops!')) | ||
| * controller.errorWith({ message: 'Oops!'}) | ||
| */ | ||
| errorWith(reason) { | ||
| outvariant.invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState); | ||
| this.readyState = RequestController.ERROR; | ||
| this.source.errorWith(reason); | ||
| this.#handled.resolve(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/createRequestId.ts | ||
| /** | ||
| * Generate a random ID string to represent a request. | ||
| * @example | ||
| * createRequestId() | ||
| * // "f774b6c9c600f" | ||
| */ | ||
| function createRequestId() { | ||
| return Math.random().toString(16).slice(2); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/canParseUrl.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given URL string | ||
| * can be parsed into a `URL` instance. | ||
| * A substitute for `URL.canParse()` for Node.js 18. | ||
| */ | ||
| function canParseUrl(url) { | ||
| try { | ||
| new URL(url); | ||
| return true; | ||
| } catch (_error) { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/getValueBySymbol.ts | ||
| /** | ||
| * Returns the value behind the symbol with the given name. | ||
| */ | ||
| function getValueBySymbol(symbolName, source) { | ||
| const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => { | ||
| return symbol$1.description === symbolName; | ||
| }); | ||
| if (symbol) return Reflect.get(source, symbol); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/fetchUtils.ts | ||
| var FetchRequest = class FetchRequest extends Request { | ||
| static #resolveProperty(input, init = {}, key) { | ||
| return init[key] ?? (input instanceof Request ? input[key] : void 0); | ||
| } | ||
| /** | ||
| * Check if the given request method is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#methods | ||
| */ | ||
| static isConfigurableMethod(method) { | ||
| return method !== "CONNECT" && method !== "TRACE" && method !== "TRACK"; | ||
| } | ||
| static isMethodWithBody(method) { | ||
| return method !== "HEAD" && method !== "GET" && FetchRequest.isConfigurableMethod(method); | ||
| } | ||
| /** | ||
| * Check if the given request `mode` is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#concept-request-mode | ||
| */ | ||
| static isConfigurableMode(mode) { | ||
| return mode !== "navigate" && mode !== "websocket" && mode !== "webtransport"; | ||
| } | ||
| constructor(input, init) { | ||
| const method = FetchRequest.#resolveProperty(input, init, "method") || "GET"; | ||
| const safeMethod = FetchRequest.isConfigurableMethod(method) ? method : "GET"; | ||
| const hasExplicitBody = init != null && "body" in init; | ||
| /** | ||
| * Only include `body` in the super init when it needs to be overridden. | ||
| * When `input` is a Request and no explicit body is in `init`, let the | ||
| * Request constructor handle body transfer naturally so it properly | ||
| * marks the original request's body as consumed (bodyUsed = true). | ||
| */ | ||
| const bodyInit = !FetchRequest.isMethodWithBody(method) ? { body: void 0 } : hasExplicitBody ? { body: init.body } : {}; | ||
| const mode = FetchRequest.#resolveProperty(input, init, "mode") ?? void 0; | ||
| const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : void 0; | ||
| super(input, { | ||
| ...init || {}, | ||
| method: safeMethod, | ||
| mode: safeMode, | ||
| duplex: init?.duplex ?? (FetchRequest.isMethodWithBody(method) ? "half" : void 0), | ||
| ...bodyInit | ||
| }); | ||
| if (method !== safeMethod) this.#setInternalProperty("method", method); | ||
| if (method === "CONNECT") { | ||
| const url = new URL(input instanceof Request ? input.url : input); | ||
| let authority; | ||
| /** | ||
| * @note Node.js has a bug parsing raw CONNECT requests URLs like | ||
| * "http://127.0.0.1:1337/localhost:80". It would treat "localhost:" as a protocol. | ||
| */ | ||
| if (url.protocol === "localhost:") authority = url.href; | ||
| else authority = url.pathname.replace(/^\/+/, ""); | ||
| /** | ||
| * @note Define "url" as a getter because Undici uses their own | ||
| * logic to resolve the "request.url" property. Simply reassigning | ||
| * its value doesn't do anything. This is a destructive action | ||
| * but it's safe because "CONNECT" requests are forbidden per fetch. | ||
| */ | ||
| Object.defineProperty(this, "url", { | ||
| get: () => authority, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| if (mode != null && mode !== safeMode) this.#setInternalProperty("mode", mode); | ||
| } | ||
| #setInternalProperty(key, value) { | ||
| const internalState = getValueBySymbol("state", this); | ||
| if (internalState) Reflect.set(internalState, key, value); | ||
| else Object.defineProperty(this, key, { | ||
| value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| }; | ||
| var FetchResponse = class FetchResponse extends Response { | ||
| static { | ||
| this.STATUS_CODES_WITHOUT_BODY = [ | ||
| 101, | ||
| 103, | ||
| 204, | ||
| 205, | ||
| 304 | ||
| ]; | ||
| } | ||
| static { | ||
| this.STATUS_CODES_WITH_REDIRECT = [ | ||
| 301, | ||
| 302, | ||
| 303, | ||
| 307, | ||
| 308 | ||
| ]; | ||
| } | ||
| static isConfigurableStatusCode(status) { | ||
| return status >= 200 && status <= 599; | ||
| } | ||
| static isRedirectResponse(status) { | ||
| return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status); | ||
| } | ||
| /** | ||
| * Returns a boolean indicating whether the given response status | ||
| * code represents a response that can have a body. | ||
| */ | ||
| static isResponseWithBody(status) { | ||
| return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status); | ||
| } | ||
| static setUrl(url, response) { | ||
| if (!url || url === "about:" || !canParseUrl(url)) return; | ||
| const state = getValueBySymbol("state", response); | ||
| if (state) state.urlList.push(new URL(url)); | ||
| else Object.defineProperty(response, "url", { | ||
| value: url, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| /** | ||
| * Parses the given raw HTTP headers into a Fetch API `Headers` instance. | ||
| */ | ||
| static parseRawHeaders(rawHeaders) { | ||
| const headers = new Headers(); | ||
| for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]); | ||
| return headers; | ||
| } | ||
| /** | ||
| * Safely clones the given `Response`. | ||
| * Coerces response clone exceptions into 500 mocked responses. | ||
| * Handy in the environments that introduce arbitrary response | ||
| * cloning restrictions, like "101 Switching Protocols" cloning | ||
| * in "miniflare". | ||
| */ | ||
| static clone(response) { | ||
| try { | ||
| return response.clone(); | ||
| } catch (error) { | ||
| return Response.json(error instanceof Error ? { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| } : {}, { | ||
| status: 500, | ||
| statusText: "Unclonable Response" | ||
| }); | ||
| } | ||
| } | ||
| constructor(body, init = {}) { | ||
| const status = init.status ?? 200; | ||
| const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200; | ||
| const finalBody = FetchResponse.isResponseWithBody(status) ? body : null; | ||
| super(finalBody, { | ||
| status: safeStatus, | ||
| statusText: init.statusText, | ||
| headers: init.headers | ||
| }); | ||
| if (status !== safeStatus) { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const state = getValueBySymbol("state", this); | ||
| if (state) state.status = status; | ||
| else Object.defineProperty(this, "status", { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| FetchResponse.setUrl(init.url, this); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'FetchRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'FetchResponse', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return FetchResponse; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'INTERNAL_REQUEST_ID_HEADER_NAME', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return INTERNAL_REQUEST_ID_HEADER_NAME; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'Interceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return Interceptor; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'InterceptorError', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return InterceptorError; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'InterceptorReadyState', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return InterceptorReadyState; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'RequestController', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return RequestController; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'canParseUrl', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return canParseUrl; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createRequestId', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createRequestId; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'deleteGlobalSymbol', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return deleteGlobalSymbol; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'getGlobalSymbol', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return getGlobalSymbol; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=fetchUtils-BAlG765A.cjs.map |
| {"version":3,"file":"fetchUtils-BAlG765A.cjs","names":["symbol: symbol","Emitter","Logger","request: Request","source: RequestControllerSource","DeferredPromise","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/Interceptor.ts","../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/createRequestId.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts"],"sourcesContent":["import { Logger } from '@open-draft/logger'\nimport { Emitter, Listener } from 'strict-event-emitter'\n\nexport type InterceptorEventMap = Record<string, any>\nexport type InterceptorSubscription = () => void\n\n/**\n * Request header name to detect when a single request\n * is being handled by nested interceptors (XHR -> ClientRequest).\n * Obscure by design to prevent collisions with user-defined headers.\n * Ideally, come up with the Interceptor-level mechanism for this.\n * @see https://github.com/mswjs/interceptors/issues/378\n */\nexport const INTERNAL_REQUEST_ID_HEADER_NAME =\n 'x-interceptors-internal-request-id'\n\nexport function getGlobalSymbol<V>(symbol: Symbol): V | undefined {\n return (\n // @ts-ignore https://github.com/Microsoft/TypeScript/issues/24587\n globalThis[symbol] || undefined\n )\n}\n\nfunction setGlobalSymbol(symbol: Symbol, value: any): void {\n // @ts-ignore\n globalThis[symbol] = value\n}\n\nexport function deleteGlobalSymbol(symbol: Symbol): void {\n // @ts-ignore\n delete globalThis[symbol]\n}\n\nexport enum InterceptorReadyState {\n INACTIVE = 'INACTIVE',\n APPLYING = 'APPLYING',\n APPLIED = 'APPLIED',\n DISPOSING = 'DISPOSING',\n DISPOSED = 'DISPOSED',\n}\n\nexport type ExtractEventNames<Events extends Record<string, any>> =\n Events extends Record<infer EventName, any> ? EventName : never\n\nexport class Interceptor<Events extends InterceptorEventMap> {\n protected emitter: Emitter<Events>\n protected subscriptions: Array<InterceptorSubscription>\n protected logger: Logger\n\n public readyState: InterceptorReadyState\n\n constructor(private readonly symbol: symbol) {\n this.readyState = InterceptorReadyState.INACTIVE\n\n this.emitter = new Emitter()\n this.subscriptions = []\n this.logger = new Logger(symbol.description!)\n\n // Do not limit the maximum number of listeners\n // so not to limit the maximum amount of parallel events emitted.\n this.emitter.setMaxListeners(0)\n\n this.logger.info('constructing the interceptor...')\n }\n\n /**\n * Determine if this interceptor can be applied\n * in the current environment.\n */\n protected checkEnvironment(): boolean {\n return true\n }\n\n /**\n * Apply this interceptor to the current process.\n * Returns an already running interceptor instance if it's present.\n */\n public apply(): void {\n const logger = this.logger.extend('apply')\n logger.info('applying the interceptor...')\n\n if (this.readyState === InterceptorReadyState.APPLIED) {\n logger.info('intercepted already applied!')\n return\n }\n\n const shouldApply = this.checkEnvironment()\n\n if (!shouldApply) {\n logger.info('the interceptor cannot be applied in this environment!')\n return\n }\n\n this.readyState = InterceptorReadyState.APPLYING\n\n // Whenever applying a new interceptor, check if it hasn't been applied already.\n // This enables to apply the same interceptor multiple times, for example from a different\n // interceptor, only proxying events but keeping the stubs in a single place.\n const runningInstance = this.getInstance()\n\n if (runningInstance) {\n logger.info('found a running instance, reusing...')\n\n // Proxy any listeners you set on this instance to the running instance.\n this.on = (event, listener) => {\n logger.info('proxying the \"%s\" listener', event)\n\n // Add listeners to the running instance so they appear\n // at the top of the event listeners list and are executed first.\n runningInstance.emitter.addListener(event, listener)\n\n // Ensure that once this interceptor instance is disposed,\n // it removes all listeners it has appended to the running interceptor instance.\n this.subscriptions.push(() => {\n runningInstance.emitter.removeListener(event, listener)\n logger.info('removed proxied \"%s\" listener!', event)\n })\n\n return this\n }\n\n this.readyState = InterceptorReadyState.APPLIED\n\n return\n }\n\n logger.info('no running instance found, setting up a new instance...')\n\n // Setup the interceptor.\n this.setup()\n\n // Store the newly applied interceptor instance globally.\n this.setInstance()\n\n this.readyState = InterceptorReadyState.APPLIED\n }\n\n /**\n * Setup the module augments and stubs necessary for this interceptor.\n * This method is not run if there's a running interceptor instance\n * to prevent instantiating an interceptor multiple times.\n */\n protected setup(): void {}\n\n /**\n * Listen to the interceptor's public events.\n */\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n const logger = this.logger.extend('on')\n\n if (\n this.readyState === InterceptorReadyState.DISPOSING ||\n this.readyState === InterceptorReadyState.DISPOSED\n ) {\n logger.info('cannot listen to events, already disposed!')\n return this\n }\n\n logger.info('adding \"%s\" event listener:', event, listener)\n\n this.emitter.on(event, listener)\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n this.emitter.once(event, listener)\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n this.emitter.off(event, listener)\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName\n ): this {\n this.emitter.removeAllListeners(event)\n return this\n }\n\n /**\n * Disposes of any side-effects this interceptor has introduced.\n */\n public dispose(): void {\n const logger = this.logger.extend('dispose')\n\n if (this.readyState === InterceptorReadyState.DISPOSED) {\n logger.info('cannot dispose, already disposed!')\n return\n }\n\n logger.info('disposing the interceptor...')\n this.readyState = InterceptorReadyState.DISPOSING\n\n if (!this.getInstance()) {\n logger.info('no interceptors running, skipping dispose...')\n return\n }\n\n // Delete the global symbol as soon as possible,\n // indicating that the interceptor is no longer running.\n this.clearInstance()\n\n logger.info('global symbol deleted:', getGlobalSymbol(this.symbol))\n\n if (this.subscriptions.length > 0) {\n logger.info('disposing of %d subscriptions...', this.subscriptions.length)\n\n for (const dispose of this.subscriptions) {\n dispose()\n }\n\n this.subscriptions = []\n\n logger.info('disposed of all subscriptions!', this.subscriptions.length)\n }\n\n this.emitter.removeAllListeners()\n logger.info('destroyed the listener!')\n\n this.readyState = InterceptorReadyState.DISPOSED\n }\n\n private getInstance(): this | undefined {\n const instance = getGlobalSymbol<this>(this.symbol)\n this.logger.info('retrieved global instance:', instance?.constructor?.name)\n return instance\n }\n\n private setInstance(): void {\n setGlobalSymbol(this.symbol, this)\n this.logger.info('set global instance!', this.symbol.description)\n }\n\n private clearInstance(): void {\n deleteGlobalSymbol(this.symbol)\n this.logger.info('cleared global instance!', this.symbol.description)\n }\n}\n","export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Generate a random ID string to represent a request.\n * @example\n * createRequestId()\n * // \"f774b6c9c600f\"\n */\nexport function createRequestId(): string {\n return Math.random().toString(16).slice(2)\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n if (status !== safeStatus) {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const state = getValueBySymbol<UndiciResponseState>('state', this)\n\n if (state) {\n state.status = status\n } else {\n Object.defineProperty(this, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAa,kCACX;AAEF,SAAgB,gBAAmB,QAA+B;AAChE,QAEE,WAAW,WAAW;;AAI1B,SAAS,gBAAgB,QAAgB,OAAkB;AAEzD,YAAW,UAAU;;AAGvB,SAAgB,mBAAmB,QAAsB;AAEvD,QAAO,WAAW;;AAGpB,IAAY,0EAAL;AACL;AACA;AACA;AACA;AACA;;;AAMF,IAAa,cAAb,MAA6D;CAO3D,YAAY,AAAiBA,QAAgB;EAAhB;AAC3B,OAAK,aAAa,sBAAsB;AAExC,OAAK,UAAU,IAAIC,8BAAS;AAC5B,OAAK,gBAAgB,EAAE;AACvB,OAAK,SAAS,IAAIC,0BAAO,OAAO,YAAa;AAI7C,OAAK,QAAQ,gBAAgB,EAAE;AAE/B,OAAK,OAAO,KAAK,kCAAkC;;;;;;CAOrD,AAAU,mBAA4B;AACpC,SAAO;;;;;;CAOT,AAAO,QAAc;EACnB,MAAM,SAAS,KAAK,OAAO,OAAO,QAAQ;AAC1C,SAAO,KAAK,8BAA8B;AAE1C,MAAI,KAAK,eAAe,sBAAsB,SAAS;AACrD,UAAO,KAAK,+BAA+B;AAC3C;;AAKF,MAAI,CAFgB,KAAK,kBAAkB,EAEzB;AAChB,UAAO,KAAK,yDAAyD;AACrE;;AAGF,OAAK,aAAa,sBAAsB;EAKxC,MAAM,kBAAkB,KAAK,aAAa;AAE1C,MAAI,iBAAiB;AACnB,UAAO,KAAK,uCAAuC;AAGnD,QAAK,MAAM,OAAO,aAAa;AAC7B,WAAO,KAAK,gCAA8B,MAAM;AAIhD,oBAAgB,QAAQ,YAAY,OAAO,SAAS;AAIpD,SAAK,cAAc,WAAW;AAC5B,qBAAgB,QAAQ,eAAe,OAAO,SAAS;AACvD,YAAO,KAAK,oCAAkC,MAAM;MACpD;AAEF,WAAO;;AAGT,QAAK,aAAa,sBAAsB;AAExC;;AAGF,SAAO,KAAK,0DAA0D;AAGtE,OAAK,OAAO;AAGZ,OAAK,aAAa;AAElB,OAAK,aAAa,sBAAsB;;;;;;;CAQ1C,AAAU,QAAc;;;;CAKxB,AAAO,GACL,OACA,UACM;EACN,MAAM,SAAS,KAAK,OAAO,OAAO,KAAK;AAEvC,MACE,KAAK,eAAe,sBAAsB,aAC1C,KAAK,eAAe,sBAAsB,UAC1C;AACA,UAAO,KAAK,6CAA6C;AACzD,UAAO;;AAGT,SAAO,KAAK,iCAA+B,OAAO,SAAS;AAE3D,OAAK,QAAQ,GAAG,OAAO,SAAS;AAChC,SAAO;;CAGT,AAAO,KACL,OACA,UACM;AACN,OAAK,QAAQ,KAAK,OAAO,SAAS;AAClC,SAAO;;CAGT,AAAO,IACL,OACA,UACM;AACN,OAAK,QAAQ,IAAI,OAAO,SAAS;AACjC,SAAO;;CAGT,AAAO,mBACL,OACM;AACN,OAAK,QAAQ,mBAAmB,MAAM;AACtC,SAAO;;;;;CAMT,AAAO,UAAgB;EACrB,MAAM,SAAS,KAAK,OAAO,OAAO,UAAU;AAE5C,MAAI,KAAK,eAAe,sBAAsB,UAAU;AACtD,UAAO,KAAK,oCAAoC;AAChD;;AAGF,SAAO,KAAK,+BAA+B;AAC3C,OAAK,aAAa,sBAAsB;AAExC,MAAI,CAAC,KAAK,aAAa,EAAE;AACvB,UAAO,KAAK,+CAA+C;AAC3D;;AAKF,OAAK,eAAe;AAEpB,SAAO,KAAK,0BAA0B,gBAAgB,KAAK,OAAO,CAAC;AAEnE,MAAI,KAAK,cAAc,SAAS,GAAG;AACjC,UAAO,KAAK,oCAAoC,KAAK,cAAc,OAAO;AAE1E,QAAK,MAAM,WAAW,KAAK,cACzB,UAAS;AAGX,QAAK,gBAAgB,EAAE;AAEvB,UAAO,KAAK,kCAAkC,KAAK,cAAc,OAAO;;AAG1E,OAAK,QAAQ,oBAAoB;AACjC,SAAO,KAAK,0BAA0B;AAEtC,OAAK,aAAa,sBAAsB;;CAG1C,AAAQ,cAAgC;EACtC,MAAM,WAAW,gBAAsB,KAAK,OAAO;AACnD,OAAK,OAAO,KAAK,8BAA8B,UAAU,aAAa,KAAK;AAC3E,SAAO;;CAGT,AAAQ,cAAoB;AAC1B,kBAAgB,KAAK,QAAQ,KAAK;AAClC,OAAK,OAAO,KAAK,wBAAwB,KAAK,OAAO,YAAY;;CAGnE,AAAQ,gBAAsB;AAC5B,qBAAmB,KAAK,OAAO;AAC/B,OAAK,OAAO,KAAK,4BAA4B,KAAK,OAAO,YAAY;;;;;;ACtPzE,IAAa,mBAAb,MAAa,yBAAyB,MAAM;CAC1C,YAAY,SAAkB;AAC5B,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,SAAO,eAAe,MAAM,iBAAiB,UAAU;;;;;;ACM3D,IAAa,oBAAb,MAAa,kBAAkB;;iBACZ;;;qBACI;;;kBACH;;;eACH;;CAUf,YACE,AAAmBC,SACnB,AAAmBC,QACnB;EAFmB;EACA;AAEnB,OAAK,aAAa,kBAAkB;AACpC,OAAK,UAAU,IAAIC,8CAAuB;;CAG5C,KAAIC,UAAW;AACb,SAAO,KAAK;;;;;CAMd,MAAa,cAA6B;AACxC,uBAAU,GACR,kBACA,KAAK,eAAe,kBAAkB,SACtC,qFACA,KAAK,QAAQ,QACb,KAAK,QAAQ,IACd;AAED,OAAK,aAAa,kBAAkB;AACpC,QAAM,KAAK,OAAO,aAAa;AAC/B,QAAKA,QAAS,SAAS;;;;;;;;;;CAWzB,AAAO,YAAY,UAA0B;AAC3C,uBAAU,GACR,kBACA,KAAK,eAAe,kBAAkB,SACtC,wGACA,KAAK,QAAQ,QACb,KAAK,QAAQ,KACb,SAAS,QACT,SAAS,cAAc,MACvB,KAAK,WACN;AAED,OAAK,aAAa,kBAAkB;AACpC,QAAKA,QAAS,SAAS;;;;;;;AAQvB,OAAK,OAAO,YAAY,SAAS;;;;;;;;;;CAWnC,AAAO,UAAU,QAAwB;AACvC,uBAAU,GACR,kBACA,KAAK,eAAe,kBAAkB,SACtC,gGACA,KAAK,QAAQ,QACb,KAAK,QAAQ,KACb,QAAQ,UAAU,EAClB,KAAK,WACN;AAED,OAAK,aAAa,kBAAkB;AACpC,OAAK,OAAO,UAAU,OAAO;AAC7B,QAAKA,QAAS,SAAS;;;;;;;;;;;;ACpG3B,SAAgB,kBAA0B;AACxC,QAAO,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE;;;;;;;;;;ACF5C,SAAgB,YAAY,KAAsB;AAChD,KAAI;AACF,MAAI,IAAI,IAAI;AACZ,SAAO;UACA,QAAQ;AACf,SAAO;;;;;;;;;ACPX,SAAgB,iBACd,YACA,QACe;CAGf,MAAM,SAFa,OAAO,sBAAsB,OAAO,CAE7B,MAAM,aAAW;AACzC,SAAOC,SAAO,gBAAgB;GAC9B;AAEF,KAAI,OACF,QAAO,QAAQ,IAAI,QAAQ,OAAO;;;;;ACJtC,IAAa,eAAb,MAAa,qBAAqB,QAAQ;CACxC,QAAOC,gBACL,OACA,OAAyB,EAAE,EAC3B,KACqB;AACrB,SAAO,KAAK,SAAS,iBAAiB,UAAU,MAAM,OAAO;;;;;;CAO/D,OAAO,qBAAqB,QAAyB;AACnD,SAAO,WAAW,aAAa,WAAW,WAAW,WAAW;;CAGlE,OAAO,iBAAiB,QAAyB;AAC/C,SACE,WAAW,UACX,WAAW,SACX,aAAa,qBAAqB,OAAO;;;;;;CAQ7C,OAAO,mBAAmB,MAAuB;AAC/C,SACE,SAAS,cAAc,SAAS,eAAe,SAAS;;CAI5D,YAAY,OAA0B,MAAyB;EAC7D,MAAM,SAAS,cAAaA,gBAAiB,OAAO,MAAM,SAAS,IAAI;EACvE,MAAM,aAAa,aAAa,qBAAqB,OAAO,GACxD,SACA;EAEJ,MAAM,kBAAkB,QAAQ,QAAQ,UAAU;;;;;;;EAQlD,MAAMC,WAAuC,CAAC,aAAa,iBACzD,OACD,GACG,EAAE,MAAM,QAAW,GACnB,kBACE,EAAE,MAAM,KAAK,MAAM,GACnB,EAAE;EAER,MAAM,OACH,cAAaD,gBAAiB,OAAO,MAAM,OAAO,IACnD;EACF,MAAM,WAAW,aAAa,mBAAmB,KAAK,GAAG,OAAO;AAEhE,QAAM,OAAO;GACX,GAAI,QAAQ,EAAE;GACd,QAAQ;GACR,MAAM;GAEN,QACE,MAAM,WACL,aAAa,iBAAiB,OAAO,GAAG,SAAS;GACpD,GAAG;GACJ,CAAC;AAEF,MAAI,WAAW,WACb,OAAKE,oBAAqB,UAAU,OAAO;AAG7C,MAAI,WAAW,WAAW;GACxB,MAAM,MAAM,IAAI,IAAI,iBAAiB,UAAU,MAAM,MAAM,MAAM;GAEjE,IAAIC;;;;;AAMJ,OAAI,IAAI,aAAa,aACnB,aAAY,IAAI;OAEhB,aAAY,IAAI,SAAS,QAAQ,QAAQ,GAAG;;;;;;;AAS9C,UAAO,eAAe,MAAM,OAAO;IACjC,WAAW;IACX,YAAY;IACZ,cAAc;IACf,CAAC;;AAGJ,MAAI,QAAQ,QAAQ,SAAS,SAC3B,OAAKD,oBAAqB,QAAQ,KAAK;;CAI3C,qBACE,KACA,OACM;EACN,MAAM,gBAAgB,iBAAqC,SAAS,KAAK;AAEzE,MAAI,cACF,SAAQ,IAAI,eAAe,KAAK,MAAM;MAEtC,QAAO,eAAe,MAAM,KAAK;GAC/B;GACA,YAAY;GACZ,cAAc;GACd,UAAU;GACX,CAAC;;;AA4BR,IAAa,gBAAb,MAAa,sBAAsB,SAAS;;mCAKE;GAAC;GAAK;GAAK;GAAK;GAAK;GAAI;;;oCAExB;GAAC;GAAK;GAAK;GAAK;GAAK;GAAI;;CAEtE,OAAO,yBAAyB,QAAyB;AACvD,SAAO,UAAU,OAAO,UAAU;;CAGpC,OAAO,mBAAmB,QAAyB;AACjD,SAAO,cAAc,2BAA2B,SAAS,OAAO;;;;;;CAOlE,OAAO,mBAAmB,QAAyB;AACjD,SAAO,CAAC,cAAc,0BAA0B,SAAS,OAAO;;CAGlE,OAAO,OAAO,KAAyB,UAA0B;AAC/D,MAAI,CAAC,OAAO,QAAQ,YAAY,CAAC,YAAY,IAAI,CAC/C;EAGF,MAAM,QAAQ,iBAAsC,SAAS,SAAS;AAEtE,MAAI,MAGF,OAAM,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC;MAGhC,QAAO,eAAe,UAAU,OAAO;GACrC,OAAO;GACP,YAAY;GACZ,cAAc;GACd,UAAU;GACX,CAAC;;;;;CAON,OAAO,gBAAgB,YAAoC;EACzD,MAAM,UAAU,IAAI,SAAS;AAC7B,OAAK,IAAI,OAAO,GAAG,OAAO,WAAW,QAAQ,QAAQ,EACnD,SAAQ,OAAO,WAAW,OAAO,WAAW,OAAO,GAAG;AAExD,SAAO;;;;;;;;;CAUT,OAAO,MAAM,UAA8B;AACzC,MAAI;AAEF,UADc,SAAS,OAAO;WAEvB,OAAO;AACd,UAAO,SAAS,KACd,iBAAiB,QACb;IACE,MAAM,MAAM;IACZ,SAAS,MAAM;IACf,OAAO,MAAM;IACd,GACD,EAAE,EACN;IACE,QAAQ;IACR,YAAY;IACb,CACF;;;CAIL,YAAY,MAAwB,OAA0B,EAAE,EAAE;EAChE,MAAM,SAAS,KAAK,UAAU;EAC9B,MAAM,aAAa,cAAc,yBAAyB,OAAO,GAC7D,SACA;EACJ,MAAM,YAAY,cAAc,mBAAmB,OAAO,GAAG,OAAO;AAEpE,QAAM,WAAW;GACf,QAAQ;GACR,YAAY,KAAK;GACjB,SAAS,KAAK;GACf,CAAC;AAEF,MAAI,WAAW,YAAY;;;;;GAKzB,MAAM,QAAQ,iBAAsC,SAAS,KAAK;AAElE,OAAI,MACF,OAAM,SAAS;OAEf,QAAO,eAAe,MAAM,UAAU;IACpC,OAAO;IACP,YAAY;IACZ,cAAc;IACd,UAAU;IACX,CAAC;;AAIN,gBAAc,OAAO,KAAK,KAAK,KAAK"} |
| import { Logger } from "@open-draft/logger"; | ||
| import { Emitter } from "strict-event-emitter"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { invariant } from "outvariant"; | ||
| //#region src/Interceptor.ts | ||
| /** | ||
| * Request header name to detect when a single request | ||
| * is being handled by nested interceptors (XHR -> ClientRequest). | ||
| * Obscure by design to prevent collisions with user-defined headers. | ||
| * Ideally, come up with the Interceptor-level mechanism for this. | ||
| * @see https://github.com/mswjs/interceptors/issues/378 | ||
| */ | ||
| const INTERNAL_REQUEST_ID_HEADER_NAME = "x-interceptors-internal-request-id"; | ||
| function getGlobalSymbol(symbol) { | ||
| return globalThis[symbol] || void 0; | ||
| } | ||
| function setGlobalSymbol(symbol, value) { | ||
| globalThis[symbol] = value; | ||
| } | ||
| function deleteGlobalSymbol(symbol) { | ||
| delete globalThis[symbol]; | ||
| } | ||
| let InterceptorReadyState = /* @__PURE__ */ function(InterceptorReadyState$1) { | ||
| InterceptorReadyState$1["INACTIVE"] = "INACTIVE"; | ||
| InterceptorReadyState$1["APPLYING"] = "APPLYING"; | ||
| InterceptorReadyState$1["APPLIED"] = "APPLIED"; | ||
| InterceptorReadyState$1["DISPOSING"] = "DISPOSING"; | ||
| InterceptorReadyState$1["DISPOSED"] = "DISPOSED"; | ||
| return InterceptorReadyState$1; | ||
| }({}); | ||
| var Interceptor = class { | ||
| constructor(symbol) { | ||
| this.symbol = symbol; | ||
| this.readyState = InterceptorReadyState.INACTIVE; | ||
| this.emitter = new Emitter(); | ||
| this.subscriptions = []; | ||
| this.logger = new Logger(symbol.description); | ||
| this.emitter.setMaxListeners(0); | ||
| this.logger.info("constructing the interceptor..."); | ||
| } | ||
| /** | ||
| * Determine if this interceptor can be applied | ||
| * in the current environment. | ||
| */ | ||
| checkEnvironment() { | ||
| return true; | ||
| } | ||
| /** | ||
| * Apply this interceptor to the current process. | ||
| * Returns an already running interceptor instance if it's present. | ||
| */ | ||
| apply() { | ||
| const logger = this.logger.extend("apply"); | ||
| logger.info("applying the interceptor..."); | ||
| if (this.readyState === InterceptorReadyState.APPLIED) { | ||
| logger.info("intercepted already applied!"); | ||
| return; | ||
| } | ||
| if (!this.checkEnvironment()) { | ||
| logger.info("the interceptor cannot be applied in this environment!"); | ||
| return; | ||
| } | ||
| this.readyState = InterceptorReadyState.APPLYING; | ||
| const runningInstance = this.getInstance(); | ||
| if (runningInstance) { | ||
| logger.info("found a running instance, reusing..."); | ||
| this.on = (event, listener) => { | ||
| logger.info("proxying the \"%s\" listener", event); | ||
| runningInstance.emitter.addListener(event, listener); | ||
| this.subscriptions.push(() => { | ||
| runningInstance.emitter.removeListener(event, listener); | ||
| logger.info("removed proxied \"%s\" listener!", event); | ||
| }); | ||
| return this; | ||
| }; | ||
| this.readyState = InterceptorReadyState.APPLIED; | ||
| return; | ||
| } | ||
| logger.info("no running instance found, setting up a new instance..."); | ||
| this.setup(); | ||
| this.setInstance(); | ||
| this.readyState = InterceptorReadyState.APPLIED; | ||
| } | ||
| /** | ||
| * Setup the module augments and stubs necessary for this interceptor. | ||
| * This method is not run if there's a running interceptor instance | ||
| * to prevent instantiating an interceptor multiple times. | ||
| */ | ||
| setup() {} | ||
| /** | ||
| * Listen to the interceptor's public events. | ||
| */ | ||
| on(event, listener) { | ||
| const logger = this.logger.extend("on"); | ||
| if (this.readyState === InterceptorReadyState.DISPOSING || this.readyState === InterceptorReadyState.DISPOSED) { | ||
| logger.info("cannot listen to events, already disposed!"); | ||
| return this; | ||
| } | ||
| logger.info("adding \"%s\" event listener:", event, listener); | ||
| this.emitter.on(event, listener); | ||
| return this; | ||
| } | ||
| once(event, listener) { | ||
| this.emitter.once(event, listener); | ||
| return this; | ||
| } | ||
| off(event, listener) { | ||
| this.emitter.off(event, listener); | ||
| return this; | ||
| } | ||
| removeAllListeners(event) { | ||
| this.emitter.removeAllListeners(event); | ||
| return this; | ||
| } | ||
| /** | ||
| * Disposes of any side-effects this interceptor has introduced. | ||
| */ | ||
| dispose() { | ||
| const logger = this.logger.extend("dispose"); | ||
| if (this.readyState === InterceptorReadyState.DISPOSED) { | ||
| logger.info("cannot dispose, already disposed!"); | ||
| return; | ||
| } | ||
| logger.info("disposing the interceptor..."); | ||
| this.readyState = InterceptorReadyState.DISPOSING; | ||
| if (!this.getInstance()) { | ||
| logger.info("no interceptors running, skipping dispose..."); | ||
| return; | ||
| } | ||
| this.clearInstance(); | ||
| logger.info("global symbol deleted:", getGlobalSymbol(this.symbol)); | ||
| if (this.subscriptions.length > 0) { | ||
| logger.info("disposing of %d subscriptions...", this.subscriptions.length); | ||
| for (const dispose of this.subscriptions) dispose(); | ||
| this.subscriptions = []; | ||
| logger.info("disposed of all subscriptions!", this.subscriptions.length); | ||
| } | ||
| this.emitter.removeAllListeners(); | ||
| logger.info("destroyed the listener!"); | ||
| this.readyState = InterceptorReadyState.DISPOSED; | ||
| } | ||
| getInstance() { | ||
| const instance = getGlobalSymbol(this.symbol); | ||
| this.logger.info("retrieved global instance:", instance?.constructor?.name); | ||
| return instance; | ||
| } | ||
| setInstance() { | ||
| setGlobalSymbol(this.symbol, this); | ||
| this.logger.info("set global instance!", this.symbol.description); | ||
| } | ||
| clearInstance() { | ||
| deleteGlobalSymbol(this.symbol); | ||
| this.logger.info("cleared global instance!", this.symbol.description); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/InterceptorError.ts | ||
| var InterceptorError = class InterceptorError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "InterceptorError"; | ||
| Object.setPrototypeOf(this, InterceptorError.prototype); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/RequestController.ts | ||
| var RequestController = class RequestController { | ||
| static { | ||
| this.PENDING = 0; | ||
| } | ||
| static { | ||
| this.PASSTHROUGH = 1; | ||
| } | ||
| static { | ||
| this.RESPONSE = 2; | ||
| } | ||
| static { | ||
| this.ERROR = 3; | ||
| } | ||
| constructor(request, source) { | ||
| this.request = request; | ||
| this.source = source; | ||
| this.readyState = RequestController.PENDING; | ||
| this.handled = new DeferredPromise(); | ||
| } | ||
| get #handled() { | ||
| return this.handled; | ||
| } | ||
| /** | ||
| * Perform this request as-is. | ||
| */ | ||
| async passthrough() { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to passthrough the \"%s %s\" request: the request has already been handled", this.request.method, this.request.url); | ||
| this.readyState = RequestController.PASSTHROUGH; | ||
| await this.source.passthrough(); | ||
| this.#handled.resolve(); | ||
| } | ||
| /** | ||
| * Respond to this request with the given `Response` instance. | ||
| * | ||
| * @example | ||
| * controller.respondWith(new Response()) | ||
| * controller.respondWith(Response.json({ id })) | ||
| * controller.respondWith(Response.error()) | ||
| */ | ||
| respondWith(response) { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)", this.request.method, this.request.url, response.status, response.statusText || "OK", this.readyState); | ||
| this.readyState = RequestController.RESPONSE; | ||
| this.#handled.resolve(); | ||
| /** | ||
| * @note Although `source.respondWith()` is potentially asynchronous, | ||
| * do NOT await it for backward-compatibility. Awaiting it will short-circuit | ||
| * the request listener invocation as soon as a listener responds to a request. | ||
| * Ideally, that's what we want, but that's not what we promise the user. | ||
| */ | ||
| this.source.respondWith(response); | ||
| } | ||
| /** | ||
| * Error this request with the given reason. | ||
| * | ||
| * @example | ||
| * controller.errorWith() | ||
| * controller.errorWith(new Error('Oops!')) | ||
| * controller.errorWith({ message: 'Oops!'}) | ||
| */ | ||
| errorWith(reason) { | ||
| invariant.as(InterceptorError, this.readyState === RequestController.PENDING, "Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)", this.request.method, this.request.url, reason?.toString(), this.readyState); | ||
| this.readyState = RequestController.ERROR; | ||
| this.source.errorWith(reason); | ||
| this.#handled.resolve(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/createRequestId.ts | ||
| /** | ||
| * Generate a random ID string to represent a request. | ||
| * @example | ||
| * createRequestId() | ||
| * // "f774b6c9c600f" | ||
| */ | ||
| function createRequestId() { | ||
| return Math.random().toString(16).slice(2); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/canParseUrl.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given URL string | ||
| * can be parsed into a `URL` instance. | ||
| * A substitute for `URL.canParse()` for Node.js 18. | ||
| */ | ||
| function canParseUrl(url) { | ||
| try { | ||
| new URL(url); | ||
| return true; | ||
| } catch (_error) { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/getValueBySymbol.ts | ||
| /** | ||
| * Returns the value behind the symbol with the given name. | ||
| */ | ||
| function getValueBySymbol(symbolName, source) { | ||
| const symbol = Object.getOwnPropertySymbols(source).find((symbol$1) => { | ||
| return symbol$1.description === symbolName; | ||
| }); | ||
| if (symbol) return Reflect.get(source, symbol); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/fetchUtils.ts | ||
| var FetchRequest = class FetchRequest extends Request { | ||
| static #resolveProperty(input, init = {}, key) { | ||
| return init[key] ?? (input instanceof Request ? input[key] : void 0); | ||
| } | ||
| /** | ||
| * Check if the given request method is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#methods | ||
| */ | ||
| static isConfigurableMethod(method) { | ||
| return method !== "CONNECT" && method !== "TRACE" && method !== "TRACK"; | ||
| } | ||
| static isMethodWithBody(method) { | ||
| return method !== "HEAD" && method !== "GET" && FetchRequest.isConfigurableMethod(method); | ||
| } | ||
| /** | ||
| * Check if the given request `mode` is configurable. | ||
| * @see https://fetch.spec.whatwg.org/#concept-request-mode | ||
| */ | ||
| static isConfigurableMode(mode) { | ||
| return mode !== "navigate" && mode !== "websocket" && mode !== "webtransport"; | ||
| } | ||
| constructor(input, init) { | ||
| const method = FetchRequest.#resolveProperty(input, init, "method") || "GET"; | ||
| const safeMethod = FetchRequest.isConfigurableMethod(method) ? method : "GET"; | ||
| const hasExplicitBody = init != null && "body" in init; | ||
| /** | ||
| * Only include `body` in the super init when it needs to be overridden. | ||
| * When `input` is a Request and no explicit body is in `init`, let the | ||
| * Request constructor handle body transfer naturally so it properly | ||
| * marks the original request's body as consumed (bodyUsed = true). | ||
| */ | ||
| const bodyInit = !FetchRequest.isMethodWithBody(method) ? { body: void 0 } : hasExplicitBody ? { body: init.body } : {}; | ||
| const mode = FetchRequest.#resolveProperty(input, init, "mode") ?? void 0; | ||
| const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : void 0; | ||
| super(input, { | ||
| ...init || {}, | ||
| method: safeMethod, | ||
| mode: safeMode, | ||
| duplex: init?.duplex ?? (FetchRequest.isMethodWithBody(method) ? "half" : void 0), | ||
| ...bodyInit | ||
| }); | ||
| if (method !== safeMethod) this.#setInternalProperty("method", method); | ||
| if (method === "CONNECT") { | ||
| const url = new URL(input instanceof Request ? input.url : input); | ||
| let authority; | ||
| /** | ||
| * @note Node.js has a bug parsing raw CONNECT requests URLs like | ||
| * "http://127.0.0.1:1337/localhost:80". It would treat "localhost:" as a protocol. | ||
| */ | ||
| if (url.protocol === "localhost:") authority = url.href; | ||
| else authority = url.pathname.replace(/^\/+/, ""); | ||
| /** | ||
| * @note Define "url" as a getter because Undici uses their own | ||
| * logic to resolve the "request.url" property. Simply reassigning | ||
| * its value doesn't do anything. This is a destructive action | ||
| * but it's safe because "CONNECT" requests are forbidden per fetch. | ||
| */ | ||
| Object.defineProperty(this, "url", { | ||
| get: () => authority, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| if (mode != null && mode !== safeMode) this.#setInternalProperty("mode", mode); | ||
| } | ||
| #setInternalProperty(key, value) { | ||
| const internalState = getValueBySymbol("state", this); | ||
| if (internalState) Reflect.set(internalState, key, value); | ||
| else Object.defineProperty(this, key, { | ||
| value, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| }; | ||
| var FetchResponse = class FetchResponse extends Response { | ||
| static { | ||
| this.STATUS_CODES_WITHOUT_BODY = [ | ||
| 101, | ||
| 103, | ||
| 204, | ||
| 205, | ||
| 304 | ||
| ]; | ||
| } | ||
| static { | ||
| this.STATUS_CODES_WITH_REDIRECT = [ | ||
| 301, | ||
| 302, | ||
| 303, | ||
| 307, | ||
| 308 | ||
| ]; | ||
| } | ||
| static isConfigurableStatusCode(status) { | ||
| return status >= 200 && status <= 599; | ||
| } | ||
| static isRedirectResponse(status) { | ||
| return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status); | ||
| } | ||
| /** | ||
| * Returns a boolean indicating whether the given response status | ||
| * code represents a response that can have a body. | ||
| */ | ||
| static isResponseWithBody(status) { | ||
| return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status); | ||
| } | ||
| static setUrl(url, response) { | ||
| if (!url || url === "about:" || !canParseUrl(url)) return; | ||
| const state = getValueBySymbol("state", response); | ||
| if (state) state.urlList.push(new URL(url)); | ||
| else Object.defineProperty(response, "url", { | ||
| value: url, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| /** | ||
| * Parses the given raw HTTP headers into a Fetch API `Headers` instance. | ||
| */ | ||
| static parseRawHeaders(rawHeaders) { | ||
| const headers = new Headers(); | ||
| for (let line = 0; line < rawHeaders.length; line += 2) headers.append(rawHeaders[line], rawHeaders[line + 1]); | ||
| return headers; | ||
| } | ||
| /** | ||
| * Safely clones the given `Response`. | ||
| * Coerces response clone exceptions into 500 mocked responses. | ||
| * Handy in the environments that introduce arbitrary response | ||
| * cloning restrictions, like "101 Switching Protocols" cloning | ||
| * in "miniflare". | ||
| */ | ||
| static clone(response) { | ||
| try { | ||
| return response.clone(); | ||
| } catch (error) { | ||
| return Response.json(error instanceof Error ? { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack | ||
| } : {}, { | ||
| status: 500, | ||
| statusText: "Unclonable Response" | ||
| }); | ||
| } | ||
| } | ||
| constructor(body, init = {}) { | ||
| const status = init.status ?? 200; | ||
| const safeStatus = FetchResponse.isConfigurableStatusCode(status) ? status : 200; | ||
| const finalBody = FetchResponse.isResponseWithBody(status) ? body : null; | ||
| super(finalBody, { | ||
| status: safeStatus, | ||
| statusText: init.statusText, | ||
| headers: init.headers | ||
| }); | ||
| if (status !== safeStatus) { | ||
| /** | ||
| * @note Undici keeps an internal "Symbol(state)" that holds | ||
| * the actual value of response status. Update that in Node.js. | ||
| */ | ||
| const state = getValueBySymbol("state", this); | ||
| if (state) state.status = status; | ||
| else Object.defineProperty(this, "status", { | ||
| value: status, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false | ||
| }); | ||
| } | ||
| FetchResponse.setUrl(init.url, this); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { RequestController as a, Interceptor as c, getGlobalSymbol as d, createRequestId as i, InterceptorReadyState as l, FetchResponse as n, InterceptorError as o, canParseUrl as r, INTERNAL_REQUEST_ID_HEADER_NAME as s, FetchRequest as t, deleteGlobalSymbol as u }; | ||
| //# sourceMappingURL=fetchUtils-Bq0Mdmkv.mjs.map |
| {"version":3,"file":"fetchUtils-Bq0Mdmkv.mjs","names":["symbol: symbol","request: Request","source: RequestControllerSource","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/Interceptor.ts","../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/createRequestId.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts"],"sourcesContent":["import { Logger } from '@open-draft/logger'\nimport { Emitter, Listener } from 'strict-event-emitter'\n\nexport type InterceptorEventMap = Record<string, any>\nexport type InterceptorSubscription = () => void\n\n/**\n * Request header name to detect when a single request\n * is being handled by nested interceptors (XHR -> ClientRequest).\n * Obscure by design to prevent collisions with user-defined headers.\n * Ideally, come up with the Interceptor-level mechanism for this.\n * @see https://github.com/mswjs/interceptors/issues/378\n */\nexport const INTERNAL_REQUEST_ID_HEADER_NAME =\n 'x-interceptors-internal-request-id'\n\nexport function getGlobalSymbol<V>(symbol: Symbol): V | undefined {\n return (\n // @ts-ignore https://github.com/Microsoft/TypeScript/issues/24587\n globalThis[symbol] || undefined\n )\n}\n\nfunction setGlobalSymbol(symbol: Symbol, value: any): void {\n // @ts-ignore\n globalThis[symbol] = value\n}\n\nexport function deleteGlobalSymbol(symbol: Symbol): void {\n // @ts-ignore\n delete globalThis[symbol]\n}\n\nexport enum InterceptorReadyState {\n INACTIVE = 'INACTIVE',\n APPLYING = 'APPLYING',\n APPLIED = 'APPLIED',\n DISPOSING = 'DISPOSING',\n DISPOSED = 'DISPOSED',\n}\n\nexport type ExtractEventNames<Events extends Record<string, any>> =\n Events extends Record<infer EventName, any> ? EventName : never\n\nexport class Interceptor<Events extends InterceptorEventMap> {\n protected emitter: Emitter<Events>\n protected subscriptions: Array<InterceptorSubscription>\n protected logger: Logger\n\n public readyState: InterceptorReadyState\n\n constructor(private readonly symbol: symbol) {\n this.readyState = InterceptorReadyState.INACTIVE\n\n this.emitter = new Emitter()\n this.subscriptions = []\n this.logger = new Logger(symbol.description!)\n\n // Do not limit the maximum number of listeners\n // so not to limit the maximum amount of parallel events emitted.\n this.emitter.setMaxListeners(0)\n\n this.logger.info('constructing the interceptor...')\n }\n\n /**\n * Determine if this interceptor can be applied\n * in the current environment.\n */\n protected checkEnvironment(): boolean {\n return true\n }\n\n /**\n * Apply this interceptor to the current process.\n * Returns an already running interceptor instance if it's present.\n */\n public apply(): void {\n const logger = this.logger.extend('apply')\n logger.info('applying the interceptor...')\n\n if (this.readyState === InterceptorReadyState.APPLIED) {\n logger.info('intercepted already applied!')\n return\n }\n\n const shouldApply = this.checkEnvironment()\n\n if (!shouldApply) {\n logger.info('the interceptor cannot be applied in this environment!')\n return\n }\n\n this.readyState = InterceptorReadyState.APPLYING\n\n // Whenever applying a new interceptor, check if it hasn't been applied already.\n // This enables to apply the same interceptor multiple times, for example from a different\n // interceptor, only proxying events but keeping the stubs in a single place.\n const runningInstance = this.getInstance()\n\n if (runningInstance) {\n logger.info('found a running instance, reusing...')\n\n // Proxy any listeners you set on this instance to the running instance.\n this.on = (event, listener) => {\n logger.info('proxying the \"%s\" listener', event)\n\n // Add listeners to the running instance so they appear\n // at the top of the event listeners list and are executed first.\n runningInstance.emitter.addListener(event, listener)\n\n // Ensure that once this interceptor instance is disposed,\n // it removes all listeners it has appended to the running interceptor instance.\n this.subscriptions.push(() => {\n runningInstance.emitter.removeListener(event, listener)\n logger.info('removed proxied \"%s\" listener!', event)\n })\n\n return this\n }\n\n this.readyState = InterceptorReadyState.APPLIED\n\n return\n }\n\n logger.info('no running instance found, setting up a new instance...')\n\n // Setup the interceptor.\n this.setup()\n\n // Store the newly applied interceptor instance globally.\n this.setInstance()\n\n this.readyState = InterceptorReadyState.APPLIED\n }\n\n /**\n * Setup the module augments and stubs necessary for this interceptor.\n * This method is not run if there's a running interceptor instance\n * to prevent instantiating an interceptor multiple times.\n */\n protected setup(): void {}\n\n /**\n * Listen to the interceptor's public events.\n */\n public on<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n const logger = this.logger.extend('on')\n\n if (\n this.readyState === InterceptorReadyState.DISPOSING ||\n this.readyState === InterceptorReadyState.DISPOSED\n ) {\n logger.info('cannot listen to events, already disposed!')\n return this\n }\n\n logger.info('adding \"%s\" event listener:', event, listener)\n\n this.emitter.on(event, listener)\n return this\n }\n\n public once<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n this.emitter.once(event, listener)\n return this\n }\n\n public off<EventName extends ExtractEventNames<Events>>(\n event: EventName,\n listener: Listener<Events[EventName]>\n ): this {\n this.emitter.off(event, listener)\n return this\n }\n\n public removeAllListeners<EventName extends ExtractEventNames<Events>>(\n event?: EventName\n ): this {\n this.emitter.removeAllListeners(event)\n return this\n }\n\n /**\n * Disposes of any side-effects this interceptor has introduced.\n */\n public dispose(): void {\n const logger = this.logger.extend('dispose')\n\n if (this.readyState === InterceptorReadyState.DISPOSED) {\n logger.info('cannot dispose, already disposed!')\n return\n }\n\n logger.info('disposing the interceptor...')\n this.readyState = InterceptorReadyState.DISPOSING\n\n if (!this.getInstance()) {\n logger.info('no interceptors running, skipping dispose...')\n return\n }\n\n // Delete the global symbol as soon as possible,\n // indicating that the interceptor is no longer running.\n this.clearInstance()\n\n logger.info('global symbol deleted:', getGlobalSymbol(this.symbol))\n\n if (this.subscriptions.length > 0) {\n logger.info('disposing of %d subscriptions...', this.subscriptions.length)\n\n for (const dispose of this.subscriptions) {\n dispose()\n }\n\n this.subscriptions = []\n\n logger.info('disposed of all subscriptions!', this.subscriptions.length)\n }\n\n this.emitter.removeAllListeners()\n logger.info('destroyed the listener!')\n\n this.readyState = InterceptorReadyState.DISPOSED\n }\n\n private getInstance(): this | undefined {\n const instance = getGlobalSymbol<this>(this.symbol)\n this.logger.info('retrieved global instance:', instance?.constructor?.name)\n return instance\n }\n\n private setInstance(): void {\n setGlobalSymbol(this.symbol, this)\n this.logger.info('set global instance!', this.symbol.description)\n }\n\n private clearInstance(): void {\n deleteGlobalSymbol(this.symbol)\n this.logger.info('cleared global instance!', this.symbol.description)\n }\n}\n","export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Generate a random ID string to represent a request.\n * @example\n * createRequestId()\n * // \"f774b6c9c600f\"\n */\nexport function createRequestId(): string {\n return Math.random().toString(16).slice(2)\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n if (status !== safeStatus) {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const state = getValueBySymbol<UndiciResponseState>('state', this)\n\n if (state) {\n state.status = status\n } else {\n Object.defineProperty(this, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n}\n"],"mappings":"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"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-BAlG765A.cjs'); | ||
| let _open_draft_deferred_promise = require("@open-draft/deferred-promise"); | ||
| let _open_draft_until = require("@open-draft/until"); | ||
| //#region src/utils/isPropertyAccessible.ts | ||
| /** | ||
| * A function that validates if property access is possible on an object | ||
| * without throwing. It returns `true` if the property access is possible | ||
| * and `false` otherwise. | ||
| * | ||
| * Environments like miniflare will throw on property access on certain objects | ||
| * like Request and Response, for unimplemented properties. | ||
| */ | ||
| function isPropertyAccessible(obj, key) { | ||
| try { | ||
| obj[key]; | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/emitAsync.ts | ||
| /** | ||
| * Emits an event on the given emitter but executes | ||
| * the listeners sequentially. This accounts for asynchronous | ||
| * listeners (e.g. those having "sleep" and handling the request). | ||
| */ | ||
| async function emitAsync(emitter, eventName, ...data) { | ||
| const listeners = emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| for (const listener of listeners) await listener.apply(emitter, data); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isObject.ts | ||
| /** | ||
| * Determines if a given value is an instance of object. | ||
| */ | ||
| function isObject(value, loose = false) { | ||
| return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]"; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/responseUtils.ts | ||
| /** | ||
| * Creates a generic 500 Unhandled Exception response. | ||
| */ | ||
| function createServerErrorResponse(body) { | ||
| return new Response(JSON.stringify(body instanceof Error ? { | ||
| name: body.name, | ||
| message: body.message, | ||
| stack: body.stack | ||
| } : body), { | ||
| status: 500, | ||
| statusText: "Unhandled Exception", | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| /** | ||
| * Check if the given response is a `Response.error()`. | ||
| * | ||
| * @note Some environments, like Miniflare (Cloudflare) do not | ||
| * implement the "Response.type" property and throw on its access. | ||
| * Safely check if we can access "type" on "Response" before continuing. | ||
| * @see https://github.com/mswjs/msw/issues/1834 | ||
| */ | ||
| function isResponseError(response) { | ||
| return response != null && response instanceof Response && isPropertyAccessible(response, "type") && response.type === "error"; | ||
| } | ||
| /** | ||
| * Check if the given value is a `Response` or a Response-like object. | ||
| * This is different from `value instanceof Response` because it supports | ||
| * custom `Response` constructors, like the one when using Undici directly. | ||
| */ | ||
| function isResponseLike(value) { | ||
| return isObject(value, true) && isPropertyAccessible(value, "status") && isPropertyAccessible(value, "statusText") && isPropertyAccessible(value, "bodyUsed"); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isNodeLikeError.ts | ||
| function isNodeLikeError(error) { | ||
| if (error == null) return false; | ||
| if (!(error instanceof Error)) return false; | ||
| return "code" in error && "errno" in error; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/handleRequest.ts | ||
| async function handleRequest(options) { | ||
| const handleResponse = async (response) => { | ||
| if (response instanceof Error) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| if (isResponseError(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| /** | ||
| * Handle normal responses or response-like objects. | ||
| * @note This must come before the arbitrary object check | ||
| * since Response instances are, in fact, objects. | ||
| */ | ||
| if (isResponseLike(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| if (isObject(response)) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| const handleResponseError = async (error) => { | ||
| if (error instanceof require_fetchUtils.InterceptorError) throw result.error; | ||
| if (isNodeLikeError(error)) { | ||
| await options.controller.errorWith(error); | ||
| return true; | ||
| } | ||
| if (error instanceof Response) return await handleResponse(error); | ||
| return false; | ||
| }; | ||
| const requestAbortPromise = new _open_draft_deferred_promise.DeferredPromise(); | ||
| /** | ||
| * @note `signal` is not always defined in React Native. | ||
| */ | ||
| if (options.request.signal) { | ||
| if (options.request.signal.aborted) { | ||
| await options.controller.errorWith(options.request.signal.reason); | ||
| return; | ||
| } | ||
| options.request.signal.addEventListener("abort", () => { | ||
| requestAbortPromise.reject(options.request.signal.reason); | ||
| }, { once: true }); | ||
| } | ||
| const result = await (0, _open_draft_until.until)(async () => { | ||
| const requestListenersPromise = emitAsync(options.emitter, "request", { | ||
| requestId: options.requestId, | ||
| request: options.request, | ||
| controller: options.controller | ||
| }); | ||
| await Promise.race([ | ||
| requestAbortPromise, | ||
| requestListenersPromise, | ||
| options.controller.handled | ||
| ]); | ||
| }); | ||
| if (requestAbortPromise.state === "rejected") { | ||
| await options.controller.errorWith(requestAbortPromise.rejectionReason); | ||
| return; | ||
| } | ||
| if (result.error) { | ||
| if (await handleResponseError(result.error)) return; | ||
| if (options.emitter.listenerCount("unhandledException") > 0) { | ||
| const unhandledExceptionController = new require_fetchUtils.RequestController(options.request, { | ||
| passthrough() {}, | ||
| async respondWith(response) { | ||
| await handleResponse(response); | ||
| }, | ||
| async errorWith(reason) { | ||
| /** | ||
| * @note Handle the result of the unhandled controller | ||
| * in the same way as the original request controller. | ||
| * The exception here is that thrown errors within the | ||
| * "unhandledException" event do NOT result in another | ||
| * emit of the same event. They are forwarded as-is. | ||
| */ | ||
| await options.controller.errorWith(reason); | ||
| } | ||
| }); | ||
| await emitAsync(options.emitter, "unhandledException", { | ||
| error: result.error, | ||
| request: options.request, | ||
| requestId: options.requestId, | ||
| controller: unhandledExceptionController | ||
| }); | ||
| if (unhandledExceptionController.readyState !== require_fetchUtils.RequestController.PENDING) return; | ||
| } | ||
| await options.controller.respondWith(createServerErrorResponse(result.error)); | ||
| return; | ||
| } | ||
| if (options.controller.readyState === require_fetchUtils.RequestController.PENDING) return await options.controller.passthrough(); | ||
| return options.controller.handled; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'emitAsync', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return emitAsync; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'handleRequest', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return handleRequest; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isObject', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isObject; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isPropertyAccessible', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isPropertyAccessible; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isResponseError', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isResponseError; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=handleRequest-DUPo40Df.cjs.map |
| {"version":3,"file":"handleRequest-DUPo40Df.cjs","names":["InterceptorError","DeferredPromise","RequestController"],"sources":["../../src/utils/isPropertyAccessible.ts","../../src/utils/emitAsync.ts","../../src/utils/isObject.ts","../../src/utils/responseUtils.ts","../../src/utils/isNodeLikeError.ts","../../src/utils/handleRequest.ts"],"sourcesContent":["/**\n * A function that validates if property access is possible on an object\n * without throwing. It returns `true` if the property access is possible\n * and `false` otherwise.\n *\n * Environments like miniflare will throw on property access on certain objects\n * like Request and Response, for unimplemented properties.\n */\nexport function isPropertyAccessible<Obj extends Record<string, any>>(\n obj: Obj,\n key: keyof Obj\n) {\n try {\n obj[key]\n return true\n } catch {\n return false\n }\n}\n","import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","/**\n * Determines if a given value is an instance of object.\n */\nexport function isObject<T>(value: any, loose = false): value is T {\n return loose\n ? Object.prototype.toString.call(value).startsWith('[object ')\n : Object.prototype.toString.call(value) === '[object Object]'\n}\n","import { isObject } from './isObject'\nimport { isPropertyAccessible } from './isPropertyAccessible'\n\n/**\n * Creates a generic 500 Unhandled Exception response.\n */\nexport function createServerErrorResponse(body: unknown): Response {\n return new Response(\n JSON.stringify(\n body instanceof Error\n ? {\n name: body.name,\n message: body.message,\n stack: body.stack,\n }\n : body\n ),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n}\n\nexport type ResponseError = Response & { type: 'error' }\n\n/**\n * Check if the given response is a `Response.error()`.\n *\n * @note Some environments, like Miniflare (Cloudflare) do not\n * implement the \"Response.type\" property and throw on its access.\n * Safely check if we can access \"type\" on \"Response\" before continuing.\n * @see https://github.com/mswjs/msw/issues/1834\n */\nexport function isResponseError(response: unknown): response is ResponseError {\n return (\n response != null &&\n response instanceof Response &&\n isPropertyAccessible(response, 'type') &&\n response.type === 'error'\n )\n}\n\n/**\n * Check if the given value is a `Response` or a Response-like object.\n * This is different from `value instanceof Response` because it supports\n * custom `Response` constructors, like the one when using Undici directly.\n */\nexport function isResponseLike(value: unknown): value is Response {\n return (\n isObject<Record<string, any>>(value, true) &&\n isPropertyAccessible(value, 'status') &&\n isPropertyAccessible(value, 'statusText') &&\n isPropertyAccessible(value, 'bodyUsed')\n )\n}\n","export function isNodeLikeError(\n error: unknown\n): error is NodeJS.ErrnoException {\n if (error == null) {\n return false\n }\n\n if (!(error instanceof Error)) {\n return false\n }\n\n return 'code' in error && 'errno' in error\n}\n","import type { Emitter } from 'strict-event-emitter'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport type { HttpRequestEventMap } from '../glossary'\nimport { emitAsync } from './emitAsync'\nimport { RequestController } from '../RequestController'\nimport {\n createServerErrorResponse,\n isResponseError,\n isResponseLike,\n} from './responseUtils'\nimport { InterceptorError } from '../InterceptorError'\nimport { isNodeLikeError } from './isNodeLikeError'\nimport { isObject } from './isObject'\n\ninterface HandleRequestOptions {\n requestId: string\n request: Request\n emitter: Emitter<HttpRequestEventMap>\n controller: RequestController\n}\n\nexport async function handleRequest(\n options: HandleRequestOptions\n): Promise<void> {\n const handleResponse = async (\n response: Response | Error | Record<string, any>\n ) => {\n if (response instanceof Error) {\n await options.controller.errorWith(response)\n return true\n }\n\n // Handle \"Response.error()\" instances.\n if (isResponseError(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n /**\n * Handle normal responses or response-like objects.\n * @note This must come before the arbitrary object check\n * since Response instances are, in fact, objects.\n */\n if (isResponseLike(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n // Handle arbitrary objects provided to `.errorWith(reason)`.\n if (isObject(response)) {\n await options.controller.errorWith(response)\n return true\n }\n\n return false\n }\n\n const handleResponseError = async (error: unknown): Promise<boolean> => {\n // Forward the special interceptor error instances\n // to the developer. These must not be handled in any way.\n if (error instanceof InterceptorError) {\n throw result.error\n }\n\n // Support mocking Node.js-like errors.\n if (isNodeLikeError(error)) {\n await options.controller.errorWith(error)\n return true\n }\n\n // Handle thrown responses.\n if (error instanceof Response) {\n return await handleResponse(error)\n }\n\n return false\n }\n\n // Add the last \"request\" listener to check if the request\n // has been handled in any way. If it hasn't, resolve the\n // response promise with undefined.\n // options.emitter.once('request', async ({ requestId: pendingRequestId }) => {\n // if (\n // pendingRequestId === options.requestId &&\n // options.controller.readyState === RequestController.PENDING\n // ) {\n // await options.controller.passthrough()\n // }\n // })\n\n const requestAbortPromise = new DeferredPromise<void, unknown>()\n\n /**\n * @note `signal` is not always defined in React Native.\n */\n if (options.request.signal) {\n if (options.request.signal.aborted) {\n await options.controller.errorWith(options.request.signal.reason)\n return\n }\n\n options.request.signal.addEventListener(\n 'abort',\n () => {\n requestAbortPromise.reject(options.request.signal.reason)\n },\n { once: true }\n )\n }\n\n const result = await until(async () => {\n // Emit the \"request\" event and wait until all the listeners\n // for that event are finished (e.g. async listeners awaited).\n // By the end of this promise, the developer cannot affect the\n // request anymore.\n const requestListenersPromise = emitAsync(options.emitter, 'request', {\n requestId: options.requestId,\n request: options.request,\n controller: options.controller,\n })\n\n await Promise.race([\n // Short-circuit the request handling promise if the request gets aborted.\n requestAbortPromise,\n requestListenersPromise,\n options.controller.handled,\n ])\n })\n\n // Handle the request being aborted while waiting for the request listeners.\n if (requestAbortPromise.state === 'rejected') {\n await options.controller.errorWith(requestAbortPromise.rejectionReason)\n return\n }\n\n if (result.error) {\n // Handle the error during the request listener execution.\n // These can be thrown responses or request errors.\n if (await handleResponseError(result.error)) {\n return\n }\n\n // If the developer has added \"unhandledException\" listeners,\n // allow them to handle the error. They can translate it to a\n // mocked response, network error, or forward it as-is.\n if (options.emitter.listenerCount('unhandledException') > 0) {\n // Create a new request controller just for the unhandled exception case.\n // This is needed because the original controller might have been already\n // interacted with (e.g. \"respondWith\" or \"errorWith\" called on it).\n const unhandledExceptionController = new RequestController(\n options.request,\n {\n /**\n * @note Intentionally empty passthrough handle.\n * This controller is created within another controller and we only need\n * to know if `unhandledException` listeners handled the request.\n */\n passthrough() {},\n async respondWith(response) {\n await handleResponse(response)\n },\n async errorWith(reason) {\n /**\n * @note Handle the result of the unhandled controller\n * in the same way as the original request controller.\n * The exception here is that thrown errors within the\n * \"unhandledException\" event do NOT result in another\n * emit of the same event. They are forwarded as-is.\n */\n await options.controller.errorWith(reason)\n },\n }\n )\n\n await emitAsync(options.emitter, 'unhandledException', {\n error: result.error,\n request: options.request,\n requestId: options.requestId,\n controller: unhandledExceptionController,\n })\n\n // If all the \"unhandledException\" listeners have finished\n // but have not handled the request in any way, passthrough.\n if (\n unhandledExceptionController.readyState !== RequestController.PENDING\n ) {\n return\n }\n }\n\n // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.\n await options.controller.respondWith(\n createServerErrorResponse(result.error)\n )\n return\n }\n\n // If the request hasn't been handled by this point, passthrough.\n if (options.controller.readyState === RequestController.PENDING) {\n return await options.controller.passthrough()\n }\n\n return options.controller.handled\n}\n"],"mappings":";;;;;;;;;;;;;;AAQA,SAAgB,qBACd,KACA,KACA;AACA,KAAI;AACF,MAAI;AACJ,SAAO;SACD;AACN,SAAO;;;;;;;;;;;ACTX,eAAsB,UAIpB,SACA,WACA,GAAG,MACY;CACf,MAAM,YAAY,QAAQ,UAAU,UAAU;AAE9C,KAAI,UAAU,WAAW,EACvB;AAGF,MAAK,MAAM,YAAY,UACrB,OAAM,SAAS,MAAM,SAAS,KAAK;;;;;;;;ACnBvC,SAAgB,SAAY,OAAY,QAAQ,OAAmB;AACjE,QAAO,QACH,OAAO,UAAU,SAAS,KAAK,MAAM,CAAC,WAAW,WAAW,GAC5D,OAAO,UAAU,SAAS,KAAK,MAAM,KAAK;;;;;;;;ACAhD,SAAgB,0BAA0B,MAAyB;AACjE,QAAO,IAAI,SACT,KAAK,UACH,gBAAgB,QACZ;EACE,MAAM,KAAK;EACX,SAAS,KAAK;EACd,OAAO,KAAK;EACb,GACD,KACL,EACD;EACE,QAAQ;EACR,YAAY;EACZ,SAAS,EACP,gBAAgB,oBACjB;EACF,CACF;;;;;;;;;;AAaH,SAAgB,gBAAgB,UAA8C;AAC5E,QACE,YAAY,QACZ,oBAAoB,YACpB,qBAAqB,UAAU,OAAO,IACtC,SAAS,SAAS;;;;;;;AAStB,SAAgB,eAAe,OAAmC;AAChE,QACE,SAA8B,OAAO,KAAK,IAC1C,qBAAqB,OAAO,SAAS,IACrC,qBAAqB,OAAO,aAAa,IACzC,qBAAqB,OAAO,WAAW;;;;;ACxD3C,SAAgB,gBACd,OACgC;AAChC,KAAI,SAAS,KACX,QAAO;AAGT,KAAI,EAAE,iBAAiB,OACrB,QAAO;AAGT,QAAO,UAAU,SAAS,WAAW;;;;;ACWvC,eAAsB,cACpB,SACe;CACf,MAAM,iBAAiB,OACrB,aACG;AACH,MAAI,oBAAoB,OAAO;AAC7B,SAAM,QAAQ,WAAW,UAAU,SAAS;AAC5C,UAAO;;AAIT,MAAI,gBAAgB,SAAS,EAAE;AAC7B,SAAM,QAAQ,WAAW,YAAY,SAAS;AAC9C,UAAO;;;;;;;AAQT,MAAI,eAAe,SAAS,EAAE;AAC5B,SAAM,QAAQ,WAAW,YAAY,SAAS;AAC9C,UAAO;;AAIT,MAAI,SAAS,SAAS,EAAE;AACtB,SAAM,QAAQ,WAAW,UAAU,SAAS;AAC5C,UAAO;;AAGT,SAAO;;CAGT,MAAM,sBAAsB,OAAO,UAAqC;AAGtE,MAAI,iBAAiBA,oCACnB,OAAM,OAAO;AAIf,MAAI,gBAAgB,MAAM,EAAE;AAC1B,SAAM,QAAQ,WAAW,UAAU,MAAM;AACzC,UAAO;;AAIT,MAAI,iBAAiB,SACnB,QAAO,MAAM,eAAe,MAAM;AAGpC,SAAO;;CAeT,MAAM,sBAAsB,IAAIC,8CAAgC;;;;AAKhE,KAAI,QAAQ,QAAQ,QAAQ;AAC1B,MAAI,QAAQ,QAAQ,OAAO,SAAS;AAClC,SAAM,QAAQ,WAAW,UAAU,QAAQ,QAAQ,OAAO,OAAO;AACjE;;AAGF,UAAQ,QAAQ,OAAO,iBACrB,eACM;AACJ,uBAAoB,OAAO,QAAQ,QAAQ,OAAO,OAAO;KAE3D,EAAE,MAAM,MAAM,CACf;;CAGH,MAAM,SAAS,mCAAY,YAAY;EAKrC,MAAM,0BAA0B,UAAU,QAAQ,SAAS,WAAW;GACpE,WAAW,QAAQ;GACnB,SAAS,QAAQ;GACjB,YAAY,QAAQ;GACrB,CAAC;AAEF,QAAM,QAAQ,KAAK;GAEjB;GACA;GACA,QAAQ,WAAW;GACpB,CAAC;GACF;AAGF,KAAI,oBAAoB,UAAU,YAAY;AAC5C,QAAM,QAAQ,WAAW,UAAU,oBAAoB,gBAAgB;AACvE;;AAGF,KAAI,OAAO,OAAO;AAGhB,MAAI,MAAM,oBAAoB,OAAO,MAAM,CACzC;AAMF,MAAI,QAAQ,QAAQ,cAAc,qBAAqB,GAAG,GAAG;GAI3D,MAAM,+BAA+B,IAAIC,qCACvC,QAAQ,SACR;IAME,cAAc;IACd,MAAM,YAAY,UAAU;AAC1B,WAAM,eAAe,SAAS;;IAEhC,MAAM,UAAU,QAAQ;;;;;;;;AAQtB,WAAM,QAAQ,WAAW,UAAU,OAAO;;IAE7C,CACF;AAED,SAAM,UAAU,QAAQ,SAAS,sBAAsB;IACrD,OAAO,OAAO;IACd,SAAS,QAAQ;IACjB,WAAW,QAAQ;IACnB,YAAY;IACb,CAAC;AAIF,OACE,6BAA6B,eAAeA,qCAAkB,QAE9D;;AAKJ,QAAM,QAAQ,WAAW,YACvB,0BAA0B,OAAO,MAAM,CACxC;AACD;;AAIF,KAAI,QAAQ,WAAW,eAAeA,qCAAkB,QACtD,QAAO,MAAM,QAAQ,WAAW,aAAa;AAG/C,QAAO,QAAQ,WAAW"} |
| import { a as RequestController, o as InterceptorError } from "./fetchUtils-Bq0Mdmkv.mjs"; | ||
| import { DeferredPromise } from "@open-draft/deferred-promise"; | ||
| import { until } from "@open-draft/until"; | ||
| //#region src/utils/isPropertyAccessible.ts | ||
| /** | ||
| * A function that validates if property access is possible on an object | ||
| * without throwing. It returns `true` if the property access is possible | ||
| * and `false` otherwise. | ||
| * | ||
| * Environments like miniflare will throw on property access on certain objects | ||
| * like Request and Response, for unimplemented properties. | ||
| */ | ||
| function isPropertyAccessible(obj, key) { | ||
| try { | ||
| obj[key]; | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/emitAsync.ts | ||
| /** | ||
| * Emits an event on the given emitter but executes | ||
| * the listeners sequentially. This accounts for asynchronous | ||
| * listeners (e.g. those having "sleep" and handling the request). | ||
| */ | ||
| async function emitAsync(emitter, eventName, ...data) { | ||
| const listeners = emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| for (const listener of listeners) await listener.apply(emitter, data); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isObject.ts | ||
| /** | ||
| * Determines if a given value is an instance of object. | ||
| */ | ||
| function isObject(value, loose = false) { | ||
| return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]"; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/responseUtils.ts | ||
| /** | ||
| * Creates a generic 500 Unhandled Exception response. | ||
| */ | ||
| function createServerErrorResponse(body) { | ||
| return new Response(JSON.stringify(body instanceof Error ? { | ||
| name: body.name, | ||
| message: body.message, | ||
| stack: body.stack | ||
| } : body), { | ||
| status: 500, | ||
| statusText: "Unhandled Exception", | ||
| headers: { "Content-Type": "application/json" } | ||
| }); | ||
| } | ||
| /** | ||
| * Check if the given response is a `Response.error()`. | ||
| * | ||
| * @note Some environments, like Miniflare (Cloudflare) do not | ||
| * implement the "Response.type" property and throw on its access. | ||
| * Safely check if we can access "type" on "Response" before continuing. | ||
| * @see https://github.com/mswjs/msw/issues/1834 | ||
| */ | ||
| function isResponseError(response) { | ||
| return response != null && response instanceof Response && isPropertyAccessible(response, "type") && response.type === "error"; | ||
| } | ||
| /** | ||
| * Check if the given value is a `Response` or a Response-like object. | ||
| * This is different from `value instanceof Response` because it supports | ||
| * custom `Response` constructors, like the one when using Undici directly. | ||
| */ | ||
| function isResponseLike(value) { | ||
| return isObject(value, true) && isPropertyAccessible(value, "status") && isPropertyAccessible(value, "statusText") && isPropertyAccessible(value, "bodyUsed"); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/isNodeLikeError.ts | ||
| function isNodeLikeError(error) { | ||
| if (error == null) return false; | ||
| if (!(error instanceof Error)) return false; | ||
| return "code" in error && "errno" in error; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/handleRequest.ts | ||
| async function handleRequest(options) { | ||
| const handleResponse = async (response) => { | ||
| if (response instanceof Error) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| if (isResponseError(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| /** | ||
| * Handle normal responses or response-like objects. | ||
| * @note This must come before the arbitrary object check | ||
| * since Response instances are, in fact, objects. | ||
| */ | ||
| if (isResponseLike(response)) { | ||
| await options.controller.respondWith(response); | ||
| return true; | ||
| } | ||
| if (isObject(response)) { | ||
| await options.controller.errorWith(response); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| const handleResponseError = async (error) => { | ||
| if (error instanceof InterceptorError) throw result.error; | ||
| if (isNodeLikeError(error)) { | ||
| await options.controller.errorWith(error); | ||
| return true; | ||
| } | ||
| if (error instanceof Response) return await handleResponse(error); | ||
| return false; | ||
| }; | ||
| const requestAbortPromise = new DeferredPromise(); | ||
| /** | ||
| * @note `signal` is not always defined in React Native. | ||
| */ | ||
| if (options.request.signal) { | ||
| if (options.request.signal.aborted) { | ||
| await options.controller.errorWith(options.request.signal.reason); | ||
| return; | ||
| } | ||
| options.request.signal.addEventListener("abort", () => { | ||
| requestAbortPromise.reject(options.request.signal.reason); | ||
| }, { once: true }); | ||
| } | ||
| const result = await until(async () => { | ||
| const requestListenersPromise = emitAsync(options.emitter, "request", { | ||
| requestId: options.requestId, | ||
| request: options.request, | ||
| controller: options.controller | ||
| }); | ||
| await Promise.race([ | ||
| requestAbortPromise, | ||
| requestListenersPromise, | ||
| options.controller.handled | ||
| ]); | ||
| }); | ||
| if (requestAbortPromise.state === "rejected") { | ||
| await options.controller.errorWith(requestAbortPromise.rejectionReason); | ||
| return; | ||
| } | ||
| if (result.error) { | ||
| if (await handleResponseError(result.error)) return; | ||
| if (options.emitter.listenerCount("unhandledException") > 0) { | ||
| const unhandledExceptionController = new RequestController(options.request, { | ||
| passthrough() {}, | ||
| async respondWith(response) { | ||
| await handleResponse(response); | ||
| }, | ||
| async errorWith(reason) { | ||
| /** | ||
| * @note Handle the result of the unhandled controller | ||
| * in the same way as the original request controller. | ||
| * The exception here is that thrown errors within the | ||
| * "unhandledException" event do NOT result in another | ||
| * emit of the same event. They are forwarded as-is. | ||
| */ | ||
| await options.controller.errorWith(reason); | ||
| } | ||
| }); | ||
| await emitAsync(options.emitter, "unhandledException", { | ||
| error: result.error, | ||
| request: options.request, | ||
| requestId: options.requestId, | ||
| controller: unhandledExceptionController | ||
| }); | ||
| if (unhandledExceptionController.readyState !== RequestController.PENDING) return; | ||
| } | ||
| await options.controller.respondWith(createServerErrorResponse(result.error)); | ||
| return; | ||
| } | ||
| if (options.controller.readyState === RequestController.PENDING) return await options.controller.passthrough(); | ||
| return options.controller.handled; | ||
| } | ||
| //#endregion | ||
| export { isPropertyAccessible as a, emitAsync as i, isResponseError as n, isObject as r, handleRequest as t }; | ||
| //# sourceMappingURL=handleRequest-u6jpjgrr.mjs.map |
| {"version":3,"file":"handleRequest-u6jpjgrr.mjs","names":[],"sources":["../../src/utils/isPropertyAccessible.ts","../../src/utils/emitAsync.ts","../../src/utils/isObject.ts","../../src/utils/responseUtils.ts","../../src/utils/isNodeLikeError.ts","../../src/utils/handleRequest.ts"],"sourcesContent":["/**\n * A function that validates if property access is possible on an object\n * without throwing. It returns `true` if the property access is possible\n * and `false` otherwise.\n *\n * Environments like miniflare will throw on property access on certain objects\n * like Request and Response, for unimplemented properties.\n */\nexport function isPropertyAccessible<Obj extends Record<string, any>>(\n obj: Obj,\n key: keyof Obj\n) {\n try {\n obj[key]\n return true\n } catch {\n return false\n }\n}\n","import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","/**\n * Determines if a given value is an instance of object.\n */\nexport function isObject<T>(value: any, loose = false): value is T {\n return loose\n ? Object.prototype.toString.call(value).startsWith('[object ')\n : Object.prototype.toString.call(value) === '[object Object]'\n}\n","import { isObject } from './isObject'\nimport { isPropertyAccessible } from './isPropertyAccessible'\n\n/**\n * Creates a generic 500 Unhandled Exception response.\n */\nexport function createServerErrorResponse(body: unknown): Response {\n return new Response(\n JSON.stringify(\n body instanceof Error\n ? {\n name: body.name,\n message: body.message,\n stack: body.stack,\n }\n : body\n ),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n}\n\nexport type ResponseError = Response & { type: 'error' }\n\n/**\n * Check if the given response is a `Response.error()`.\n *\n * @note Some environments, like Miniflare (Cloudflare) do not\n * implement the \"Response.type\" property and throw on its access.\n * Safely check if we can access \"type\" on \"Response\" before continuing.\n * @see https://github.com/mswjs/msw/issues/1834\n */\nexport function isResponseError(response: unknown): response is ResponseError {\n return (\n response != null &&\n response instanceof Response &&\n isPropertyAccessible(response, 'type') &&\n response.type === 'error'\n )\n}\n\n/**\n * Check if the given value is a `Response` or a Response-like object.\n * This is different from `value instanceof Response` because it supports\n * custom `Response` constructors, like the one when using Undici directly.\n */\nexport function isResponseLike(value: unknown): value is Response {\n return (\n isObject<Record<string, any>>(value, true) &&\n isPropertyAccessible(value, 'status') &&\n isPropertyAccessible(value, 'statusText') &&\n isPropertyAccessible(value, 'bodyUsed')\n )\n}\n","export function isNodeLikeError(\n error: unknown\n): error is NodeJS.ErrnoException {\n if (error == null) {\n return false\n }\n\n if (!(error instanceof Error)) {\n return false\n }\n\n return 'code' in error && 'errno' in error\n}\n","import type { Emitter } from 'strict-event-emitter'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport type { HttpRequestEventMap } from '../glossary'\nimport { emitAsync } from './emitAsync'\nimport { RequestController } from '../RequestController'\nimport {\n createServerErrorResponse,\n isResponseError,\n isResponseLike,\n} from './responseUtils'\nimport { InterceptorError } from '../InterceptorError'\nimport { isNodeLikeError } from './isNodeLikeError'\nimport { isObject } from './isObject'\n\ninterface HandleRequestOptions {\n requestId: string\n request: Request\n emitter: Emitter<HttpRequestEventMap>\n controller: RequestController\n}\n\nexport async function handleRequest(\n options: HandleRequestOptions\n): Promise<void> {\n const handleResponse = async (\n response: Response | Error | Record<string, any>\n ) => {\n if (response instanceof Error) {\n await options.controller.errorWith(response)\n return true\n }\n\n // Handle \"Response.error()\" instances.\n if (isResponseError(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n /**\n * Handle normal responses or response-like objects.\n * @note This must come before the arbitrary object check\n * since Response instances are, in fact, objects.\n */\n if (isResponseLike(response)) {\n await options.controller.respondWith(response)\n return true\n }\n\n // Handle arbitrary objects provided to `.errorWith(reason)`.\n if (isObject(response)) {\n await options.controller.errorWith(response)\n return true\n }\n\n return false\n }\n\n const handleResponseError = async (error: unknown): Promise<boolean> => {\n // Forward the special interceptor error instances\n // to the developer. These must not be handled in any way.\n if (error instanceof InterceptorError) {\n throw result.error\n }\n\n // Support mocking Node.js-like errors.\n if (isNodeLikeError(error)) {\n await options.controller.errorWith(error)\n return true\n }\n\n // Handle thrown responses.\n if (error instanceof Response) {\n return await handleResponse(error)\n }\n\n return false\n }\n\n // Add the last \"request\" listener to check if the request\n // has been handled in any way. If it hasn't, resolve the\n // response promise with undefined.\n // options.emitter.once('request', async ({ requestId: pendingRequestId }) => {\n // if (\n // pendingRequestId === options.requestId &&\n // options.controller.readyState === RequestController.PENDING\n // ) {\n // await options.controller.passthrough()\n // }\n // })\n\n const requestAbortPromise = new DeferredPromise<void, unknown>()\n\n /**\n * @note `signal` is not always defined in React Native.\n */\n if (options.request.signal) {\n if (options.request.signal.aborted) {\n await options.controller.errorWith(options.request.signal.reason)\n return\n }\n\n options.request.signal.addEventListener(\n 'abort',\n () => {\n requestAbortPromise.reject(options.request.signal.reason)\n },\n { once: true }\n )\n }\n\n const result = await until(async () => {\n // Emit the \"request\" event and wait until all the listeners\n // for that event are finished (e.g. async listeners awaited).\n // By the end of this promise, the developer cannot affect the\n // request anymore.\n const requestListenersPromise = emitAsync(options.emitter, 'request', {\n requestId: options.requestId,\n request: options.request,\n controller: options.controller,\n })\n\n await Promise.race([\n // Short-circuit the request handling promise if the request gets aborted.\n requestAbortPromise,\n requestListenersPromise,\n options.controller.handled,\n ])\n })\n\n // Handle the request being aborted while waiting for the request listeners.\n if (requestAbortPromise.state === 'rejected') {\n await options.controller.errorWith(requestAbortPromise.rejectionReason)\n return\n }\n\n if (result.error) {\n // Handle the error during the request listener execution.\n // These can be thrown responses or request errors.\n if (await handleResponseError(result.error)) {\n return\n }\n\n // If the developer has added \"unhandledException\" listeners,\n // allow them to handle the error. They can translate it to a\n // mocked response, network error, or forward it as-is.\n if (options.emitter.listenerCount('unhandledException') > 0) {\n // Create a new request controller just for the unhandled exception case.\n // This is needed because the original controller might have been already\n // interacted with (e.g. \"respondWith\" or \"errorWith\" called on it).\n const unhandledExceptionController = new RequestController(\n options.request,\n {\n /**\n * @note Intentionally empty passthrough handle.\n * This controller is created within another controller and we only need\n * to know if `unhandledException` listeners handled the request.\n */\n passthrough() {},\n async respondWith(response) {\n await handleResponse(response)\n },\n async errorWith(reason) {\n /**\n * @note Handle the result of the unhandled controller\n * in the same way as the original request controller.\n * The exception here is that thrown errors within the\n * \"unhandledException\" event do NOT result in another\n * emit of the same event. They are forwarded as-is.\n */\n await options.controller.errorWith(reason)\n },\n }\n )\n\n await emitAsync(options.emitter, 'unhandledException', {\n error: result.error,\n request: options.request,\n requestId: options.requestId,\n controller: unhandledExceptionController,\n })\n\n // If all the \"unhandledException\" listeners have finished\n // but have not handled the request in any way, passthrough.\n if (\n unhandledExceptionController.readyState !== RequestController.PENDING\n ) {\n return\n }\n }\n\n // Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.\n await options.controller.respondWith(\n createServerErrorResponse(result.error)\n )\n return\n }\n\n // If the request hasn't been handled by this point, passthrough.\n if (options.controller.readyState === RequestController.PENDING) {\n return await options.controller.passthrough()\n }\n\n return options.controller.handled\n}\n"],"mappings":"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"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| let outvariant = require("outvariant"); | ||
| //#region src/utils/globalsRegistry.ts | ||
| var GlobalsRegistry = class { | ||
| #globals = /* @__PURE__ */ new Map(); | ||
| replaceGlobal(key, nextValue) { | ||
| (0, outvariant.invariant)(!this.#globals.has(key), `Failed to replace a global value at "${key}": already replaced.`); | ||
| const match = getDeepPropertyDescriptor(globalThis, key); | ||
| if (typeof match === "undefined") { | ||
| console.warn(`Failed to replace a global value at "${key}": not a global value.`); | ||
| return () => {}; | ||
| } | ||
| Object.defineProperty(globalThis, key, { | ||
| value: nextValue, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| const restoreGlobal = () => { | ||
| if (!this.#globals.has(key)) return; | ||
| if (match.owner === globalThis) Object.defineProperty(match.owner, key, match.descriptor); | ||
| else | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the owner isn't `globalThis`, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(globalThis, key); | ||
| this.#globals.delete(key); | ||
| }; | ||
| this.#globals.set(key, restoreGlobal); | ||
| return restoreGlobal; | ||
| } | ||
| restoreAllGlobals() { | ||
| const errors = []; | ||
| for (const [, restoreGlobal] of this.#globals) try { | ||
| restoreGlobal(); | ||
| } catch (error) { | ||
| if (error instanceof Error) errors.push(error); | ||
| else throw error; | ||
| } | ||
| if (errors.length > 0) throw new AggregateError(errors, "FOO!"); | ||
| } | ||
| }; | ||
| const globalsRegistry = new GlobalsRegistry(); | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| function getDeepPropertyDescriptor(owner, key) { | ||
| let currentOwner = owner; | ||
| let descriptor; | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key); | ||
| if (descriptor) return { | ||
| owner: currentOwner, | ||
| descriptor | ||
| }; | ||
| currentOwner = Object.getPrototypeOf(currentOwner); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/hasConfigurableGlobal.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given global property | ||
| * is defined and is configurable. | ||
| */ | ||
| function hasConfigurableGlobal(propertyName) { | ||
| const match = getDeepPropertyDescriptor(globalThis, propertyName); | ||
| if (typeof match === "undefined") return false; | ||
| const { descriptor } = match; | ||
| if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false; | ||
| if (typeof descriptor.get === "undefined" && descriptor.value == null) return false; | ||
| if (typeof descriptor.set === "undefined" && !descriptor.configurable) { | ||
| console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'globalsRegistry', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return globalsRegistry; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'hasConfigurableGlobal', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return hasConfigurableGlobal; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=hasConfigurableGlobal-Befl_QJD.cjs.map |
| {"version":3,"file":"hasConfigurableGlobal-Befl_QJD.cjs","names":["#globals","errors: Array<Error>","currentOwner: Owner | null","descriptor: PropertyDescriptor | undefined"],"sources":["../../src/utils/globalsRegistry.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { invariant } from 'outvariant'\n\nclass GlobalsRegistry {\n #globals = new Map<keyof typeof globalThis, () => void>()\n\n public replaceGlobal<K extends keyof typeof globalThis>(\n key: K,\n nextValue: (typeof globalThis)[K]\n ): () => void {\n invariant(\n !this.#globals.has(key),\n `Failed to replace a global value at \"${key}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(globalThis, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${key}\": not a global value.`\n )\n return () => {}\n }\n\n Object.defineProperty(globalThis, key, {\n value: nextValue,\n enumerable: true,\n configurable: true,\n })\n\n const restoreGlobal = () => {\n if (!this.#globals.has(key)) {\n return\n }\n\n if (match.owner === globalThis) {\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the owner isn't `globalThis`, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(globalThis, key)\n }\n\n this.#globals.delete(key)\n }\n\n this.#globals.set(key, restoreGlobal)\n\n return restoreGlobal\n }\n\n public restoreAllGlobals(): void {\n const errors: Array<Error> = []\n\n for (const [, restoreGlobal] of this.#globals) {\n try {\n restoreGlobal()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const globalsRegistry = new GlobalsRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: keyof Owner\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n","import { getDeepPropertyDescriptor } from './globalsRegistry'\n\n/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(\n propertyName: keyof typeof globalThis\n): boolean {\n const match = getDeepPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof match === 'undefined') {\n return false\n }\n\n const { descriptor } = match\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;;AAEA,IAAM,kBAAN,MAAsB;CACpB,2BAAW,IAAI,KAA0C;CAEzD,AAAO,cACL,KACA,WACY;AACZ,4BACE,CAAC,MAAKA,QAAS,IAAI,IAAI,EACvB,wCAAwC,IAAI,sBAC7C;EAED,MAAM,QAAQ,0BAA0B,YAAY,IAAI;AAExD,MAAI,OAAO,UAAU,aAAa;AAChC,WAAQ,KACN,wCAAwC,IAAI,wBAC7C;AACD,gBAAa;;AAGf,SAAO,eAAe,YAAY,KAAK;GACrC,OAAO;GACP,YAAY;GACZ,cAAc;GACf,CAAC;EAEF,MAAM,sBAAsB;AAC1B,OAAI,CAAC,MAAKA,QAAS,IAAI,IAAI,CACzB;AAGF,OAAI,MAAM,UAAU,WAClB,QAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;;;;;;;AAOzD,WAAQ,eAAe,YAAY,IAAI;AAGzC,SAAKA,QAAS,OAAO,IAAI;;AAG3B,QAAKA,QAAS,IAAI,KAAK,cAAc;AAErC,SAAO;;CAGT,AAAO,oBAA0B;EAC/B,MAAMC,SAAuB,EAAE;AAE/B,OAAK,MAAM,GAAG,kBAAkB,MAAKD,QACnC,KAAI;AACF,kBAAe;WACR,OAAO;AACd,OAAI,iBAAiB,MACnB,QAAO,KAAK,MAAM;OAElB,OAAM;;AAKZ,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,QAAQ,OAAO;;;AAK9C,MAAa,kBAAkB,IAAI,iBAAiB;;;;;;;AAapD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAIE,eAA6B;CACjC,IAAIC;AAEJ,QAAO,cAAc;AACnB,eAAa,OAAO,yBAAyB,cAAc,IAAI;AAE/D,MAAI,WACF,QAAO;GACL,OAAO;GACP;GACD;AAGH,iBAAe,OAAO,eAAe,aAAa;;;;;;;;;;AClGtD,SAAgB,sBACd,cACS;CACT,MAAM,QAAQ,0BAA0B,YAAY,aAAa;AAGjE,KAAI,OAAO,UAAU,YACnB,QAAO;CAGT,MAAM,EAAE,eAAe;AAGvB,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"} |
| import { invariant } from "outvariant"; | ||
| //#region src/utils/globalsRegistry.ts | ||
| var GlobalsRegistry = class { | ||
| #globals = /* @__PURE__ */ new Map(); | ||
| replaceGlobal(key, nextValue) { | ||
| invariant(!this.#globals.has(key), `Failed to replace a global value at "${key}": already replaced.`); | ||
| const match = getDeepPropertyDescriptor(globalThis, key); | ||
| if (typeof match === "undefined") { | ||
| console.warn(`Failed to replace a global value at "${key}": not a global value.`); | ||
| return () => {}; | ||
| } | ||
| Object.defineProperty(globalThis, key, { | ||
| value: nextValue, | ||
| enumerable: true, | ||
| configurable: true | ||
| }); | ||
| const restoreGlobal = () => { | ||
| if (!this.#globals.has(key)) return; | ||
| if (match.owner === globalThis) Object.defineProperty(match.owner, key, match.descriptor); | ||
| else | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the owner isn't `globalThis`, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(globalThis, key); | ||
| this.#globals.delete(key); | ||
| }; | ||
| this.#globals.set(key, restoreGlobal); | ||
| return restoreGlobal; | ||
| } | ||
| restoreAllGlobals() { | ||
| const errors = []; | ||
| for (const [, restoreGlobal] of this.#globals) try { | ||
| restoreGlobal(); | ||
| } catch (error) { | ||
| if (error instanceof Error) errors.push(error); | ||
| else throw error; | ||
| } | ||
| if (errors.length > 0) throw new AggregateError(errors, "FOO!"); | ||
| } | ||
| }; | ||
| const globalsRegistry = new GlobalsRegistry(); | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| function getDeepPropertyDescriptor(owner, key) { | ||
| let currentOwner = owner; | ||
| let descriptor; | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key); | ||
| if (descriptor) return { | ||
| owner: currentOwner, | ||
| descriptor | ||
| }; | ||
| currentOwner = Object.getPrototypeOf(currentOwner); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/hasConfigurableGlobal.ts | ||
| /** | ||
| * Returns a boolean indicating whether the given global property | ||
| * is defined and is configurable. | ||
| */ | ||
| function hasConfigurableGlobal(propertyName) { | ||
| const match = getDeepPropertyDescriptor(globalThis, propertyName); | ||
| if (typeof match === "undefined") return false; | ||
| const { descriptor } = match; | ||
| if (typeof descriptor.get === "function" && typeof descriptor.get() === "undefined") return false; | ||
| if (typeof descriptor.get === "undefined" && descriptor.value == null) return false; | ||
| if (typeof descriptor.set === "undefined" && !descriptor.configurable) { | ||
| console.error(`[MSW] Failed to apply interceptor: the global \`${propertyName}\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| //#endregion | ||
| export { globalsRegistry as n, hasConfigurableGlobal as t }; | ||
| //# sourceMappingURL=hasConfigurableGlobal-jN_-v3gp.mjs.map |
| {"version":3,"file":"hasConfigurableGlobal-jN_-v3gp.mjs","names":["#globals","errors: Array<Error>","currentOwner: Owner | null","descriptor: PropertyDescriptor | undefined"],"sources":["../../src/utils/globalsRegistry.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { invariant } from 'outvariant'\n\nclass GlobalsRegistry {\n #globals = new Map<keyof typeof globalThis, () => void>()\n\n public replaceGlobal<K extends keyof typeof globalThis>(\n key: K,\n nextValue: (typeof globalThis)[K]\n ): () => void {\n invariant(\n !this.#globals.has(key),\n `Failed to replace a global value at \"${key}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(globalThis, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${key}\": not a global value.`\n )\n return () => {}\n }\n\n Object.defineProperty(globalThis, key, {\n value: nextValue,\n enumerable: true,\n configurable: true,\n })\n\n const restoreGlobal = () => {\n if (!this.#globals.has(key)) {\n return\n }\n\n if (match.owner === globalThis) {\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the owner isn't `globalThis`, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(globalThis, key)\n }\n\n this.#globals.delete(key)\n }\n\n this.#globals.set(key, restoreGlobal)\n\n return restoreGlobal\n }\n\n public restoreAllGlobals(): void {\n const errors: Array<Error> = []\n\n for (const [, restoreGlobal] of this.#globals) {\n try {\n restoreGlobal()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const globalsRegistry = new GlobalsRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: keyof Owner\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n","import { getDeepPropertyDescriptor } from './globalsRegistry'\n\n/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(\n propertyName: keyof typeof globalThis\n): boolean {\n const match = getDeepPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof match === 'undefined') {\n return false\n }\n\n const { descriptor } = match\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;AAEA,IAAM,kBAAN,MAAsB;CACpB,2BAAW,IAAI,KAA0C;CAEzD,AAAO,cACL,KACA,WACY;AACZ,YACE,CAAC,MAAKA,QAAS,IAAI,IAAI,EACvB,wCAAwC,IAAI,sBAC7C;EAED,MAAM,QAAQ,0BAA0B,YAAY,IAAI;AAExD,MAAI,OAAO,UAAU,aAAa;AAChC,WAAQ,KACN,wCAAwC,IAAI,wBAC7C;AACD,gBAAa;;AAGf,SAAO,eAAe,YAAY,KAAK;GACrC,OAAO;GACP,YAAY;GACZ,cAAc;GACf,CAAC;EAEF,MAAM,sBAAsB;AAC1B,OAAI,CAAC,MAAKA,QAAS,IAAI,IAAI,CACzB;AAGF,OAAI,MAAM,UAAU,WAClB,QAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;;;;;;;AAOzD,WAAQ,eAAe,YAAY,IAAI;AAGzC,SAAKA,QAAS,OAAO,IAAI;;AAG3B,QAAKA,QAAS,IAAI,KAAK,cAAc;AAErC,SAAO;;CAGT,AAAO,oBAA0B;EAC/B,MAAMC,SAAuB,EAAE;AAE/B,OAAK,MAAM,GAAG,kBAAkB,MAAKD,QACnC,KAAI;AACF,kBAAe;WACR,OAAO;AACd,OAAI,iBAAiB,MACnB,QAAO,KAAK,MAAM;OAElB,OAAM;;AAKZ,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,QAAQ,OAAO;;;AAK9C,MAAa,kBAAkB,IAAI,iBAAiB;;;;;;;AAapD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAIE,eAA6B;CACjC,IAAIC;AAEJ,QAAO,cAAc;AACnB,eAAa,OAAO,yBAAyB,cAAc,IAAI;AAE/D,MAAI,WACF,QAAO;GACL,OAAO;GACP;GACD;AAGH,iBAAe,OAAO,eAAe,aAAa;;;;;;;;;;AClGtD,SAAgB,sBACd,cACS;CACT,MAAM,QAAQ,0BAA0B,YAAY,aAAa;AAGjE,KAAI,OAAO,UAAU,YACnB,QAAO;CAGT,MAAM,EAAE,eAAe;AAGvB,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"} |
| const require_chunk = require('./chunk-CbDLau6x.cjs'); | ||
| const require_fetchUtils = require('./fetchUtils-BAlG765A.cjs'); | ||
| const require_bufferUtils = require('./bufferUtils-S5_-2eN4.cjs'); | ||
| const require_getRawRequest = require('./getRawRequest-B6znU6il.cjs'); | ||
| const require_handleRequest = require('./handleRequest-DUPo40Df.cjs'); | ||
| const require_hasConfigurableGlobal = require('./hasConfigurableGlobal-Befl_QJD.cjs'); | ||
| let outvariant = require("outvariant"); | ||
| let is_node_process = require("is-node-process"); | ||
| //#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts | ||
| /** | ||
| * Concatenate two `Uint8Array` buffers. | ||
| */ | ||
| function concatArrayBuffer(left, right) { | ||
| const result = new Uint8Array(left.byteLength + right.byteLength); | ||
| result.set(left, 0); | ||
| result.set(right, left.byteLength); | ||
| return result; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts | ||
| var EventPolyfill = class { | ||
| constructor(type, options) { | ||
| this.NONE = 0; | ||
| this.CAPTURING_PHASE = 1; | ||
| this.AT_TARGET = 2; | ||
| this.BUBBLING_PHASE = 3; | ||
| this.type = ""; | ||
| this.srcElement = null; | ||
| this.currentTarget = null; | ||
| this.eventPhase = 0; | ||
| this.isTrusted = true; | ||
| this.composed = false; | ||
| this.cancelable = true; | ||
| this.defaultPrevented = false; | ||
| this.bubbles = true; | ||
| this.lengthComputable = true; | ||
| this.loaded = 0; | ||
| this.total = 0; | ||
| this.cancelBubble = false; | ||
| this.returnValue = true; | ||
| this.type = type; | ||
| this.target = options?.target || null; | ||
| this.currentTarget = options?.currentTarget || null; | ||
| this.timeStamp = Date.now(); | ||
| } | ||
| composedPath() { | ||
| return []; | ||
| } | ||
| initEvent(type, bubbles, cancelable) { | ||
| this.type = type; | ||
| this.bubbles = !!bubbles; | ||
| this.cancelable = !!cancelable; | ||
| } | ||
| preventDefault() { | ||
| this.defaultPrevented = true; | ||
| } | ||
| stopPropagation() {} | ||
| stopImmediatePropagation() {} | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts | ||
| var ProgressEventPolyfill = class extends EventPolyfill { | ||
| constructor(type, init) { | ||
| super(type); | ||
| this.lengthComputable = init?.lengthComputable || false; | ||
| this.composed = init?.composed || false; | ||
| this.loaded = init?.loaded || 0; | ||
| this.total = init?.total || 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createEvent.ts | ||
| const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined"; | ||
| function createEvent(target, type, init) { | ||
| const progressEvents = [ | ||
| "error", | ||
| "progress", | ||
| "loadstart", | ||
| "loadend", | ||
| "load", | ||
| "timeout", | ||
| "abort" | ||
| ]; | ||
| /** | ||
| * `ProgressEvent` is not supported in React Native. | ||
| * @see https://github.com/mswjs/interceptors/issues/40 | ||
| */ | ||
| const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill; | ||
| return progressEvents.includes(type) ? new ProgressEventClass(type, { | ||
| lengthComputable: true, | ||
| loaded: init?.loaded || 0, | ||
| total: init?.total || 0 | ||
| }) : new EventPolyfill(type, { | ||
| target, | ||
| currentTarget: target | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/findPropertySource.ts | ||
| /** | ||
| * Returns the source object of the given property on the target object | ||
| * (the target itself, any parent in its prototype, or null). | ||
| */ | ||
| function findPropertySource(target, propertyName) { | ||
| if (!(propertyName in target)) return null; | ||
| if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target; | ||
| const prototype = Reflect.getPrototypeOf(target); | ||
| return prototype ? findPropertySource(prototype, propertyName) : null; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/createProxy.ts | ||
| function createProxy(target, options) { | ||
| return new Proxy(target, optionsToProxyHandler(options)); | ||
| } | ||
| function optionsToProxyHandler(options) { | ||
| const { constructorCall, methodCall, getProperty, setProperty } = options; | ||
| const handler = {}; | ||
| if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) { | ||
| const next = Reflect.construct.bind(null, target, args, newTarget); | ||
| return constructorCall.call(newTarget, args, next); | ||
| }; | ||
| handler.set = function(target, propertyName, nextValue) { | ||
| const next = () => { | ||
| const propertySource = findPropertySource(target, propertyName) || target; | ||
| const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName); | ||
| if (typeof ownDescriptors?.set !== "undefined") { | ||
| ownDescriptors.set.apply(target, [nextValue]); | ||
| return true; | ||
| } | ||
| return Reflect.defineProperty(propertySource, propertyName, { | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| value: nextValue | ||
| }); | ||
| }; | ||
| if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next); | ||
| return next(); | ||
| }; | ||
| handler.get = function(target, propertyName, receiver) { | ||
| /** | ||
| * @note Using `Reflect.get()` here causes "TypeError: Illegal invocation". | ||
| */ | ||
| const next = () => target[propertyName]; | ||
| const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next(); | ||
| if (typeof value === "function") return (...args) => { | ||
| const next$1 = value.bind(target, ...args); | ||
| if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1); | ||
| return next$1(); | ||
| }; | ||
| return value; | ||
| }; | ||
| return handler; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts | ||
| function isDomParserSupportedType(type) { | ||
| return [ | ||
| "application/xhtml+xml", | ||
| "application/xml", | ||
| "image/svg+xml", | ||
| "text/html", | ||
| "text/xml" | ||
| ].some((supportedType) => { | ||
| return type.startsWith(supportedType); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/parseJson.ts | ||
| /** | ||
| * Parses a given string into JSON. | ||
| * Gracefully handles invalid JSON by returning `null`. | ||
| */ | ||
| function parseJson(data) { | ||
| try { | ||
| return JSON.parse(data); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createResponse.ts | ||
| /** | ||
| * Creates a Fetch API `Response` instance from the given | ||
| * `XMLHttpRequest` instance and a response body. | ||
| */ | ||
| function createResponse(request, body) { | ||
| return new require_fetchUtils.FetchResponse(require_fetchUtils.FetchResponse.isResponseWithBody(request.status) ? body : null, { | ||
| url: request.responseURL, | ||
| status: request.status, | ||
| statusText: request.statusText, | ||
| headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders()) | ||
| }); | ||
| } | ||
| function createHeadersFromXMLHttpRequestHeaders(headersString) { | ||
| const headers = new Headers(); | ||
| const lines = headersString.split(/[\r\n]+/); | ||
| for (const line of lines) { | ||
| if (line.trim() === "") continue; | ||
| const [name, ...parts] = line.split(": "); | ||
| const value = parts.join(": "); | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts | ||
| /** | ||
| * Return a total byte length of the given request/response body. | ||
| * If the `Content-Length` header is present, it will be used as the byte length. | ||
| */ | ||
| async function getBodyByteLength(input) { | ||
| const explicitContentLength = input.headers.get("content-length"); | ||
| if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength); | ||
| return (await input.arrayBuffer()).byteLength; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts | ||
| const kIsRequestHandled = Symbol("kIsRequestHandled"); | ||
| const IS_NODE = (0, is_node_process.isNodeProcess)(); | ||
| const kFetchRequest = Symbol("kFetchRequest"); | ||
| /** | ||
| * An `XMLHttpRequest` instance controller that allows us | ||
| * to handle any given request instance (e.g. responding to it). | ||
| */ | ||
| var XMLHttpRequestController = class { | ||
| constructor(initialRequest, logger) { | ||
| this.initialRequest = initialRequest; | ||
| this.logger = logger; | ||
| this.method = "GET"; | ||
| this.url = null; | ||
| this[kIsRequestHandled] = false; | ||
| this.events = /* @__PURE__ */ new Map(); | ||
| this.uploadEvents = /* @__PURE__ */ new Map(); | ||
| this.requestId = require_fetchUtils.createRequestId(); | ||
| this.requestHeaders = new Headers(); | ||
| this.responseBuffer = new Uint8Array(); | ||
| this.request = createProxy(initialRequest, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "ontimeout": { | ||
| const eventName = propertyName.slice(2); | ||
| /** | ||
| * @note Proxy callbacks to event listeners because JSDOM has trouble | ||
| * translating these properties to callbacks. It seemed to be operating | ||
| * on events exclusively. | ||
| */ | ||
| this.request.addEventListener(eventName, nextValue); | ||
| return invoke(); | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "open": { | ||
| const [method, url] = args; | ||
| if (typeof url === "undefined") { | ||
| this.method = "GET"; | ||
| this.url = toAbsoluteUrl(method); | ||
| } else { | ||
| this.method = method; | ||
| this.url = toAbsoluteUrl(url); | ||
| } | ||
| this.logger = this.logger.extend(`${this.method} ${this.url.href}`); | ||
| this.logger.info("open", this.method, this.url.href); | ||
| return invoke(); | ||
| } | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerEvent(eventName, listener); | ||
| this.logger.info("addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| case "setRequestHeader": { | ||
| const [name, value] = args; | ||
| this.requestHeaders.set(name, value); | ||
| this.logger.info("setRequestHeader", name, value); | ||
| return invoke(); | ||
| } | ||
| case "send": { | ||
| const [body] = args; | ||
| this.request.addEventListener("load", () => { | ||
| if (typeof this.onResponse !== "undefined") { | ||
| const fetchResponse = createResponse( | ||
| this.request, | ||
| /** | ||
| * The `response` property is the right way to read | ||
| * the ambiguous response body, as the request's "responseType" may differ. | ||
| * @see https://xhr.spec.whatwg.org/#the-response-attribute | ||
| */ | ||
| this.request.response | ||
| ); | ||
| this.onResponse.call(this, { | ||
| response: fetchResponse, | ||
| isMockedResponse: this[kIsRequestHandled], | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }); | ||
| } | ||
| }); | ||
| const requestBody = typeof body === "string" ? require_bufferUtils.encodeBuffer(body) : body; | ||
| const fetchRequest = this.toFetchApiRequest(requestBody); | ||
| this[kFetchRequest] = fetchRequest.clone(); | ||
| /** | ||
| * @note Start request handling on the next tick so that the user | ||
| * could add event listeners for "loadend" before the interceptor fires it. | ||
| */ | ||
| queueMicrotask(() => { | ||
| (this.onRequest?.call(this, { | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }) || Promise.resolve()).finally(() => { | ||
| if (!this[kIsRequestHandled]) { | ||
| this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState); | ||
| /** | ||
| * @note Set the intercepted request ID on the original request in Node.js | ||
| * so that if it triggers any other interceptors, they don't attempt | ||
| * to process it once again. | ||
| * | ||
| * For instance, XMLHttpRequest is often implemented via "http.ClientRequest" | ||
| * and we don't want for both XHR and ClientRequest interceptors to | ||
| * handle the same request at the same time (e.g. emit the "response" event twice). | ||
| */ | ||
| if (IS_NODE) this.request.setRequestHeader(require_fetchUtils.INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId); | ||
| return invoke(); | ||
| } | ||
| }); | ||
| }); | ||
| break; | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| } | ||
| }); | ||
| /** | ||
| * Proxy the `.upload` property to gather the event listeners/callbacks. | ||
| */ | ||
| define(this.request, "upload", createProxy(this.request.upload, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "onloadstart": | ||
| case "onprogress": | ||
| case "onaboart": | ||
| case "onerror": | ||
| case "onload": | ||
| case "ontimeout": | ||
| case "onloadend": { | ||
| const eventName = propertyName.slice(2); | ||
| this.registerUploadEvent(eventName, nextValue); | ||
| } | ||
| } | ||
| return invoke(); | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerUploadEvent(eventName, listener); | ||
| this.logger.info("upload.addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| } | ||
| } | ||
| })); | ||
| } | ||
| registerEvent(eventName, listener) { | ||
| const nextEvents = (this.events.get(eventName) || []).concat(listener); | ||
| this.events.set(eventName, nextEvents); | ||
| this.logger.info("registered event \"%s\"", eventName, listener); | ||
| } | ||
| registerUploadEvent(eventName, listener) { | ||
| const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener); | ||
| this.uploadEvents.set(eventName, nextEvents); | ||
| this.logger.info("registered upload event \"%s\"", eventName, listener); | ||
| } | ||
| /** | ||
| * Responds to the current request with the given | ||
| * Fetch API `Response` instance. | ||
| */ | ||
| async respondWith(response) { | ||
| /** | ||
| * @note Since `XMLHttpRequestController` delegates the handling of the responses | ||
| * to the "load" event listener that doesn't distinguish between the mocked and original | ||
| * responses, mark the request that had a mocked response with a corresponding symbol. | ||
| * | ||
| * Mark this request as having a mocked response immediately since | ||
| * calculating request/response total body length is asynchronous. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| /** | ||
| * Dispatch request upload events for requests with a body. | ||
| * @see https://github.com/mswjs/interceptors/issues/573 | ||
| */ | ||
| if (this[kFetchRequest]) { | ||
| const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]); | ||
| this.trigger("loadstart", this.request.upload, { | ||
| loaded: 0, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("progress", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("load", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| } | ||
| this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText); | ||
| define(this.request, "status", response.status); | ||
| define(this.request, "statusText", response.statusText); | ||
| define(this.request, "responseURL", this.url.href); | ||
| this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => { | ||
| this.logger.info("getResponseHeader", args[0]); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning null"); | ||
| return null; | ||
| } | ||
| const headerValue = response.headers.get(args[0]); | ||
| this.logger.info("resolved response header \"%s\" to", args[0], headerValue); | ||
| return headerValue; | ||
| } }); | ||
| this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => { | ||
| this.logger.info("getAllResponseHeaders"); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning empty string"); | ||
| return ""; | ||
| } | ||
| const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => { | ||
| return `${headerName}: ${headerValue}`; | ||
| }).join("\r\n"); | ||
| this.logger.info("resolved all response headers to", allHeaders); | ||
| return allHeaders; | ||
| } }); | ||
| Object.defineProperties(this.request, { | ||
| response: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.response | ||
| }, | ||
| responseText: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseText | ||
| }, | ||
| responseXML: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseXML | ||
| } | ||
| }); | ||
| const totalResponseBodyLength = await getBodyByteLength(response.clone()); | ||
| this.logger.info("calculated response body length", totalResponseBodyLength); | ||
| this.trigger("loadstart", this.request, { | ||
| loaded: 0, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.setReadyState(this.request.HEADERS_RECEIVED); | ||
| this.setReadyState(this.request.LOADING); | ||
| const finalizeResponse = () => { | ||
| this.logger.info("finalizing the mocked response..."); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("load", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| }; | ||
| if (response.body) { | ||
| this.logger.info("mocked response has body, streaming..."); | ||
| const reader = response.body.getReader(); | ||
| const readNextResponseBodyChunk = async () => { | ||
| const { value, done } = await reader.read(); | ||
| if (done) { | ||
| this.logger.info("response body stream done!"); | ||
| finalizeResponse(); | ||
| return; | ||
| } | ||
| if (value) { | ||
| this.logger.info("read response body chunk:", value); | ||
| this.responseBuffer = concatArrayBuffer(this.responseBuffer, value); | ||
| this.trigger("progress", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| } | ||
| readNextResponseBodyChunk(); | ||
| }; | ||
| readNextResponseBodyChunk(); | ||
| } else finalizeResponse(); | ||
| } | ||
| responseBufferToText() { | ||
| return require_bufferUtils.decodeBuffer(this.responseBuffer); | ||
| } | ||
| get response() { | ||
| this.logger.info("getResponse (responseType: %s)", this.request.responseType); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| switch (this.request.responseType) { | ||
| case "json": { | ||
| const responseJson = parseJson(this.responseBufferToText()); | ||
| this.logger.info("resolved response JSON", responseJson); | ||
| return responseJson; | ||
| } | ||
| case "arraybuffer": { | ||
| const arrayBuffer = require_bufferUtils.toArrayBuffer(this.responseBuffer); | ||
| this.logger.info("resolved response ArrayBuffer", arrayBuffer); | ||
| return arrayBuffer; | ||
| } | ||
| case "blob": { | ||
| const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain"; | ||
| const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType }); | ||
| this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType); | ||
| return responseBlob; | ||
| } | ||
| default: { | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText); | ||
| return responseText; | ||
| } | ||
| } | ||
| } | ||
| get responseText() { | ||
| /** | ||
| * Throw when trying to read the response body as text when the | ||
| * "responseType" doesn't expect text. This just respects the spec better. | ||
| * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute | ||
| */ | ||
| (0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return ""; | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("getResponseText: \"%s\"", responseText); | ||
| return responseText; | ||
| } | ||
| get responseXML() { | ||
| (0, outvariant.invariant)(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| const contentType = this.request.getResponseHeader("Content-Type") || ""; | ||
| if (typeof DOMParser === "undefined") { | ||
| console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly."); | ||
| return null; | ||
| } | ||
| if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType); | ||
| return null; | ||
| } | ||
| errorWith(error) { | ||
| /** | ||
| * @note Mark this request as handled even if it received a mock error. | ||
| * This prevents the controller from trying to perform this request as-is. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| this.logger.info("responding with an error"); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("error", this.request); | ||
| this.trigger("loadend", this.request); | ||
| } | ||
| /** | ||
| * Transitions this request's `readyState` to the given one. | ||
| */ | ||
| setReadyState(nextReadyState) { | ||
| this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState); | ||
| if (this.request.readyState === nextReadyState) { | ||
| this.logger.info("ready state identical, skipping transition..."); | ||
| return; | ||
| } | ||
| define(this.request, "readyState", nextReadyState); | ||
| this.logger.info("set readyState to: %d", nextReadyState); | ||
| if (nextReadyState !== this.request.UNSENT) { | ||
| this.logger.info("triggering \"readystatechange\" event..."); | ||
| this.trigger("readystatechange", this.request); | ||
| } | ||
| } | ||
| /** | ||
| * Triggers given event on the `XMLHttpRequest` instance. | ||
| */ | ||
| trigger(eventName, target, options) { | ||
| const callback = target[`on${eventName}`]; | ||
| const event = createEvent(target, eventName, options); | ||
| this.logger.info("trigger \"%s\"", eventName, options || ""); | ||
| if (typeof callback === "function") { | ||
| this.logger.info("found a direct \"%s\" callback, calling...", eventName); | ||
| callback.call(target, event); | ||
| } | ||
| const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events; | ||
| for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) { | ||
| this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName); | ||
| listeners.forEach((listener) => listener.call(target, event)); | ||
| } | ||
| } | ||
| /** | ||
| * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance. | ||
| */ | ||
| toFetchApiRequest(body) { | ||
| this.logger.info("converting request to a Fetch API Request..."); | ||
| const resolvedBody = body instanceof Document ? body.documentElement.innerText : body; | ||
| const fetchRequest = new require_fetchUtils.FetchRequest(this.url.href, { | ||
| method: this.method, | ||
| headers: this.requestHeaders, | ||
| credentials: this.request.withCredentials ? "include" : "same-origin", | ||
| body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody | ||
| }); | ||
| define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "append": | ||
| case "set": { | ||
| const [headerName, headerValue] = args; | ||
| this.request.setRequestHeader(headerName, headerValue); | ||
| break; | ||
| } | ||
| case "delete": { | ||
| const [headerName] = args; | ||
| console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`); | ||
| break; | ||
| } | ||
| } | ||
| return invoke(); | ||
| } })); | ||
| require_getRawRequest.setRawRequest(fetchRequest, this.request); | ||
| this.logger.info("converted request to a Fetch API Request!", fetchRequest); | ||
| return fetchRequest; | ||
| } | ||
| }; | ||
| function toAbsoluteUrl(url) { | ||
| /** | ||
| * @note XMLHttpRequest interceptor may run in environments | ||
| * that implement XMLHttpRequest but don't implement "location" | ||
| * (for example, React Native). If that's the case, return the | ||
| * input URL as-is (nothing to be relative to). | ||
| * @see https://github.com/mswjs/msw/issues/1777 | ||
| */ | ||
| if (typeof location === "undefined") return new URL(url); | ||
| return new URL(url.toString(), location.href); | ||
| } | ||
| function define(target, property, value) { | ||
| Reflect.defineProperty(target, property, { | ||
| writable: true, | ||
| enumerable: true, | ||
| value | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts | ||
| /** | ||
| * Create a proxied `XMLHttpRequest` class. | ||
| * The proxied class establishes spies on certain methods, | ||
| * allowing us to intercept requests and respond to them. | ||
| */ | ||
| function createXMLHttpRequestProxy({ emitter, logger }) { | ||
| return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) { | ||
| logger.info("constructed new XMLHttpRequest"); | ||
| const originalRequest = Reflect.construct(target, args, newTarget); | ||
| /** | ||
| * @note Forward prototype descriptors onto the proxied object. | ||
| * XMLHttpRequest is implemented in JSDOM in a way that assigns | ||
| * a bunch of descriptors, like "set responseType()" on the prototype. | ||
| * With this propagation, we make sure that those descriptors trigger | ||
| * when the user operates with the proxied request instance. | ||
| */ | ||
| const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype); | ||
| for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]); | ||
| const xhrRequestController = new XMLHttpRequestController(originalRequest, logger); | ||
| xhrRequestController.onRequest = async function({ request, requestId }) { | ||
| const controller = new require_fetchUtils.RequestController(request, { | ||
| passthrough: () => { | ||
| this.logger.info("no mocked response received, performing request as-is..."); | ||
| }, | ||
| respondWith: async (response) => { | ||
| if (require_handleRequest.isResponseError(response)) { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| await this.respondWith(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request errored!", { error: reason }); | ||
| if (reason instanceof Error) this.errorWith(reason); | ||
| } | ||
| }); | ||
| this.logger.info("awaiting mocked response..."); | ||
| this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request")); | ||
| await require_handleRequest.handleRequest({ | ||
| request, | ||
| requestId, | ||
| controller, | ||
| emitter | ||
| }); | ||
| }; | ||
| xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) { | ||
| this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response")); | ||
| emitter.emit("response", { | ||
| response, | ||
| isMockedResponse, | ||
| request, | ||
| requestId | ||
| }); | ||
| }; | ||
| return xhrRequestController.request; | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/index.ts | ||
| var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends require_fetchUtils.Interceptor { | ||
| static { | ||
| this.interceptorSymbol = Symbol("xhr"); | ||
| } | ||
| constructor() { | ||
| super(XMLHttpRequestInterceptor.interceptorSymbol); | ||
| } | ||
| checkEnvironment() { | ||
| return require_hasConfigurableGlobal.hasConfigurableGlobal("XMLHttpRequest"); | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("patching global XMLHttpRequest..."); | ||
| this.subscriptions.push(require_hasConfigurableGlobal.globalsRegistry.replaceGlobal("XMLHttpRequest", createXMLHttpRequestProxy({ | ||
| emitter: this.emitter, | ||
| logger: this.logger | ||
| }))); | ||
| logger.info("global XMLHttpRequest patched!", globalThis.XMLHttpRequest.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| Object.defineProperty(exports, 'XMLHttpRequestInterceptor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return XMLHttpRequestInterceptor; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=XMLHttpRequest-C1KTngEF.cjs.map |
| {"version":3,"file":"XMLHttpRequest-C1KTngEF.cjs","names":["handler: ProxyHandler<T>","next","FetchResponse","initialRequest: XMLHttpRequest","logger: Logger","createRequestId","encodeBuffer","INTERNAL_REQUEST_ID_HEADER_NAME","decodeBuffer","toArrayBuffer","FetchRequest","RequestController","isResponseError","handleRequest","Interceptor","hasConfigurableGlobal","globalsRegistry"],"sources":["../../src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts","../../src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts","../../src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts","../../src/interceptors/XMLHttpRequest/utils/createEvent.ts","../../src/utils/findPropertySource.ts","../../src/utils/createProxy.ts","../../src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts","../../src/utils/parseJson.ts","../../src/interceptors/XMLHttpRequest/utils/createResponse.ts","../../src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts","../../src/interceptors/XMLHttpRequest/index.ts"],"sourcesContent":["/**\n * Concatenate two `Uint8Array` buffers.\n */\nexport function concatArrayBuffer(\n left: Uint8Array,\n right: Uint8Array\n): Uint8Array {\n const result = new Uint8Array(left.byteLength + right.byteLength)\n result.set(left, 0)\n result.set(right, left.byteLength)\n return result\n}\n","export class EventPolyfill implements Event {\n readonly NONE = 0\n readonly CAPTURING_PHASE = 1\n readonly AT_TARGET = 2\n readonly BUBBLING_PHASE = 3\n\n public type: string = ''\n public srcElement: EventTarget | null = null\n public target: EventTarget | null\n public currentTarget: EventTarget | null = null\n public eventPhase: number = 0\n public timeStamp: number\n public isTrusted: boolean = true\n public composed: boolean = false\n public cancelable: boolean = true\n public defaultPrevented: boolean = false\n public bubbles: boolean = true\n public lengthComputable: boolean = true\n public loaded: number = 0\n public total: number = 0\n\n cancelBubble: boolean = false\n returnValue: boolean = true\n\n constructor(\n type: string,\n options?: { target: EventTarget; currentTarget: EventTarget }\n ) {\n this.type = type\n this.target = options?.target || null\n this.currentTarget = options?.currentTarget || null\n this.timeStamp = Date.now()\n }\n\n public composedPath(): EventTarget[] {\n return []\n }\n\n public initEvent(type: string, bubbles?: boolean, cancelable?: boolean) {\n this.type = type\n this.bubbles = !!bubbles\n this.cancelable = !!cancelable\n }\n\n public preventDefault() {\n this.defaultPrevented = true\n }\n\n public stopPropagation() {}\n public stopImmediatePropagation() {}\n}\n","import { EventPolyfill } from './EventPolyfill'\n\nexport class ProgressEventPolyfill extends EventPolyfill {\n readonly lengthComputable: boolean\n readonly composed: boolean\n readonly loaded: number\n readonly total: number\n\n constructor(type: string, init?: ProgressEventInit) {\n super(type)\n\n this.lengthComputable = init?.lengthComputable || false\n this.composed = init?.composed || false\n this.loaded = init?.loaded || 0\n this.total = init?.total || 0\n }\n}\n","import { EventPolyfill } from '../polyfills/EventPolyfill'\nimport { ProgressEventPolyfill } from '../polyfills/ProgressEventPolyfill'\n\nconst SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined'\n\nexport function createEvent(\n target: XMLHttpRequest | XMLHttpRequestUpload,\n type: string,\n init?: ProgressEventInit\n): EventPolyfill | ProgressEvent {\n const progressEvents = [\n 'error',\n 'progress',\n 'loadstart',\n 'loadend',\n 'load',\n 'timeout',\n 'abort',\n ]\n\n /**\n * `ProgressEvent` is not supported in React Native.\n * @see https://github.com/mswjs/interceptors/issues/40\n */\n const ProgressEventClass = SUPPORTS_PROGRESS_EVENT\n ? ProgressEvent\n : ProgressEventPolyfill\n\n const event = progressEvents.includes(type)\n ? new ProgressEventClass(type, {\n lengthComputable: true,\n loaded: init?.loaded || 0,\n total: init?.total || 0,\n })\n : new EventPolyfill(type, {\n target,\n currentTarget: target,\n })\n\n return event\n}\n","/**\n * Returns the source object of the given property on the target object\n * (the target itself, any parent in its prototype, or null).\n */\nexport function findPropertySource(\n target: object,\n propertyName: string | symbol\n): object | null {\n if (!(propertyName in target)) {\n return null\n }\n\n const hasProperty = Object.prototype.hasOwnProperty.call(target, propertyName)\n if (hasProperty) {\n return target\n }\n\n const prototype = Reflect.getPrototypeOf(target)\n return prototype ? findPropertySource(prototype, propertyName) : null\n}\n","import { findPropertySource } from './findPropertySource'\n\nexport interface ProxyOptions<Target extends Record<string, any>> {\n constructorCall?(args: Array<unknown>, next: NextFunction<Target>): Target\n\n methodCall?<F extends keyof Target>(\n this: Target,\n data: [methodName: F, args: Array<unknown>],\n next: NextFunction<void>\n ): void\n\n setProperty?(\n data: [propertyName: string | symbol, nextValue: unknown],\n next: NextFunction<boolean>\n ): boolean\n\n getProperty?(\n data: [propertyName: string | symbol, receiver: Target],\n next: NextFunction<void>\n ): void\n}\n\nexport type NextFunction<ReturnType> = () => ReturnType\n\nexport function createProxy<Target extends object>(\n target: Target,\n options: ProxyOptions<Target>\n): Target {\n const proxy = new Proxy(target, optionsToProxyHandler(options))\n\n return proxy\n}\n\nfunction optionsToProxyHandler<T extends Record<string, any>>(\n options: ProxyOptions<T>\n): ProxyHandler<T> {\n const { constructorCall, methodCall, getProperty, setProperty } = options\n const handler: ProxyHandler<T> = {}\n\n if (typeof constructorCall !== 'undefined') {\n handler.construct = function (target, args, newTarget) {\n const next = Reflect.construct.bind(null, target as any, args, newTarget)\n return constructorCall.call(newTarget, args, next)\n }\n }\n\n handler.set = function (target, propertyName, nextValue) {\n const next = () => {\n const propertySource = findPropertySource(target, propertyName) || target\n const ownDescriptors = Reflect.getOwnPropertyDescriptor(\n propertySource,\n propertyName\n )\n\n // Respect any custom setters present for this property.\n if (typeof ownDescriptors?.set !== 'undefined') {\n ownDescriptors.set.apply(target, [nextValue])\n return true\n }\n\n // Otherwise, set the property on the source.\n return Reflect.defineProperty(propertySource, propertyName, {\n writable: true,\n enumerable: true,\n configurable: true,\n value: nextValue,\n })\n }\n\n if (typeof setProperty !== 'undefined') {\n return setProperty.call(target, [propertyName, nextValue], next)\n }\n\n return next()\n }\n\n handler.get = function (target, propertyName, receiver) {\n /**\n * @note Using `Reflect.get()` here causes \"TypeError: Illegal invocation\".\n */\n const next = () => target[propertyName as any]\n\n const value =\n typeof getProperty !== 'undefined'\n ? getProperty.call(target, [propertyName, receiver], next)\n : next()\n\n if (typeof value === 'function') {\n return (...args: Array<any>) => {\n const next = value.bind(target, ...args)\n\n if (typeof methodCall !== 'undefined') {\n return methodCall.call(target, [propertyName as any, args], next)\n }\n\n return next()\n }\n }\n\n return value\n }\n\n return handler\n}\n","export function isDomParserSupportedType(\n type: string\n): type is DOMParserSupportedType {\n const supportedTypes: Array<DOMParserSupportedType> = [\n 'application/xhtml+xml',\n 'application/xml',\n 'image/svg+xml',\n 'text/html',\n 'text/xml',\n ]\n return supportedTypes.some((supportedType) => {\n return type.startsWith(supportedType)\n })\n}\n","/**\n * Parses a given string into JSON.\n * Gracefully handles invalid JSON by returning `null`.\n */\nexport function parseJson(data: string): Record<string, unknown> | null {\n try {\n const json = JSON.parse(data)\n return json\n } catch (_) {\n return null\n }\n}\n","import { FetchResponse } from '../../../utils/fetchUtils'\n\n/**\n * Creates a Fetch API `Response` instance from the given\n * `XMLHttpRequest` instance and a response body.\n */\nexport function createResponse(\n request: XMLHttpRequest,\n body: BodyInit | null\n): Response {\n /**\n * Handle XMLHttpRequest responses that must have null as the\n * response body when represented using Fetch API Response.\n * XMLHttpRequest response will always have an empty string\n * as the \"request.response\" in those cases, resulting in an error\n * when constructing a Response instance.\n * @see https://github.com/mswjs/interceptors/issues/379\n */\n const responseBodyOrNull = FetchResponse.isResponseWithBody(request.status)\n ? body\n : null\n\n return new FetchResponse(responseBodyOrNull, {\n url: request.responseURL,\n status: request.status,\n statusText: request.statusText,\n headers: createHeadersFromXMLHttpRequestHeaders(\n request.getAllResponseHeaders()\n ),\n })\n}\n\nfunction createHeadersFromXMLHttpRequestHeaders(headersString: string): Headers {\n const headers = new Headers()\n\n const lines = headersString.split(/[\\r\\n]+/)\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n const [name, ...parts] = line.split(': ')\n const value = parts.join(': ')\n\n headers.append(name, value)\n }\n\n return headers\n}\n","/**\n * Return a total byte length of the given request/response body.\n * If the `Content-Length` header is present, it will be used as the byte length.\n */\nexport async function getBodyByteLength(\n input: Request | Response\n): Promise<number> {\n const explicitContentLength = input.headers.get('content-length')\n\n if (explicitContentLength != null && explicitContentLength !== '') {\n return Number(explicitContentLength)\n }\n\n const buffer = await input.arrayBuffer()\n return buffer.byteLength\n}\n","import { invariant } from 'outvariant'\nimport { isNodeProcess } from 'is-node-process'\nimport type { Logger } from '@open-draft/logger'\nimport { concatArrayBuffer } from './utils/concatArrayBuffer'\nimport { createEvent } from './utils/createEvent'\nimport {\n decodeBuffer,\n encodeBuffer,\n toArrayBuffer,\n} from '../../utils/bufferUtils'\nimport { createProxy } from '../../utils/createProxy'\nimport { isDomParserSupportedType } from './utils/isDomParserSupportedType'\nimport { parseJson } from '../../utils/parseJson'\nimport { createResponse } from './utils/createResponse'\nimport { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'\nimport { createRequestId } from '../../createRequestId'\nimport { getBodyByteLength } from './utils/getBodyByteLength'\nimport { setRawRequest } from '../../getRawRequest'\nimport { FetchRequest } from '../../utils/fetchUtils'\n\nconst kIsRequestHandled = Symbol('kIsRequestHandled')\nconst IS_NODE = isNodeProcess()\nconst kFetchRequest = Symbol('kFetchRequest')\n\n/**\n * An `XMLHttpRequest` instance controller that allows us\n * to handle any given request instance (e.g. responding to it).\n */\nexport class XMLHttpRequestController {\n public request: XMLHttpRequest\n public requestId: string\n public onRequest?: (\n this: XMLHttpRequestController,\n args: {\n request: Request\n requestId: string\n }\n ) => Promise<void>\n public onResponse?: (\n this: XMLHttpRequestController,\n args: {\n response: Response\n isMockedResponse: boolean\n request: Request\n requestId: string\n }\n ) => void;\n\n [kIsRequestHandled]: boolean;\n [kFetchRequest]?: Request\n private method: string = 'GET'\n private url: URL = null as any\n private requestHeaders: Headers\n private responseBuffer: Uint8Array\n private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>\n private uploadEvents: Map<\n keyof XMLHttpRequestEventTargetEventMap,\n Array<Function>\n >\n\n constructor(\n readonly initialRequest: XMLHttpRequest,\n public logger: Logger\n ) {\n this[kIsRequestHandled] = false\n\n this.events = new Map()\n this.uploadEvents = new Map()\n this.requestId = createRequestId()\n this.requestHeaders = new Headers()\n this.responseBuffer = new Uint8Array()\n\n this.request = createProxy(initialRequest, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'ontimeout': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n /**\n * @note Proxy callbacks to event listeners because JSDOM has trouble\n * translating these properties to callbacks. It seemed to be operating\n * on events exclusively.\n */\n this.request.addEventListener(eventName, nextValue as any)\n\n return invoke()\n }\n\n default: {\n return invoke()\n }\n }\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'open': {\n const [method, url] = args as [string, string | undefined]\n\n if (typeof url === 'undefined') {\n this.method = 'GET'\n this.url = toAbsoluteUrl(method)\n } else {\n this.method = method\n this.url = toAbsoluteUrl(url)\n }\n\n this.logger = this.logger.extend(`${this.method} ${this.url.href}`)\n this.logger.info('open', this.method, this.url.href)\n\n return invoke()\n }\n\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n\n this.registerEvent(eventName, listener)\n this.logger.info('addEventListener', eventName, listener)\n\n return invoke()\n }\n\n case 'setRequestHeader': {\n const [name, value] = args as [string, string]\n this.requestHeaders.set(name, value)\n\n this.logger.info('setRequestHeader', name, value)\n\n return invoke()\n }\n\n case 'send': {\n const [body] = args as [\n body?: XMLHttpRequestBodyInit | Document | null,\n ]\n\n this.request.addEventListener('load', () => {\n if (typeof this.onResponse !== 'undefined') {\n // Create a Fetch API Response representation of whichever\n // response this XMLHttpRequest received. Note those may\n // be either a mocked and the original response.\n const fetchResponse = createResponse(\n this.request,\n /**\n * The `response` property is the right way to read\n * the ambiguous response body, as the request's \"responseType\" may differ.\n * @see https://xhr.spec.whatwg.org/#the-response-attribute\n */\n this.request.response\n )\n\n // Notify the consumer about the response.\n this.onResponse.call(this, {\n response: fetchResponse,\n isMockedResponse: this[kIsRequestHandled],\n request: fetchRequest,\n requestId: this.requestId!,\n })\n }\n })\n\n const requestBody =\n typeof body === 'string' ? encodeBuffer(body) : body\n\n // Delegate request handling to the consumer.\n const fetchRequest = this.toFetchApiRequest(requestBody)\n this[kFetchRequest] = fetchRequest.clone()\n\n /**\n * @note Start request handling on the next tick so that the user\n * could add event listeners for \"loadend\" before the interceptor fires it.\n */\n queueMicrotask(() => {\n const onceRequestSettled =\n this.onRequest?.call(this, {\n request: fetchRequest,\n requestId: this.requestId!,\n }) || Promise.resolve()\n\n onceRequestSettled.finally(() => {\n // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.\n if (!this[kIsRequestHandled]) {\n this.logger.info(\n 'request callback settled but request has not been handled (readystate %d), performing as-is...',\n this.request.readyState\n )\n\n /**\n * @note Set the intercepted request ID on the original request in Node.js\n * so that if it triggers any other interceptors, they don't attempt\n * to process it once again.\n *\n * For instance, XMLHttpRequest is often implemented via \"http.ClientRequest\"\n * and we don't want for both XHR and ClientRequest interceptors to\n * handle the same request at the same time (e.g. emit the \"response\" event twice).\n */\n if (IS_NODE) {\n this.request.setRequestHeader(\n INTERNAL_REQUEST_ID_HEADER_NAME,\n this.requestId!\n )\n }\n\n return invoke()\n }\n })\n })\n\n break\n }\n\n default: {\n return invoke()\n }\n }\n },\n })\n\n /**\n * Proxy the `.upload` property to gather the event listeners/callbacks.\n */\n define(\n this.request,\n 'upload',\n createProxy(this.request.upload, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'onloadstart':\n case 'onprogress':\n case 'onaboart':\n case 'onerror':\n case 'onload':\n case 'ontimeout':\n case 'onloadend': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n this.registerUploadEvent(eventName, nextValue as Function)\n }\n }\n\n return invoke()\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n this.registerUploadEvent(eventName, listener)\n this.logger.info('upload.addEventListener', eventName, listener)\n\n return invoke()\n }\n }\n },\n })\n )\n }\n\n private registerEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.events.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.events.set(eventName, nextEvents)\n\n this.logger.info('registered event \"%s\"', eventName, listener)\n }\n\n private registerUploadEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.uploadEvents.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.uploadEvents.set(eventName, nextEvents)\n\n this.logger.info('registered upload event \"%s\"', eventName, listener)\n }\n\n /**\n * Responds to the current request with the given\n * Fetch API `Response` instance.\n */\n public async respondWith(response: Response): Promise<void> {\n /**\n * @note Since `XMLHttpRequestController` delegates the handling of the responses\n * to the \"load\" event listener that doesn't distinguish between the mocked and original\n * responses, mark the request that had a mocked response with a corresponding symbol.\n *\n * Mark this request as having a mocked response immediately since\n * calculating request/response total body length is asynchronous.\n */\n this[kIsRequestHandled] = true\n\n /**\n * Dispatch request upload events for requests with a body.\n * @see https://github.com/mswjs/interceptors/issues/573\n */\n if (this[kFetchRequest]) {\n const totalRequestBodyLength = await getBodyByteLength(\n this[kFetchRequest]\n )\n\n this.trigger('loadstart', this.request.upload, {\n loaded: 0,\n total: totalRequestBodyLength,\n })\n this.trigger('progress', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n this.trigger('load', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n\n this.trigger('loadend', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n }\n\n this.logger.info(\n 'responding with a mocked response: %d %s',\n response.status,\n response.statusText\n )\n\n define(this.request, 'status', response.status)\n define(this.request, 'statusText', response.statusText)\n define(this.request, 'responseURL', this.url.href)\n\n this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {\n apply: (_, __, args: [name: string]) => {\n this.logger.info('getResponseHeader', args[0])\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning null')\n\n // Headers not received yet, nothing to return.\n return null\n }\n\n const headerValue = response.headers.get(args[0])\n this.logger.info(\n 'resolved response header \"%s\" to',\n args[0],\n headerValue\n )\n\n return headerValue\n },\n })\n\n this.request.getAllResponseHeaders = new Proxy(\n this.request.getAllResponseHeaders,\n {\n apply: () => {\n this.logger.info('getAllResponseHeaders')\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning empty string')\n\n // Headers not received yet, nothing to return.\n return ''\n }\n\n const headersList = Array.from(response.headers.entries())\n const allHeaders = headersList\n .map(([headerName, headerValue]) => {\n return `${headerName}: ${headerValue}`\n })\n .join('\\r\\n')\n\n this.logger.info('resolved all response headers to', allHeaders)\n\n return allHeaders\n },\n }\n )\n\n // Update the response getters to resolve against the mocked response.\n Object.defineProperties(this.request, {\n response: {\n enumerable: true,\n configurable: false,\n get: () => this.response,\n },\n responseText: {\n enumerable: true,\n configurable: false,\n get: () => this.responseText,\n },\n responseXML: {\n enumerable: true,\n configurable: false,\n get: () => this.responseXML,\n },\n })\n\n const totalResponseBodyLength = await getBodyByteLength(response.clone())\n\n this.logger.info('calculated response body length', totalResponseBodyLength)\n\n this.trigger('loadstart', this.request, {\n loaded: 0,\n total: totalResponseBodyLength,\n })\n\n this.setReadyState(this.request.HEADERS_RECEIVED)\n this.setReadyState(this.request.LOADING)\n\n const finalizeResponse = () => {\n this.logger.info('finalizing the mocked response...')\n\n this.setReadyState(this.request.DONE)\n\n this.trigger('load', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n\n this.trigger('loadend', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n if (response.body) {\n this.logger.info('mocked response has body, streaming...')\n\n const reader = response.body.getReader()\n\n const readNextResponseBodyChunk = async () => {\n const { value, done } = await reader.read()\n\n if (done) {\n this.logger.info('response body stream done!')\n finalizeResponse()\n return\n }\n\n if (value) {\n this.logger.info('read response body chunk:', value)\n this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)\n\n this.trigger('progress', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n readNextResponseBodyChunk()\n }\n\n readNextResponseBodyChunk()\n } else {\n finalizeResponse()\n }\n }\n\n private responseBufferToText(): string {\n return decodeBuffer(this.responseBuffer)\n }\n\n get response(): unknown {\n this.logger.info(\n 'getResponse (responseType: %s)',\n this.request.responseType\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n switch (this.request.responseType) {\n case 'json': {\n const responseJson = parseJson(this.responseBufferToText())\n this.logger.info('resolved response JSON', responseJson)\n\n return responseJson\n }\n\n case 'arraybuffer': {\n const arrayBuffer = toArrayBuffer(this.responseBuffer)\n this.logger.info('resolved response ArrayBuffer', arrayBuffer)\n\n return arrayBuffer\n }\n\n case 'blob': {\n const mimeType =\n this.request.getResponseHeader('Content-Type') || 'text/plain'\n const responseBlob = new Blob([this.responseBufferToText()], {\n type: mimeType,\n })\n\n this.logger.info(\n 'resolved response Blob (mime type: %s)',\n responseBlob,\n mimeType\n )\n\n return responseBlob\n }\n\n default: {\n const responseText = this.responseBufferToText()\n this.logger.info(\n 'resolving \"%s\" response type as text',\n this.request.responseType,\n responseText\n )\n\n return responseText\n }\n }\n }\n\n get responseText(): string {\n /**\n * Throw when trying to read the response body as text when the\n * \"responseType\" doesn't expect text. This just respects the spec better.\n * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute\n */\n invariant(\n this.request.responseType === '' || this.request.responseType === 'text',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (\n this.request.readyState !== this.request.LOADING &&\n this.request.readyState !== this.request.DONE\n ) {\n return ''\n }\n\n const responseText = this.responseBufferToText()\n this.logger.info('getResponseText: \"%s\"', responseText)\n\n return responseText\n }\n\n get responseXML(): Document | null {\n invariant(\n this.request.responseType === '' ||\n this.request.responseType === 'document',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n const contentType = this.request.getResponseHeader('Content-Type') || ''\n\n if (typeof DOMParser === 'undefined') {\n console.warn(\n 'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'\n )\n return null\n }\n\n if (isDomParserSupportedType(contentType)) {\n return new DOMParser().parseFromString(\n this.responseBufferToText(),\n contentType\n )\n }\n\n return null\n }\n\n public errorWith(error?: Error): void {\n /**\n * @note Mark this request as handled even if it received a mock error.\n * This prevents the controller from trying to perform this request as-is.\n */\n this[kIsRequestHandled] = true\n this.logger.info('responding with an error')\n\n this.setReadyState(this.request.DONE)\n this.trigger('error', this.request)\n this.trigger('loadend', this.request)\n }\n\n /**\n * Transitions this request's `readyState` to the given one.\n */\n private setReadyState(nextReadyState: number): void {\n this.logger.info(\n 'setReadyState: %d -> %d',\n this.request.readyState,\n nextReadyState\n )\n\n if (this.request.readyState === nextReadyState) {\n this.logger.info('ready state identical, skipping transition...')\n return\n }\n\n define(this.request, 'readyState', nextReadyState)\n\n this.logger.info('set readyState to: %d', nextReadyState)\n\n if (nextReadyState !== this.request.UNSENT) {\n this.logger.info('triggering \"readystatechange\" event...')\n\n this.trigger('readystatechange', this.request)\n }\n }\n\n /**\n * Triggers given event on the `XMLHttpRequest` instance.\n */\n private trigger<\n EventName extends keyof (XMLHttpRequestEventTargetEventMap & {\n readystatechange: ProgressEvent<XMLHttpRequestEventTarget>\n }),\n >(\n eventName: EventName,\n target: XMLHttpRequest | XMLHttpRequestUpload,\n options?: ProgressEventInit\n ): void {\n const callback = (target as XMLHttpRequest)[`on${eventName}`]\n const event = createEvent(target, eventName, options)\n\n this.logger.info('trigger \"%s\"', eventName, options || '')\n\n // Invoke direct callbacks.\n if (typeof callback === 'function') {\n this.logger.info('found a direct \"%s\" callback, calling...', eventName)\n callback.call(target as XMLHttpRequest, event)\n }\n\n // Invoke event listeners.\n const events =\n target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events\n\n for (const [registeredEventName, listeners] of events) {\n if (registeredEventName === eventName) {\n this.logger.info(\n 'found %d listener(s) for \"%s\" event, calling...',\n listeners.length,\n eventName\n )\n\n listeners.forEach((listener) => listener.call(target, event))\n }\n }\n }\n\n /**\n * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.\n */\n private toFetchApiRequest(\n body: XMLHttpRequestBodyInit | Document | null | undefined\n ): Request {\n this.logger.info('converting request to a Fetch API Request...')\n\n // If the `Document` is used as the body of this XMLHttpRequest,\n // set its inner text as the Fetch API Request body.\n const resolvedBody =\n body instanceof Document ? body.documentElement.innerText : body\n\n const fetchRequest = new FetchRequest(this.url.href, {\n method: this.method,\n headers: this.requestHeaders,\n /**\n * @see https://xhr.spec.whatwg.org/#cross-origin-credentials\n */\n credentials: this.request.withCredentials ? 'include' : 'same-origin',\n body: ['GET', 'HEAD'].includes(this.method.toUpperCase())\n ? null\n : resolvedBody,\n })\n\n const proxyHeaders = createProxy(fetchRequest.headers, {\n methodCall: ([methodName, args], invoke) => {\n // Forward the latest state of the internal request headers\n // because the interceptor might have modified them\n // without responding to the request.\n switch (methodName) {\n case 'append':\n case 'set': {\n const [headerName, headerValue] = args as [string, string]\n this.request.setRequestHeader(headerName, headerValue)\n break\n }\n\n case 'delete': {\n const [headerName] = args as [string]\n console.warn(\n `XMLHttpRequest: Cannot remove a \"${headerName}\" header from the Fetch API representation of the \"${fetchRequest.method} ${fetchRequest.url}\" request. XMLHttpRequest headers cannot be removed.`\n )\n break\n }\n }\n\n return invoke()\n },\n })\n define(fetchRequest, 'headers', proxyHeaders)\n setRawRequest(fetchRequest, this.request)\n\n this.logger.info('converted request to a Fetch API Request!', fetchRequest)\n\n return fetchRequest\n }\n}\n\nfunction toAbsoluteUrl(url: string | URL): URL {\n /**\n * @note XMLHttpRequest interceptor may run in environments\n * that implement XMLHttpRequest but don't implement \"location\"\n * (for example, React Native). If that's the case, return the\n * input URL as-is (nothing to be relative to).\n * @see https://github.com/mswjs/msw/issues/1777\n */\n if (typeof location === 'undefined') {\n return new URL(url)\n }\n\n return new URL(url.toString(), location.href)\n}\n\nfunction define(\n target: object,\n property: string | symbol,\n value: unknown\n): void {\n Reflect.defineProperty(target, property, {\n // Ensure writable properties to allow redefining readonly properties.\n writable: true,\n enumerable: true,\n value,\n })\n}\n","import type { Logger } from '@open-draft/logger'\nimport { XMLHttpRequestEmitter } from '.'\nimport { RequestController } from '../../RequestController'\nimport { XMLHttpRequestController } from './XMLHttpRequestController'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { isResponseError } from '../../utils/responseUtils'\n\nexport interface XMLHttpRequestProxyOptions {\n emitter: XMLHttpRequestEmitter\n logger: Logger\n}\n\n/**\n * Create a proxied `XMLHttpRequest` class.\n * The proxied class establishes spies on certain methods,\n * allowing us to intercept requests and respond to them.\n */\nexport function createXMLHttpRequestProxy({\n emitter,\n logger,\n}: XMLHttpRequestProxyOptions) {\n const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {\n construct(target, args, newTarget) {\n logger.info('constructed new XMLHttpRequest')\n\n const originalRequest = Reflect.construct(\n target,\n args,\n newTarget\n ) as XMLHttpRequest\n\n /**\n * @note Forward prototype descriptors onto the proxied object.\n * XMLHttpRequest is implemented in JSDOM in a way that assigns\n * a bunch of descriptors, like \"set responseType()\" on the prototype.\n * With this propagation, we make sure that those descriptors trigger\n * when the user operates with the proxied request instance.\n */\n const prototypeDescriptors = Object.getOwnPropertyDescriptors(\n target.prototype\n )\n for (const propertyName in prototypeDescriptors) {\n Reflect.defineProperty(\n originalRequest,\n propertyName,\n prototypeDescriptors[propertyName]\n )\n }\n\n const xhrRequestController = new XMLHttpRequestController(\n originalRequest,\n logger\n )\n\n xhrRequestController.onRequest = async function ({ request, requestId }) {\n const controller = new RequestController(request, {\n passthrough: () => {\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n },\n respondWith: async (response) => {\n if (isResponseError(response)) {\n this.errorWith(new TypeError('Network error'))\n return\n }\n\n await this.respondWith(response)\n },\n errorWith: (reason) => {\n this.logger.info('request errored!', { error: reason })\n\n if (reason instanceof Error) {\n this.errorWith(reason)\n }\n },\n })\n\n this.logger.info('awaiting mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n controller,\n emitter,\n })\n }\n\n xhrRequestController.onResponse = async function ({\n response,\n isMockedResponse,\n request,\n requestId,\n }) {\n this.logger.info(\n 'emitting the \"response\" event for %s listener(s)...',\n emitter.listenerCount('response')\n )\n\n emitter.emit('response', {\n response,\n isMockedResponse,\n request,\n requestId,\n })\n }\n\n // Return the proxied request from the controller\n // so that the controller can react to the consumer's interactions\n // with this request (opening/sending/etc).\n return xhrRequestController.request\n },\n })\n\n return XMLHttpRequestProxy\n}\n","import { Emitter } from 'strict-event-emitter'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>\n\nexport class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {\n static interceptorSymbol = Symbol('xhr')\n\n constructor() {\n super(XMLHttpRequestInterceptor.interceptorSymbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('XMLHttpRequest')\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('patching global XMLHttpRequest...')\n\n this.subscriptions.push(\n globalsRegistry.replaceGlobal(\n 'XMLHttpRequest',\n createXMLHttpRequestProxy({\n emitter: this.emitter,\n logger: this.logger,\n })\n )\n )\n\n logger.info(\n 'global XMLHttpRequest patched!',\n globalThis.XMLHttpRequest.name\n )\n }\n}\n"],"mappings":"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"} |
| import { a as RequestController, c as Interceptor, i as createRequestId, n as FetchResponse, s as INTERNAL_REQUEST_ID_HEADER_NAME, t as FetchRequest } from "./fetchUtils-Bq0Mdmkv.mjs"; | ||
| import { n as encodeBuffer, r as toArrayBuffer, t as decodeBuffer } from "./bufferUtils-DxPxwff_.mjs"; | ||
| import { n as setRawRequest } from "./getRawRequest-C2-1urzA.mjs"; | ||
| import { n as isResponseError, t as handleRequest } from "./handleRequest-u6jpjgrr.mjs"; | ||
| import { n as globalsRegistry, t as hasConfigurableGlobal } from "./hasConfigurableGlobal-jN_-v3gp.mjs"; | ||
| import { invariant } from "outvariant"; | ||
| import { isNodeProcess } from "is-node-process"; | ||
| //#region src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts | ||
| /** | ||
| * Concatenate two `Uint8Array` buffers. | ||
| */ | ||
| function concatArrayBuffer(left, right) { | ||
| const result = new Uint8Array(left.byteLength + right.byteLength); | ||
| result.set(left, 0); | ||
| result.set(right, left.byteLength); | ||
| return result; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts | ||
| var EventPolyfill = class { | ||
| constructor(type, options) { | ||
| this.NONE = 0; | ||
| this.CAPTURING_PHASE = 1; | ||
| this.AT_TARGET = 2; | ||
| this.BUBBLING_PHASE = 3; | ||
| this.type = ""; | ||
| this.srcElement = null; | ||
| this.currentTarget = null; | ||
| this.eventPhase = 0; | ||
| this.isTrusted = true; | ||
| this.composed = false; | ||
| this.cancelable = true; | ||
| this.defaultPrevented = false; | ||
| this.bubbles = true; | ||
| this.lengthComputable = true; | ||
| this.loaded = 0; | ||
| this.total = 0; | ||
| this.cancelBubble = false; | ||
| this.returnValue = true; | ||
| this.type = type; | ||
| this.target = options?.target || null; | ||
| this.currentTarget = options?.currentTarget || null; | ||
| this.timeStamp = Date.now(); | ||
| } | ||
| composedPath() { | ||
| return []; | ||
| } | ||
| initEvent(type, bubbles, cancelable) { | ||
| this.type = type; | ||
| this.bubbles = !!bubbles; | ||
| this.cancelable = !!cancelable; | ||
| } | ||
| preventDefault() { | ||
| this.defaultPrevented = true; | ||
| } | ||
| stopPropagation() {} | ||
| stopImmediatePropagation() {} | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts | ||
| var ProgressEventPolyfill = class extends EventPolyfill { | ||
| constructor(type, init) { | ||
| super(type); | ||
| this.lengthComputable = init?.lengthComputable || false; | ||
| this.composed = init?.composed || false; | ||
| this.loaded = init?.loaded || 0; | ||
| this.total = init?.total || 0; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createEvent.ts | ||
| const SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== "undefined"; | ||
| function createEvent(target, type, init) { | ||
| const progressEvents = [ | ||
| "error", | ||
| "progress", | ||
| "loadstart", | ||
| "loadend", | ||
| "load", | ||
| "timeout", | ||
| "abort" | ||
| ]; | ||
| /** | ||
| * `ProgressEvent` is not supported in React Native. | ||
| * @see https://github.com/mswjs/interceptors/issues/40 | ||
| */ | ||
| const ProgressEventClass = SUPPORTS_PROGRESS_EVENT ? ProgressEvent : ProgressEventPolyfill; | ||
| return progressEvents.includes(type) ? new ProgressEventClass(type, { | ||
| lengthComputable: true, | ||
| loaded: init?.loaded || 0, | ||
| total: init?.total || 0 | ||
| }) : new EventPolyfill(type, { | ||
| target, | ||
| currentTarget: target | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/findPropertySource.ts | ||
| /** | ||
| * Returns the source object of the given property on the target object | ||
| * (the target itself, any parent in its prototype, or null). | ||
| */ | ||
| function findPropertySource(target, propertyName) { | ||
| if (!(propertyName in target)) return null; | ||
| if (Object.prototype.hasOwnProperty.call(target, propertyName)) return target; | ||
| const prototype = Reflect.getPrototypeOf(target); | ||
| return prototype ? findPropertySource(prototype, propertyName) : null; | ||
| } | ||
| //#endregion | ||
| //#region src/utils/createProxy.ts | ||
| function createProxy(target, options) { | ||
| return new Proxy(target, optionsToProxyHandler(options)); | ||
| } | ||
| function optionsToProxyHandler(options) { | ||
| const { constructorCall, methodCall, getProperty, setProperty } = options; | ||
| const handler = {}; | ||
| if (typeof constructorCall !== "undefined") handler.construct = function(target, args, newTarget) { | ||
| const next = Reflect.construct.bind(null, target, args, newTarget); | ||
| return constructorCall.call(newTarget, args, next); | ||
| }; | ||
| handler.set = function(target, propertyName, nextValue) { | ||
| const next = () => { | ||
| const propertySource = findPropertySource(target, propertyName) || target; | ||
| const ownDescriptors = Reflect.getOwnPropertyDescriptor(propertySource, propertyName); | ||
| if (typeof ownDescriptors?.set !== "undefined") { | ||
| ownDescriptors.set.apply(target, [nextValue]); | ||
| return true; | ||
| } | ||
| return Reflect.defineProperty(propertySource, propertyName, { | ||
| writable: true, | ||
| enumerable: true, | ||
| configurable: true, | ||
| value: nextValue | ||
| }); | ||
| }; | ||
| if (typeof setProperty !== "undefined") return setProperty.call(target, [propertyName, nextValue], next); | ||
| return next(); | ||
| }; | ||
| handler.get = function(target, propertyName, receiver) { | ||
| /** | ||
| * @note Using `Reflect.get()` here causes "TypeError: Illegal invocation". | ||
| */ | ||
| const next = () => target[propertyName]; | ||
| const value = typeof getProperty !== "undefined" ? getProperty.call(target, [propertyName, receiver], next) : next(); | ||
| if (typeof value === "function") return (...args) => { | ||
| const next$1 = value.bind(target, ...args); | ||
| if (typeof methodCall !== "undefined") return methodCall.call(target, [propertyName, args], next$1); | ||
| return next$1(); | ||
| }; | ||
| return value; | ||
| }; | ||
| return handler; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts | ||
| function isDomParserSupportedType(type) { | ||
| return [ | ||
| "application/xhtml+xml", | ||
| "application/xml", | ||
| "image/svg+xml", | ||
| "text/html", | ||
| "text/xml" | ||
| ].some((supportedType) => { | ||
| return type.startsWith(supportedType); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/parseJson.ts | ||
| /** | ||
| * Parses a given string into JSON. | ||
| * Gracefully handles invalid JSON by returning `null`. | ||
| */ | ||
| function parseJson(data) { | ||
| try { | ||
| return JSON.parse(data); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/createResponse.ts | ||
| /** | ||
| * Creates a Fetch API `Response` instance from the given | ||
| * `XMLHttpRequest` instance and a response body. | ||
| */ | ||
| function createResponse(request, body) { | ||
| return new FetchResponse(FetchResponse.isResponseWithBody(request.status) ? body : null, { | ||
| url: request.responseURL, | ||
| status: request.status, | ||
| statusText: request.statusText, | ||
| headers: createHeadersFromXMLHttpRequestHeaders(request.getAllResponseHeaders()) | ||
| }); | ||
| } | ||
| function createHeadersFromXMLHttpRequestHeaders(headersString) { | ||
| const headers = new Headers(); | ||
| const lines = headersString.split(/[\r\n]+/); | ||
| for (const line of lines) { | ||
| if (line.trim() === "") continue; | ||
| const [name, ...parts] = line.split(": "); | ||
| const value = parts.join(": "); | ||
| headers.append(name, value); | ||
| } | ||
| return headers; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts | ||
| /** | ||
| * Return a total byte length of the given request/response body. | ||
| * If the `Content-Length` header is present, it will be used as the byte length. | ||
| */ | ||
| async function getBodyByteLength(input) { | ||
| const explicitContentLength = input.headers.get("content-length"); | ||
| if (explicitContentLength != null && explicitContentLength !== "") return Number(explicitContentLength); | ||
| return (await input.arrayBuffer()).byteLength; | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts | ||
| const kIsRequestHandled = Symbol("kIsRequestHandled"); | ||
| const IS_NODE = isNodeProcess(); | ||
| const kFetchRequest = Symbol("kFetchRequest"); | ||
| /** | ||
| * An `XMLHttpRequest` instance controller that allows us | ||
| * to handle any given request instance (e.g. responding to it). | ||
| */ | ||
| var XMLHttpRequestController = class { | ||
| constructor(initialRequest, logger) { | ||
| this.initialRequest = initialRequest; | ||
| this.logger = logger; | ||
| this.method = "GET"; | ||
| this.url = null; | ||
| this[kIsRequestHandled] = false; | ||
| this.events = /* @__PURE__ */ new Map(); | ||
| this.uploadEvents = /* @__PURE__ */ new Map(); | ||
| this.requestId = createRequestId(); | ||
| this.requestHeaders = new Headers(); | ||
| this.responseBuffer = new Uint8Array(); | ||
| this.request = createProxy(initialRequest, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "ontimeout": { | ||
| const eventName = propertyName.slice(2); | ||
| /** | ||
| * @note Proxy callbacks to event listeners because JSDOM has trouble | ||
| * translating these properties to callbacks. It seemed to be operating | ||
| * on events exclusively. | ||
| */ | ||
| this.request.addEventListener(eventName, nextValue); | ||
| return invoke(); | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "open": { | ||
| const [method, url] = args; | ||
| if (typeof url === "undefined") { | ||
| this.method = "GET"; | ||
| this.url = toAbsoluteUrl(method); | ||
| } else { | ||
| this.method = method; | ||
| this.url = toAbsoluteUrl(url); | ||
| } | ||
| this.logger = this.logger.extend(`${this.method} ${this.url.href}`); | ||
| this.logger.info("open", this.method, this.url.href); | ||
| return invoke(); | ||
| } | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerEvent(eventName, listener); | ||
| this.logger.info("addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| case "setRequestHeader": { | ||
| const [name, value] = args; | ||
| this.requestHeaders.set(name, value); | ||
| this.logger.info("setRequestHeader", name, value); | ||
| return invoke(); | ||
| } | ||
| case "send": { | ||
| const [body] = args; | ||
| this.request.addEventListener("load", () => { | ||
| if (typeof this.onResponse !== "undefined") { | ||
| const fetchResponse = createResponse( | ||
| this.request, | ||
| /** | ||
| * The `response` property is the right way to read | ||
| * the ambiguous response body, as the request's "responseType" may differ. | ||
| * @see https://xhr.spec.whatwg.org/#the-response-attribute | ||
| */ | ||
| this.request.response | ||
| ); | ||
| this.onResponse.call(this, { | ||
| response: fetchResponse, | ||
| isMockedResponse: this[kIsRequestHandled], | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }); | ||
| } | ||
| }); | ||
| const requestBody = typeof body === "string" ? encodeBuffer(body) : body; | ||
| const fetchRequest = this.toFetchApiRequest(requestBody); | ||
| this[kFetchRequest] = fetchRequest.clone(); | ||
| /** | ||
| * @note Start request handling on the next tick so that the user | ||
| * could add event listeners for "loadend" before the interceptor fires it. | ||
| */ | ||
| queueMicrotask(() => { | ||
| (this.onRequest?.call(this, { | ||
| request: fetchRequest, | ||
| requestId: this.requestId | ||
| }) || Promise.resolve()).finally(() => { | ||
| if (!this[kIsRequestHandled]) { | ||
| this.logger.info("request callback settled but request has not been handled (readystate %d), performing as-is...", this.request.readyState); | ||
| /** | ||
| * @note Set the intercepted request ID on the original request in Node.js | ||
| * so that if it triggers any other interceptors, they don't attempt | ||
| * to process it once again. | ||
| * | ||
| * For instance, XMLHttpRequest is often implemented via "http.ClientRequest" | ||
| * and we don't want for both XHR and ClientRequest interceptors to | ||
| * handle the same request at the same time (e.g. emit the "response" event twice). | ||
| */ | ||
| if (IS_NODE) this.request.setRequestHeader(INTERNAL_REQUEST_ID_HEADER_NAME, this.requestId); | ||
| return invoke(); | ||
| } | ||
| }); | ||
| }); | ||
| break; | ||
| } | ||
| default: return invoke(); | ||
| } | ||
| } | ||
| }); | ||
| /** | ||
| * Proxy the `.upload` property to gather the event listeners/callbacks. | ||
| */ | ||
| define(this.request, "upload", createProxy(this.request.upload, { | ||
| setProperty: ([propertyName, nextValue], invoke) => { | ||
| switch (propertyName) { | ||
| case "onloadstart": | ||
| case "onprogress": | ||
| case "onaboart": | ||
| case "onerror": | ||
| case "onload": | ||
| case "ontimeout": | ||
| case "onloadend": { | ||
| const eventName = propertyName.slice(2); | ||
| this.registerUploadEvent(eventName, nextValue); | ||
| } | ||
| } | ||
| return invoke(); | ||
| }, | ||
| methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "addEventListener": { | ||
| const [eventName, listener] = args; | ||
| this.registerUploadEvent(eventName, listener); | ||
| this.logger.info("upload.addEventListener", eventName, listener); | ||
| return invoke(); | ||
| } | ||
| } | ||
| } | ||
| })); | ||
| } | ||
| registerEvent(eventName, listener) { | ||
| const nextEvents = (this.events.get(eventName) || []).concat(listener); | ||
| this.events.set(eventName, nextEvents); | ||
| this.logger.info("registered event \"%s\"", eventName, listener); | ||
| } | ||
| registerUploadEvent(eventName, listener) { | ||
| const nextEvents = (this.uploadEvents.get(eventName) || []).concat(listener); | ||
| this.uploadEvents.set(eventName, nextEvents); | ||
| this.logger.info("registered upload event \"%s\"", eventName, listener); | ||
| } | ||
| /** | ||
| * Responds to the current request with the given | ||
| * Fetch API `Response` instance. | ||
| */ | ||
| async respondWith(response) { | ||
| /** | ||
| * @note Since `XMLHttpRequestController` delegates the handling of the responses | ||
| * to the "load" event listener that doesn't distinguish between the mocked and original | ||
| * responses, mark the request that had a mocked response with a corresponding symbol. | ||
| * | ||
| * Mark this request as having a mocked response immediately since | ||
| * calculating request/response total body length is asynchronous. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| /** | ||
| * Dispatch request upload events for requests with a body. | ||
| * @see https://github.com/mswjs/interceptors/issues/573 | ||
| */ | ||
| if (this[kFetchRequest]) { | ||
| const totalRequestBodyLength = await getBodyByteLength(this[kFetchRequest]); | ||
| this.trigger("loadstart", this.request.upload, { | ||
| loaded: 0, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("progress", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("load", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request.upload, { | ||
| loaded: totalRequestBodyLength, | ||
| total: totalRequestBodyLength | ||
| }); | ||
| } | ||
| this.logger.info("responding with a mocked response: %d %s", response.status, response.statusText); | ||
| define(this.request, "status", response.status); | ||
| define(this.request, "statusText", response.statusText); | ||
| define(this.request, "responseURL", this.url.href); | ||
| this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, { apply: (_, __, args) => { | ||
| this.logger.info("getResponseHeader", args[0]); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning null"); | ||
| return null; | ||
| } | ||
| const headerValue = response.headers.get(args[0]); | ||
| this.logger.info("resolved response header \"%s\" to", args[0], headerValue); | ||
| return headerValue; | ||
| } }); | ||
| this.request.getAllResponseHeaders = new Proxy(this.request.getAllResponseHeaders, { apply: () => { | ||
| this.logger.info("getAllResponseHeaders"); | ||
| if (this.request.readyState < this.request.HEADERS_RECEIVED) { | ||
| this.logger.info("headers not received yet, returning empty string"); | ||
| return ""; | ||
| } | ||
| const allHeaders = Array.from(response.headers.entries()).map(([headerName, headerValue]) => { | ||
| return `${headerName}: ${headerValue}`; | ||
| }).join("\r\n"); | ||
| this.logger.info("resolved all response headers to", allHeaders); | ||
| return allHeaders; | ||
| } }); | ||
| Object.defineProperties(this.request, { | ||
| response: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.response | ||
| }, | ||
| responseText: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseText | ||
| }, | ||
| responseXML: { | ||
| enumerable: true, | ||
| configurable: false, | ||
| get: () => this.responseXML | ||
| } | ||
| }); | ||
| const totalResponseBodyLength = await getBodyByteLength(response.clone()); | ||
| this.logger.info("calculated response body length", totalResponseBodyLength); | ||
| this.trigger("loadstart", this.request, { | ||
| loaded: 0, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.setReadyState(this.request.HEADERS_RECEIVED); | ||
| this.setReadyState(this.request.LOADING); | ||
| const finalizeResponse = () => { | ||
| this.logger.info("finalizing the mocked response..."); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("load", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| this.trigger("loadend", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| }; | ||
| if (response.body) { | ||
| this.logger.info("mocked response has body, streaming..."); | ||
| const reader = response.body.getReader(); | ||
| const readNextResponseBodyChunk = async () => { | ||
| const { value, done } = await reader.read(); | ||
| if (done) { | ||
| this.logger.info("response body stream done!"); | ||
| finalizeResponse(); | ||
| return; | ||
| } | ||
| if (value) { | ||
| this.logger.info("read response body chunk:", value); | ||
| this.responseBuffer = concatArrayBuffer(this.responseBuffer, value); | ||
| this.trigger("progress", this.request, { | ||
| loaded: this.responseBuffer.byteLength, | ||
| total: totalResponseBodyLength | ||
| }); | ||
| } | ||
| readNextResponseBodyChunk(); | ||
| }; | ||
| readNextResponseBodyChunk(); | ||
| } else finalizeResponse(); | ||
| } | ||
| responseBufferToText() { | ||
| return decodeBuffer(this.responseBuffer); | ||
| } | ||
| get response() { | ||
| this.logger.info("getResponse (responseType: %s)", this.request.responseType); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| switch (this.request.responseType) { | ||
| case "json": { | ||
| const responseJson = parseJson(this.responseBufferToText()); | ||
| this.logger.info("resolved response JSON", responseJson); | ||
| return responseJson; | ||
| } | ||
| case "arraybuffer": { | ||
| const arrayBuffer = toArrayBuffer(this.responseBuffer); | ||
| this.logger.info("resolved response ArrayBuffer", arrayBuffer); | ||
| return arrayBuffer; | ||
| } | ||
| case "blob": { | ||
| const mimeType = this.request.getResponseHeader("Content-Type") || "text/plain"; | ||
| const responseBlob = new Blob([this.responseBufferToText()], { type: mimeType }); | ||
| this.logger.info("resolved response Blob (mime type: %s)", responseBlob, mimeType); | ||
| return responseBlob; | ||
| } | ||
| default: { | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("resolving \"%s\" response type as text", this.request.responseType, responseText); | ||
| return responseText; | ||
| } | ||
| } | ||
| } | ||
| get responseText() { | ||
| /** | ||
| * Throw when trying to read the response body as text when the | ||
| * "responseType" doesn't expect text. This just respects the spec better. | ||
| * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute | ||
| */ | ||
| invariant(this.request.responseType === "" || this.request.responseType === "text", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.LOADING && this.request.readyState !== this.request.DONE) return ""; | ||
| const responseText = this.responseBufferToText(); | ||
| this.logger.info("getResponseText: \"%s\"", responseText); | ||
| return responseText; | ||
| } | ||
| get responseXML() { | ||
| invariant(this.request.responseType === "" || this.request.responseType === "document", "InvalidStateError: The object is in invalid state."); | ||
| if (this.request.readyState !== this.request.DONE) return null; | ||
| const contentType = this.request.getResponseHeader("Content-Type") || ""; | ||
| if (typeof DOMParser === "undefined") { | ||
| console.warn("Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly."); | ||
| return null; | ||
| } | ||
| if (isDomParserSupportedType(contentType)) return new DOMParser().parseFromString(this.responseBufferToText(), contentType); | ||
| return null; | ||
| } | ||
| errorWith(error) { | ||
| /** | ||
| * @note Mark this request as handled even if it received a mock error. | ||
| * This prevents the controller from trying to perform this request as-is. | ||
| */ | ||
| this[kIsRequestHandled] = true; | ||
| this.logger.info("responding with an error"); | ||
| this.setReadyState(this.request.DONE); | ||
| this.trigger("error", this.request); | ||
| this.trigger("loadend", this.request); | ||
| } | ||
| /** | ||
| * Transitions this request's `readyState` to the given one. | ||
| */ | ||
| setReadyState(nextReadyState) { | ||
| this.logger.info("setReadyState: %d -> %d", this.request.readyState, nextReadyState); | ||
| if (this.request.readyState === nextReadyState) { | ||
| this.logger.info("ready state identical, skipping transition..."); | ||
| return; | ||
| } | ||
| define(this.request, "readyState", nextReadyState); | ||
| this.logger.info("set readyState to: %d", nextReadyState); | ||
| if (nextReadyState !== this.request.UNSENT) { | ||
| this.logger.info("triggering \"readystatechange\" event..."); | ||
| this.trigger("readystatechange", this.request); | ||
| } | ||
| } | ||
| /** | ||
| * Triggers given event on the `XMLHttpRequest` instance. | ||
| */ | ||
| trigger(eventName, target, options) { | ||
| const callback = target[`on${eventName}`]; | ||
| const event = createEvent(target, eventName, options); | ||
| this.logger.info("trigger \"%s\"", eventName, options || ""); | ||
| if (typeof callback === "function") { | ||
| this.logger.info("found a direct \"%s\" callback, calling...", eventName); | ||
| callback.call(target, event); | ||
| } | ||
| const events = target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events; | ||
| for (const [registeredEventName, listeners] of events) if (registeredEventName === eventName) { | ||
| this.logger.info("found %d listener(s) for \"%s\" event, calling...", listeners.length, eventName); | ||
| listeners.forEach((listener) => listener.call(target, event)); | ||
| } | ||
| } | ||
| /** | ||
| * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance. | ||
| */ | ||
| toFetchApiRequest(body) { | ||
| this.logger.info("converting request to a Fetch API Request..."); | ||
| const resolvedBody = body instanceof Document ? body.documentElement.innerText : body; | ||
| const fetchRequest = new FetchRequest(this.url.href, { | ||
| method: this.method, | ||
| headers: this.requestHeaders, | ||
| credentials: this.request.withCredentials ? "include" : "same-origin", | ||
| body: ["GET", "HEAD"].includes(this.method.toUpperCase()) ? null : resolvedBody | ||
| }); | ||
| define(fetchRequest, "headers", createProxy(fetchRequest.headers, { methodCall: ([methodName, args], invoke) => { | ||
| switch (methodName) { | ||
| case "append": | ||
| case "set": { | ||
| const [headerName, headerValue] = args; | ||
| this.request.setRequestHeader(headerName, headerValue); | ||
| break; | ||
| } | ||
| case "delete": { | ||
| const [headerName] = args; | ||
| console.warn(`XMLHttpRequest: Cannot remove a "${headerName}" header from the Fetch API representation of the "${fetchRequest.method} ${fetchRequest.url}" request. XMLHttpRequest headers cannot be removed.`); | ||
| break; | ||
| } | ||
| } | ||
| return invoke(); | ||
| } })); | ||
| setRawRequest(fetchRequest, this.request); | ||
| this.logger.info("converted request to a Fetch API Request!", fetchRequest); | ||
| return fetchRequest; | ||
| } | ||
| }; | ||
| function toAbsoluteUrl(url) { | ||
| /** | ||
| * @note XMLHttpRequest interceptor may run in environments | ||
| * that implement XMLHttpRequest but don't implement "location" | ||
| * (for example, React Native). If that's the case, return the | ||
| * input URL as-is (nothing to be relative to). | ||
| * @see https://github.com/mswjs/msw/issues/1777 | ||
| */ | ||
| if (typeof location === "undefined") return new URL(url); | ||
| return new URL(url.toString(), location.href); | ||
| } | ||
| function define(target, property, value) { | ||
| Reflect.defineProperty(target, property, { | ||
| writable: true, | ||
| enumerable: true, | ||
| value | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts | ||
| /** | ||
| * Create a proxied `XMLHttpRequest` class. | ||
| * The proxied class establishes spies on certain methods, | ||
| * allowing us to intercept requests and respond to them. | ||
| */ | ||
| function createXMLHttpRequestProxy({ emitter, logger }) { | ||
| return new Proxy(globalThis.XMLHttpRequest, { construct(target, args, newTarget) { | ||
| logger.info("constructed new XMLHttpRequest"); | ||
| const originalRequest = Reflect.construct(target, args, newTarget); | ||
| /** | ||
| * @note Forward prototype descriptors onto the proxied object. | ||
| * XMLHttpRequest is implemented in JSDOM in a way that assigns | ||
| * a bunch of descriptors, like "set responseType()" on the prototype. | ||
| * With this propagation, we make sure that those descriptors trigger | ||
| * when the user operates with the proxied request instance. | ||
| */ | ||
| const prototypeDescriptors = Object.getOwnPropertyDescriptors(target.prototype); | ||
| for (const propertyName in prototypeDescriptors) Reflect.defineProperty(originalRequest, propertyName, prototypeDescriptors[propertyName]); | ||
| const xhrRequestController = new XMLHttpRequestController(originalRequest, logger); | ||
| xhrRequestController.onRequest = async function({ request, requestId }) { | ||
| const controller = new RequestController(request, { | ||
| passthrough: () => { | ||
| this.logger.info("no mocked response received, performing request as-is..."); | ||
| }, | ||
| respondWith: async (response) => { | ||
| if (isResponseError(response)) { | ||
| this.errorWith(/* @__PURE__ */ new TypeError("Network error")); | ||
| return; | ||
| } | ||
| await this.respondWith(response); | ||
| }, | ||
| errorWith: (reason) => { | ||
| this.logger.info("request errored!", { error: reason }); | ||
| if (reason instanceof Error) this.errorWith(reason); | ||
| } | ||
| }); | ||
| this.logger.info("awaiting mocked response..."); | ||
| this.logger.info("emitting the \"request\" event for %s listener(s)...", emitter.listenerCount("request")); | ||
| await handleRequest({ | ||
| request, | ||
| requestId, | ||
| controller, | ||
| emitter | ||
| }); | ||
| }; | ||
| xhrRequestController.onResponse = async function({ response, isMockedResponse, request, requestId }) { | ||
| this.logger.info("emitting the \"response\" event for %s listener(s)...", emitter.listenerCount("response")); | ||
| emitter.emit("response", { | ||
| response, | ||
| isMockedResponse, | ||
| request, | ||
| requestId | ||
| }); | ||
| }; | ||
| return xhrRequestController.request; | ||
| } }); | ||
| } | ||
| //#endregion | ||
| //#region src/interceptors/XMLHttpRequest/index.ts | ||
| var XMLHttpRequestInterceptor = class XMLHttpRequestInterceptor extends Interceptor { | ||
| static { | ||
| this.interceptorSymbol = Symbol("xhr"); | ||
| } | ||
| constructor() { | ||
| super(XMLHttpRequestInterceptor.interceptorSymbol); | ||
| } | ||
| checkEnvironment() { | ||
| return hasConfigurableGlobal("XMLHttpRequest"); | ||
| } | ||
| setup() { | ||
| const logger = this.logger.extend("setup"); | ||
| logger.info("patching global XMLHttpRequest..."); | ||
| this.subscriptions.push(globalsRegistry.replaceGlobal("XMLHttpRequest", createXMLHttpRequestProxy({ | ||
| emitter: this.emitter, | ||
| logger: this.logger | ||
| }))); | ||
| logger.info("global XMLHttpRequest patched!", globalThis.XMLHttpRequest.name); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { XMLHttpRequestInterceptor as t }; | ||
| //# sourceMappingURL=XMLHttpRequest-Cj07Pos7.mjs.map |
| {"version":3,"file":"XMLHttpRequest-Cj07Pos7.mjs","names":["handler: ProxyHandler<T>","next","initialRequest: XMLHttpRequest","logger: Logger"],"sources":["../../src/interceptors/XMLHttpRequest/utils/concatArrayBuffer.ts","../../src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts","../../src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts","../../src/interceptors/XMLHttpRequest/utils/createEvent.ts","../../src/utils/findPropertySource.ts","../../src/utils/createProxy.ts","../../src/interceptors/XMLHttpRequest/utils/isDomParserSupportedType.ts","../../src/utils/parseJson.ts","../../src/interceptors/XMLHttpRequest/utils/createResponse.ts","../../src/interceptors/XMLHttpRequest/utils/getBodyByteLength.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts","../../src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts","../../src/interceptors/XMLHttpRequest/index.ts"],"sourcesContent":["/**\n * Concatenate two `Uint8Array` buffers.\n */\nexport function concatArrayBuffer(\n left: Uint8Array,\n right: Uint8Array\n): Uint8Array {\n const result = new Uint8Array(left.byteLength + right.byteLength)\n result.set(left, 0)\n result.set(right, left.byteLength)\n return result\n}\n","export class EventPolyfill implements Event {\n readonly NONE = 0\n readonly CAPTURING_PHASE = 1\n readonly AT_TARGET = 2\n readonly BUBBLING_PHASE = 3\n\n public type: string = ''\n public srcElement: EventTarget | null = null\n public target: EventTarget | null\n public currentTarget: EventTarget | null = null\n public eventPhase: number = 0\n public timeStamp: number\n public isTrusted: boolean = true\n public composed: boolean = false\n public cancelable: boolean = true\n public defaultPrevented: boolean = false\n public bubbles: boolean = true\n public lengthComputable: boolean = true\n public loaded: number = 0\n public total: number = 0\n\n cancelBubble: boolean = false\n returnValue: boolean = true\n\n constructor(\n type: string,\n options?: { target: EventTarget; currentTarget: EventTarget }\n ) {\n this.type = type\n this.target = options?.target || null\n this.currentTarget = options?.currentTarget || null\n this.timeStamp = Date.now()\n }\n\n public composedPath(): EventTarget[] {\n return []\n }\n\n public initEvent(type: string, bubbles?: boolean, cancelable?: boolean) {\n this.type = type\n this.bubbles = !!bubbles\n this.cancelable = !!cancelable\n }\n\n public preventDefault() {\n this.defaultPrevented = true\n }\n\n public stopPropagation() {}\n public stopImmediatePropagation() {}\n}\n","import { EventPolyfill } from './EventPolyfill'\n\nexport class ProgressEventPolyfill extends EventPolyfill {\n readonly lengthComputable: boolean\n readonly composed: boolean\n readonly loaded: number\n readonly total: number\n\n constructor(type: string, init?: ProgressEventInit) {\n super(type)\n\n this.lengthComputable = init?.lengthComputable || false\n this.composed = init?.composed || false\n this.loaded = init?.loaded || 0\n this.total = init?.total || 0\n }\n}\n","import { EventPolyfill } from '../polyfills/EventPolyfill'\nimport { ProgressEventPolyfill } from '../polyfills/ProgressEventPolyfill'\n\nconst SUPPORTS_PROGRESS_EVENT = typeof ProgressEvent !== 'undefined'\n\nexport function createEvent(\n target: XMLHttpRequest | XMLHttpRequestUpload,\n type: string,\n init?: ProgressEventInit\n): EventPolyfill | ProgressEvent {\n const progressEvents = [\n 'error',\n 'progress',\n 'loadstart',\n 'loadend',\n 'load',\n 'timeout',\n 'abort',\n ]\n\n /**\n * `ProgressEvent` is not supported in React Native.\n * @see https://github.com/mswjs/interceptors/issues/40\n */\n const ProgressEventClass = SUPPORTS_PROGRESS_EVENT\n ? ProgressEvent\n : ProgressEventPolyfill\n\n const event = progressEvents.includes(type)\n ? new ProgressEventClass(type, {\n lengthComputable: true,\n loaded: init?.loaded || 0,\n total: init?.total || 0,\n })\n : new EventPolyfill(type, {\n target,\n currentTarget: target,\n })\n\n return event\n}\n","/**\n * Returns the source object of the given property on the target object\n * (the target itself, any parent in its prototype, or null).\n */\nexport function findPropertySource(\n target: object,\n propertyName: string | symbol\n): object | null {\n if (!(propertyName in target)) {\n return null\n }\n\n const hasProperty = Object.prototype.hasOwnProperty.call(target, propertyName)\n if (hasProperty) {\n return target\n }\n\n const prototype = Reflect.getPrototypeOf(target)\n return prototype ? findPropertySource(prototype, propertyName) : null\n}\n","import { findPropertySource } from './findPropertySource'\n\nexport interface ProxyOptions<Target extends Record<string, any>> {\n constructorCall?(args: Array<unknown>, next: NextFunction<Target>): Target\n\n methodCall?<F extends keyof Target>(\n this: Target,\n data: [methodName: F, args: Array<unknown>],\n next: NextFunction<void>\n ): void\n\n setProperty?(\n data: [propertyName: string | symbol, nextValue: unknown],\n next: NextFunction<boolean>\n ): boolean\n\n getProperty?(\n data: [propertyName: string | symbol, receiver: Target],\n next: NextFunction<void>\n ): void\n}\n\nexport type NextFunction<ReturnType> = () => ReturnType\n\nexport function createProxy<Target extends object>(\n target: Target,\n options: ProxyOptions<Target>\n): Target {\n const proxy = new Proxy(target, optionsToProxyHandler(options))\n\n return proxy\n}\n\nfunction optionsToProxyHandler<T extends Record<string, any>>(\n options: ProxyOptions<T>\n): ProxyHandler<T> {\n const { constructorCall, methodCall, getProperty, setProperty } = options\n const handler: ProxyHandler<T> = {}\n\n if (typeof constructorCall !== 'undefined') {\n handler.construct = function (target, args, newTarget) {\n const next = Reflect.construct.bind(null, target as any, args, newTarget)\n return constructorCall.call(newTarget, args, next)\n }\n }\n\n handler.set = function (target, propertyName, nextValue) {\n const next = () => {\n const propertySource = findPropertySource(target, propertyName) || target\n const ownDescriptors = Reflect.getOwnPropertyDescriptor(\n propertySource,\n propertyName\n )\n\n // Respect any custom setters present for this property.\n if (typeof ownDescriptors?.set !== 'undefined') {\n ownDescriptors.set.apply(target, [nextValue])\n return true\n }\n\n // Otherwise, set the property on the source.\n return Reflect.defineProperty(propertySource, propertyName, {\n writable: true,\n enumerable: true,\n configurable: true,\n value: nextValue,\n })\n }\n\n if (typeof setProperty !== 'undefined') {\n return setProperty.call(target, [propertyName, nextValue], next)\n }\n\n return next()\n }\n\n handler.get = function (target, propertyName, receiver) {\n /**\n * @note Using `Reflect.get()` here causes \"TypeError: Illegal invocation\".\n */\n const next = () => target[propertyName as any]\n\n const value =\n typeof getProperty !== 'undefined'\n ? getProperty.call(target, [propertyName, receiver], next)\n : next()\n\n if (typeof value === 'function') {\n return (...args: Array<any>) => {\n const next = value.bind(target, ...args)\n\n if (typeof methodCall !== 'undefined') {\n return methodCall.call(target, [propertyName as any, args], next)\n }\n\n return next()\n }\n }\n\n return value\n }\n\n return handler\n}\n","export function isDomParserSupportedType(\n type: string\n): type is DOMParserSupportedType {\n const supportedTypes: Array<DOMParserSupportedType> = [\n 'application/xhtml+xml',\n 'application/xml',\n 'image/svg+xml',\n 'text/html',\n 'text/xml',\n ]\n return supportedTypes.some((supportedType) => {\n return type.startsWith(supportedType)\n })\n}\n","/**\n * Parses a given string into JSON.\n * Gracefully handles invalid JSON by returning `null`.\n */\nexport function parseJson(data: string): Record<string, unknown> | null {\n try {\n const json = JSON.parse(data)\n return json\n } catch (_) {\n return null\n }\n}\n","import { FetchResponse } from '../../../utils/fetchUtils'\n\n/**\n * Creates a Fetch API `Response` instance from the given\n * `XMLHttpRequest` instance and a response body.\n */\nexport function createResponse(\n request: XMLHttpRequest,\n body: BodyInit | null\n): Response {\n /**\n * Handle XMLHttpRequest responses that must have null as the\n * response body when represented using Fetch API Response.\n * XMLHttpRequest response will always have an empty string\n * as the \"request.response\" in those cases, resulting in an error\n * when constructing a Response instance.\n * @see https://github.com/mswjs/interceptors/issues/379\n */\n const responseBodyOrNull = FetchResponse.isResponseWithBody(request.status)\n ? body\n : null\n\n return new FetchResponse(responseBodyOrNull, {\n url: request.responseURL,\n status: request.status,\n statusText: request.statusText,\n headers: createHeadersFromXMLHttpRequestHeaders(\n request.getAllResponseHeaders()\n ),\n })\n}\n\nfunction createHeadersFromXMLHttpRequestHeaders(headersString: string): Headers {\n const headers = new Headers()\n\n const lines = headersString.split(/[\\r\\n]+/)\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n const [name, ...parts] = line.split(': ')\n const value = parts.join(': ')\n\n headers.append(name, value)\n }\n\n return headers\n}\n","/**\n * Return a total byte length of the given request/response body.\n * If the `Content-Length` header is present, it will be used as the byte length.\n */\nexport async function getBodyByteLength(\n input: Request | Response\n): Promise<number> {\n const explicitContentLength = input.headers.get('content-length')\n\n if (explicitContentLength != null && explicitContentLength !== '') {\n return Number(explicitContentLength)\n }\n\n const buffer = await input.arrayBuffer()\n return buffer.byteLength\n}\n","import { invariant } from 'outvariant'\nimport { isNodeProcess } from 'is-node-process'\nimport type { Logger } from '@open-draft/logger'\nimport { concatArrayBuffer } from './utils/concatArrayBuffer'\nimport { createEvent } from './utils/createEvent'\nimport {\n decodeBuffer,\n encodeBuffer,\n toArrayBuffer,\n} from '../../utils/bufferUtils'\nimport { createProxy } from '../../utils/createProxy'\nimport { isDomParserSupportedType } from './utils/isDomParserSupportedType'\nimport { parseJson } from '../../utils/parseJson'\nimport { createResponse } from './utils/createResponse'\nimport { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'\nimport { createRequestId } from '../../createRequestId'\nimport { getBodyByteLength } from './utils/getBodyByteLength'\nimport { setRawRequest } from '../../getRawRequest'\nimport { FetchRequest } from '../../utils/fetchUtils'\n\nconst kIsRequestHandled = Symbol('kIsRequestHandled')\nconst IS_NODE = isNodeProcess()\nconst kFetchRequest = Symbol('kFetchRequest')\n\n/**\n * An `XMLHttpRequest` instance controller that allows us\n * to handle any given request instance (e.g. responding to it).\n */\nexport class XMLHttpRequestController {\n public request: XMLHttpRequest\n public requestId: string\n public onRequest?: (\n this: XMLHttpRequestController,\n args: {\n request: Request\n requestId: string\n }\n ) => Promise<void>\n public onResponse?: (\n this: XMLHttpRequestController,\n args: {\n response: Response\n isMockedResponse: boolean\n request: Request\n requestId: string\n }\n ) => void;\n\n [kIsRequestHandled]: boolean;\n [kFetchRequest]?: Request\n private method: string = 'GET'\n private url: URL = null as any\n private requestHeaders: Headers\n private responseBuffer: Uint8Array\n private events: Map<keyof XMLHttpRequestEventTargetEventMap, Array<Function>>\n private uploadEvents: Map<\n keyof XMLHttpRequestEventTargetEventMap,\n Array<Function>\n >\n\n constructor(\n readonly initialRequest: XMLHttpRequest,\n public logger: Logger\n ) {\n this[kIsRequestHandled] = false\n\n this.events = new Map()\n this.uploadEvents = new Map()\n this.requestId = createRequestId()\n this.requestHeaders = new Headers()\n this.responseBuffer = new Uint8Array()\n\n this.request = createProxy(initialRequest, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'ontimeout': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n /**\n * @note Proxy callbacks to event listeners because JSDOM has trouble\n * translating these properties to callbacks. It seemed to be operating\n * on events exclusively.\n */\n this.request.addEventListener(eventName, nextValue as any)\n\n return invoke()\n }\n\n default: {\n return invoke()\n }\n }\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'open': {\n const [method, url] = args as [string, string | undefined]\n\n if (typeof url === 'undefined') {\n this.method = 'GET'\n this.url = toAbsoluteUrl(method)\n } else {\n this.method = method\n this.url = toAbsoluteUrl(url)\n }\n\n this.logger = this.logger.extend(`${this.method} ${this.url.href}`)\n this.logger.info('open', this.method, this.url.href)\n\n return invoke()\n }\n\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n\n this.registerEvent(eventName, listener)\n this.logger.info('addEventListener', eventName, listener)\n\n return invoke()\n }\n\n case 'setRequestHeader': {\n const [name, value] = args as [string, string]\n this.requestHeaders.set(name, value)\n\n this.logger.info('setRequestHeader', name, value)\n\n return invoke()\n }\n\n case 'send': {\n const [body] = args as [\n body?: XMLHttpRequestBodyInit | Document | null,\n ]\n\n this.request.addEventListener('load', () => {\n if (typeof this.onResponse !== 'undefined') {\n // Create a Fetch API Response representation of whichever\n // response this XMLHttpRequest received. Note those may\n // be either a mocked and the original response.\n const fetchResponse = createResponse(\n this.request,\n /**\n * The `response` property is the right way to read\n * the ambiguous response body, as the request's \"responseType\" may differ.\n * @see https://xhr.spec.whatwg.org/#the-response-attribute\n */\n this.request.response\n )\n\n // Notify the consumer about the response.\n this.onResponse.call(this, {\n response: fetchResponse,\n isMockedResponse: this[kIsRequestHandled],\n request: fetchRequest,\n requestId: this.requestId!,\n })\n }\n })\n\n const requestBody =\n typeof body === 'string' ? encodeBuffer(body) : body\n\n // Delegate request handling to the consumer.\n const fetchRequest = this.toFetchApiRequest(requestBody)\n this[kFetchRequest] = fetchRequest.clone()\n\n /**\n * @note Start request handling on the next tick so that the user\n * could add event listeners for \"loadend\" before the interceptor fires it.\n */\n queueMicrotask(() => {\n const onceRequestSettled =\n this.onRequest?.call(this, {\n request: fetchRequest,\n requestId: this.requestId!,\n }) || Promise.resolve()\n\n onceRequestSettled.finally(() => {\n // If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.\n if (!this[kIsRequestHandled]) {\n this.logger.info(\n 'request callback settled but request has not been handled (readystate %d), performing as-is...',\n this.request.readyState\n )\n\n /**\n * @note Set the intercepted request ID on the original request in Node.js\n * so that if it triggers any other interceptors, they don't attempt\n * to process it once again.\n *\n * For instance, XMLHttpRequest is often implemented via \"http.ClientRequest\"\n * and we don't want for both XHR and ClientRequest interceptors to\n * handle the same request at the same time (e.g. emit the \"response\" event twice).\n */\n if (IS_NODE) {\n this.request.setRequestHeader(\n INTERNAL_REQUEST_ID_HEADER_NAME,\n this.requestId!\n )\n }\n\n return invoke()\n }\n })\n })\n\n break\n }\n\n default: {\n return invoke()\n }\n }\n },\n })\n\n /**\n * Proxy the `.upload` property to gather the event listeners/callbacks.\n */\n define(\n this.request,\n 'upload',\n createProxy(this.request.upload, {\n setProperty: ([propertyName, nextValue], invoke) => {\n switch (propertyName) {\n case 'onloadstart':\n case 'onprogress':\n case 'onaboart':\n case 'onerror':\n case 'onload':\n case 'ontimeout':\n case 'onloadend': {\n const eventName = propertyName.slice(\n 2\n ) as keyof XMLHttpRequestEventTargetEventMap\n\n this.registerUploadEvent(eventName, nextValue as Function)\n }\n }\n\n return invoke()\n },\n methodCall: ([methodName, args], invoke) => {\n switch (methodName) {\n case 'addEventListener': {\n const [eventName, listener] = args as [\n keyof XMLHttpRequestEventTargetEventMap,\n Function,\n ]\n this.registerUploadEvent(eventName, listener)\n this.logger.info('upload.addEventListener', eventName, listener)\n\n return invoke()\n }\n }\n },\n })\n )\n }\n\n private registerEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.events.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.events.set(eventName, nextEvents)\n\n this.logger.info('registered event \"%s\"', eventName, listener)\n }\n\n private registerUploadEvent(\n eventName: keyof XMLHttpRequestEventTargetEventMap,\n listener: Function\n ): void {\n const prevEvents = this.uploadEvents.get(eventName) || []\n const nextEvents = prevEvents.concat(listener)\n this.uploadEvents.set(eventName, nextEvents)\n\n this.logger.info('registered upload event \"%s\"', eventName, listener)\n }\n\n /**\n * Responds to the current request with the given\n * Fetch API `Response` instance.\n */\n public async respondWith(response: Response): Promise<void> {\n /**\n * @note Since `XMLHttpRequestController` delegates the handling of the responses\n * to the \"load\" event listener that doesn't distinguish between the mocked and original\n * responses, mark the request that had a mocked response with a corresponding symbol.\n *\n * Mark this request as having a mocked response immediately since\n * calculating request/response total body length is asynchronous.\n */\n this[kIsRequestHandled] = true\n\n /**\n * Dispatch request upload events for requests with a body.\n * @see https://github.com/mswjs/interceptors/issues/573\n */\n if (this[kFetchRequest]) {\n const totalRequestBodyLength = await getBodyByteLength(\n this[kFetchRequest]\n )\n\n this.trigger('loadstart', this.request.upload, {\n loaded: 0,\n total: totalRequestBodyLength,\n })\n this.trigger('progress', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n this.trigger('load', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n\n this.trigger('loadend', this.request.upload, {\n loaded: totalRequestBodyLength,\n total: totalRequestBodyLength,\n })\n }\n\n this.logger.info(\n 'responding with a mocked response: %d %s',\n response.status,\n response.statusText\n )\n\n define(this.request, 'status', response.status)\n define(this.request, 'statusText', response.statusText)\n define(this.request, 'responseURL', this.url.href)\n\n this.request.getResponseHeader = new Proxy(this.request.getResponseHeader, {\n apply: (_, __, args: [name: string]) => {\n this.logger.info('getResponseHeader', args[0])\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning null')\n\n // Headers not received yet, nothing to return.\n return null\n }\n\n const headerValue = response.headers.get(args[0])\n this.logger.info(\n 'resolved response header \"%s\" to',\n args[0],\n headerValue\n )\n\n return headerValue\n },\n })\n\n this.request.getAllResponseHeaders = new Proxy(\n this.request.getAllResponseHeaders,\n {\n apply: () => {\n this.logger.info('getAllResponseHeaders')\n\n if (this.request.readyState < this.request.HEADERS_RECEIVED) {\n this.logger.info('headers not received yet, returning empty string')\n\n // Headers not received yet, nothing to return.\n return ''\n }\n\n const headersList = Array.from(response.headers.entries())\n const allHeaders = headersList\n .map(([headerName, headerValue]) => {\n return `${headerName}: ${headerValue}`\n })\n .join('\\r\\n')\n\n this.logger.info('resolved all response headers to', allHeaders)\n\n return allHeaders\n },\n }\n )\n\n // Update the response getters to resolve against the mocked response.\n Object.defineProperties(this.request, {\n response: {\n enumerable: true,\n configurable: false,\n get: () => this.response,\n },\n responseText: {\n enumerable: true,\n configurable: false,\n get: () => this.responseText,\n },\n responseXML: {\n enumerable: true,\n configurable: false,\n get: () => this.responseXML,\n },\n })\n\n const totalResponseBodyLength = await getBodyByteLength(response.clone())\n\n this.logger.info('calculated response body length', totalResponseBodyLength)\n\n this.trigger('loadstart', this.request, {\n loaded: 0,\n total: totalResponseBodyLength,\n })\n\n this.setReadyState(this.request.HEADERS_RECEIVED)\n this.setReadyState(this.request.LOADING)\n\n const finalizeResponse = () => {\n this.logger.info('finalizing the mocked response...')\n\n this.setReadyState(this.request.DONE)\n\n this.trigger('load', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n\n this.trigger('loadend', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n if (response.body) {\n this.logger.info('mocked response has body, streaming...')\n\n const reader = response.body.getReader()\n\n const readNextResponseBodyChunk = async () => {\n const { value, done } = await reader.read()\n\n if (done) {\n this.logger.info('response body stream done!')\n finalizeResponse()\n return\n }\n\n if (value) {\n this.logger.info('read response body chunk:', value)\n this.responseBuffer = concatArrayBuffer(this.responseBuffer, value)\n\n this.trigger('progress', this.request, {\n loaded: this.responseBuffer.byteLength,\n total: totalResponseBodyLength,\n })\n }\n\n readNextResponseBodyChunk()\n }\n\n readNextResponseBodyChunk()\n } else {\n finalizeResponse()\n }\n }\n\n private responseBufferToText(): string {\n return decodeBuffer(this.responseBuffer)\n }\n\n get response(): unknown {\n this.logger.info(\n 'getResponse (responseType: %s)',\n this.request.responseType\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n switch (this.request.responseType) {\n case 'json': {\n const responseJson = parseJson(this.responseBufferToText())\n this.logger.info('resolved response JSON', responseJson)\n\n return responseJson\n }\n\n case 'arraybuffer': {\n const arrayBuffer = toArrayBuffer(this.responseBuffer)\n this.logger.info('resolved response ArrayBuffer', arrayBuffer)\n\n return arrayBuffer\n }\n\n case 'blob': {\n const mimeType =\n this.request.getResponseHeader('Content-Type') || 'text/plain'\n const responseBlob = new Blob([this.responseBufferToText()], {\n type: mimeType,\n })\n\n this.logger.info(\n 'resolved response Blob (mime type: %s)',\n responseBlob,\n mimeType\n )\n\n return responseBlob\n }\n\n default: {\n const responseText = this.responseBufferToText()\n this.logger.info(\n 'resolving \"%s\" response type as text',\n this.request.responseType,\n responseText\n )\n\n return responseText\n }\n }\n }\n\n get responseText(): string {\n /**\n * Throw when trying to read the response body as text when the\n * \"responseType\" doesn't expect text. This just respects the spec better.\n * @see https://xhr.spec.whatwg.org/#the-responsetext-attribute\n */\n invariant(\n this.request.responseType === '' || this.request.responseType === 'text',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (\n this.request.readyState !== this.request.LOADING &&\n this.request.readyState !== this.request.DONE\n ) {\n return ''\n }\n\n const responseText = this.responseBufferToText()\n this.logger.info('getResponseText: \"%s\"', responseText)\n\n return responseText\n }\n\n get responseXML(): Document | null {\n invariant(\n this.request.responseType === '' ||\n this.request.responseType === 'document',\n 'InvalidStateError: The object is in invalid state.'\n )\n\n if (this.request.readyState !== this.request.DONE) {\n return null\n }\n\n const contentType = this.request.getResponseHeader('Content-Type') || ''\n\n if (typeof DOMParser === 'undefined') {\n console.warn(\n 'Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.'\n )\n return null\n }\n\n if (isDomParserSupportedType(contentType)) {\n return new DOMParser().parseFromString(\n this.responseBufferToText(),\n contentType\n )\n }\n\n return null\n }\n\n public errorWith(error?: Error): void {\n /**\n * @note Mark this request as handled even if it received a mock error.\n * This prevents the controller from trying to perform this request as-is.\n */\n this[kIsRequestHandled] = true\n this.logger.info('responding with an error')\n\n this.setReadyState(this.request.DONE)\n this.trigger('error', this.request)\n this.trigger('loadend', this.request)\n }\n\n /**\n * Transitions this request's `readyState` to the given one.\n */\n private setReadyState(nextReadyState: number): void {\n this.logger.info(\n 'setReadyState: %d -> %d',\n this.request.readyState,\n nextReadyState\n )\n\n if (this.request.readyState === nextReadyState) {\n this.logger.info('ready state identical, skipping transition...')\n return\n }\n\n define(this.request, 'readyState', nextReadyState)\n\n this.logger.info('set readyState to: %d', nextReadyState)\n\n if (nextReadyState !== this.request.UNSENT) {\n this.logger.info('triggering \"readystatechange\" event...')\n\n this.trigger('readystatechange', this.request)\n }\n }\n\n /**\n * Triggers given event on the `XMLHttpRequest` instance.\n */\n private trigger<\n EventName extends keyof (XMLHttpRequestEventTargetEventMap & {\n readystatechange: ProgressEvent<XMLHttpRequestEventTarget>\n }),\n >(\n eventName: EventName,\n target: XMLHttpRequest | XMLHttpRequestUpload,\n options?: ProgressEventInit\n ): void {\n const callback = (target as XMLHttpRequest)[`on${eventName}`]\n const event = createEvent(target, eventName, options)\n\n this.logger.info('trigger \"%s\"', eventName, options || '')\n\n // Invoke direct callbacks.\n if (typeof callback === 'function') {\n this.logger.info('found a direct \"%s\" callback, calling...', eventName)\n callback.call(target as XMLHttpRequest, event)\n }\n\n // Invoke event listeners.\n const events =\n target instanceof XMLHttpRequestUpload ? this.uploadEvents : this.events\n\n for (const [registeredEventName, listeners] of events) {\n if (registeredEventName === eventName) {\n this.logger.info(\n 'found %d listener(s) for \"%s\" event, calling...',\n listeners.length,\n eventName\n )\n\n listeners.forEach((listener) => listener.call(target, event))\n }\n }\n }\n\n /**\n * Converts this `XMLHttpRequest` instance into a Fetch API `Request` instance.\n */\n private toFetchApiRequest(\n body: XMLHttpRequestBodyInit | Document | null | undefined\n ): Request {\n this.logger.info('converting request to a Fetch API Request...')\n\n // If the `Document` is used as the body of this XMLHttpRequest,\n // set its inner text as the Fetch API Request body.\n const resolvedBody =\n body instanceof Document ? body.documentElement.innerText : body\n\n const fetchRequest = new FetchRequest(this.url.href, {\n method: this.method,\n headers: this.requestHeaders,\n /**\n * @see https://xhr.spec.whatwg.org/#cross-origin-credentials\n */\n credentials: this.request.withCredentials ? 'include' : 'same-origin',\n body: ['GET', 'HEAD'].includes(this.method.toUpperCase())\n ? null\n : resolvedBody,\n })\n\n const proxyHeaders = createProxy(fetchRequest.headers, {\n methodCall: ([methodName, args], invoke) => {\n // Forward the latest state of the internal request headers\n // because the interceptor might have modified them\n // without responding to the request.\n switch (methodName) {\n case 'append':\n case 'set': {\n const [headerName, headerValue] = args as [string, string]\n this.request.setRequestHeader(headerName, headerValue)\n break\n }\n\n case 'delete': {\n const [headerName] = args as [string]\n console.warn(\n `XMLHttpRequest: Cannot remove a \"${headerName}\" header from the Fetch API representation of the \"${fetchRequest.method} ${fetchRequest.url}\" request. XMLHttpRequest headers cannot be removed.`\n )\n break\n }\n }\n\n return invoke()\n },\n })\n define(fetchRequest, 'headers', proxyHeaders)\n setRawRequest(fetchRequest, this.request)\n\n this.logger.info('converted request to a Fetch API Request!', fetchRequest)\n\n return fetchRequest\n }\n}\n\nfunction toAbsoluteUrl(url: string | URL): URL {\n /**\n * @note XMLHttpRequest interceptor may run in environments\n * that implement XMLHttpRequest but don't implement \"location\"\n * (for example, React Native). If that's the case, return the\n * input URL as-is (nothing to be relative to).\n * @see https://github.com/mswjs/msw/issues/1777\n */\n if (typeof location === 'undefined') {\n return new URL(url)\n }\n\n return new URL(url.toString(), location.href)\n}\n\nfunction define(\n target: object,\n property: string | symbol,\n value: unknown\n): void {\n Reflect.defineProperty(target, property, {\n // Ensure writable properties to allow redefining readonly properties.\n writable: true,\n enumerable: true,\n value,\n })\n}\n","import type { Logger } from '@open-draft/logger'\nimport { XMLHttpRequestEmitter } from '.'\nimport { RequestController } from '../../RequestController'\nimport { XMLHttpRequestController } from './XMLHttpRequestController'\nimport { handleRequest } from '../../utils/handleRequest'\nimport { isResponseError } from '../../utils/responseUtils'\n\nexport interface XMLHttpRequestProxyOptions {\n emitter: XMLHttpRequestEmitter\n logger: Logger\n}\n\n/**\n * Create a proxied `XMLHttpRequest` class.\n * The proxied class establishes spies on certain methods,\n * allowing us to intercept requests and respond to them.\n */\nexport function createXMLHttpRequestProxy({\n emitter,\n logger,\n}: XMLHttpRequestProxyOptions) {\n const XMLHttpRequestProxy = new Proxy(globalThis.XMLHttpRequest, {\n construct(target, args, newTarget) {\n logger.info('constructed new XMLHttpRequest')\n\n const originalRequest = Reflect.construct(\n target,\n args,\n newTarget\n ) as XMLHttpRequest\n\n /**\n * @note Forward prototype descriptors onto the proxied object.\n * XMLHttpRequest is implemented in JSDOM in a way that assigns\n * a bunch of descriptors, like \"set responseType()\" on the prototype.\n * With this propagation, we make sure that those descriptors trigger\n * when the user operates with the proxied request instance.\n */\n const prototypeDescriptors = Object.getOwnPropertyDescriptors(\n target.prototype\n )\n for (const propertyName in prototypeDescriptors) {\n Reflect.defineProperty(\n originalRequest,\n propertyName,\n prototypeDescriptors[propertyName]\n )\n }\n\n const xhrRequestController = new XMLHttpRequestController(\n originalRequest,\n logger\n )\n\n xhrRequestController.onRequest = async function ({ request, requestId }) {\n const controller = new RequestController(request, {\n passthrough: () => {\n this.logger.info(\n 'no mocked response received, performing request as-is...'\n )\n },\n respondWith: async (response) => {\n if (isResponseError(response)) {\n this.errorWith(new TypeError('Network error'))\n return\n }\n\n await this.respondWith(response)\n },\n errorWith: (reason) => {\n this.logger.info('request errored!', { error: reason })\n\n if (reason instanceof Error) {\n this.errorWith(reason)\n }\n },\n })\n\n this.logger.info('awaiting mocked response...')\n\n this.logger.info(\n 'emitting the \"request\" event for %s listener(s)...',\n emitter.listenerCount('request')\n )\n\n await handleRequest({\n request,\n requestId,\n controller,\n emitter,\n })\n }\n\n xhrRequestController.onResponse = async function ({\n response,\n isMockedResponse,\n request,\n requestId,\n }) {\n this.logger.info(\n 'emitting the \"response\" event for %s listener(s)...',\n emitter.listenerCount('response')\n )\n\n emitter.emit('response', {\n response,\n isMockedResponse,\n request,\n requestId,\n })\n }\n\n // Return the proxied request from the controller\n // so that the controller can react to the consumer's interactions\n // with this request (opening/sending/etc).\n return xhrRequestController.request\n },\n })\n\n return XMLHttpRequestProxy\n}\n","import { Emitter } from 'strict-event-emitter'\nimport { HttpRequestEventMap } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'\nimport { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'\nimport { globalsRegistry } from '../../utils/globalsRegistry'\n\nexport type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>\n\nexport class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {\n static interceptorSymbol = Symbol('xhr')\n\n constructor() {\n super(XMLHttpRequestInterceptor.interceptorSymbol)\n }\n\n protected checkEnvironment() {\n return hasConfigurableGlobal('XMLHttpRequest')\n }\n\n protected setup() {\n const logger = this.logger.extend('setup')\n\n logger.info('patching global XMLHttpRequest...')\n\n this.subscriptions.push(\n globalsRegistry.replaceGlobal(\n 'XMLHttpRequest',\n createXMLHttpRequestProxy({\n emitter: this.emitter,\n logger: this.logger,\n })\n )\n )\n\n logger.info(\n 'global XMLHttpRequest patched!',\n globalThis.XMLHttpRequest.name\n )\n }\n}\n"],"mappings":"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"} |
| import { invariant } from 'outvariant' | ||
| class GlobalsRegistry { | ||
| #globals = new Map<keyof typeof globalThis, () => void>() | ||
| public replaceGlobal<K extends keyof typeof globalThis>( | ||
| key: K, | ||
| nextValue: (typeof globalThis)[K] | ||
| ): () => void { | ||
| invariant( | ||
| !this.#globals.has(key), | ||
| `Failed to replace a global value at "${key}": already replaced.` | ||
| ) | ||
| const match = getDeepPropertyDescriptor(globalThis, key) | ||
| if (typeof match === 'undefined') { | ||
| console.warn( | ||
| `Failed to replace a global value at "${key}": not a global value.` | ||
| ) | ||
| return () => {} | ||
| } | ||
| Object.defineProperty(globalThis, key, { | ||
| value: nextValue, | ||
| enumerable: true, | ||
| configurable: true, | ||
| }) | ||
| const restoreGlobal = () => { | ||
| if (!this.#globals.has(key)) { | ||
| return | ||
| } | ||
| if (match.owner === globalThis) { | ||
| Object.defineProperty(match.owner, key, match.descriptor) | ||
| } else { | ||
| /** | ||
| * @todo Delete the proxy property set by the registry. | ||
| * If the owner isn't `globalThis`, the property is likely nested in the prototype. | ||
| * The registry does not meddle with those, they are left intact. | ||
| */ | ||
| Reflect.deleteProperty(globalThis, key) | ||
| } | ||
| this.#globals.delete(key) | ||
| } | ||
| this.#globals.set(key, restoreGlobal) | ||
| return restoreGlobal | ||
| } | ||
| public restoreAllGlobals(): void { | ||
| const errors: Array<Error> = [] | ||
| for (const [, restoreGlobal] of this.#globals) { | ||
| try { | ||
| restoreGlobal() | ||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| errors.push(error) | ||
| } else { | ||
| throw error | ||
| } | ||
| } | ||
| } | ||
| if (errors.length > 0) { | ||
| throw new AggregateError(errors, 'FOO!') | ||
| } | ||
| } | ||
| } | ||
| export const globalsRegistry = new GlobalsRegistry() | ||
| interface DeepDescriptorMatch { | ||
| owner: object | ||
| descriptor: PropertyDescriptor | ||
| } | ||
| /** | ||
| * Returns a property descriptor for the given property on the owner. | ||
| * Walks down the prototype chain if the property does not exist on the owner. | ||
| * Handy for getting a global property descriptor where `globalThis` is | ||
| * replaced with a controlled class (e.g. ServiceWorkerGlobalScope). | ||
| */ | ||
| export function getDeepPropertyDescriptor<Owner extends object>( | ||
| owner: Owner, | ||
| key: keyof Owner | ||
| ): DeepDescriptorMatch | undefined { | ||
| let currentOwner: Owner | null = owner | ||
| let descriptor: PropertyDescriptor | undefined | ||
| while (currentOwner) { | ||
| descriptor = Object.getOwnPropertyDescriptor(currentOwner, key) | ||
| if (descriptor) { | ||
| return { | ||
| owner: currentOwner, | ||
| descriptor, | ||
| } | ||
| } | ||
| currentOwner = Object.getPrototypeOf(currentOwner) | ||
| } | ||
| } |
| import { it, beforeEach, afterEach, expect, vi } from 'vitest' | ||
| import { globalsRegistry } from './globalsRegistry' | ||
| declare global { | ||
| var foo: { original: boolean } | ||
| } | ||
| const realGlobalPrototype = Object.getPrototypeOf(global) | ||
| beforeEach(() => { | ||
| global.foo = { original: true } | ||
| }) | ||
| afterEach(() => { | ||
| Object.setPrototypeOf(global, realGlobalPrototype) | ||
| globalsRegistry.restoreAllGlobals() | ||
| }) | ||
| it('replaces the global', () => { | ||
| globalsRegistry.replaceGlobal('foo', { original: false }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| }) | ||
| it('replaces the global set on the prototype', () => { | ||
| function FakeGlobalScope() {} | ||
| FakeGlobalScope.prototype.foo = { prototype: true } | ||
| Object.setPrototypeOf(global, FakeGlobalScope.prototype) | ||
| Reflect.deleteProperty(global, 'foo') | ||
| expect(global.foo).toEqual({ prototype: true }) | ||
| expect(FakeGlobalScope.prototype.foo).toEqual({ prototype: true }) | ||
| globalsRegistry.replaceGlobal('foo', { original: false }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| expect(FakeGlobalScope.prototype.foo, 'Preserves prototype value').toEqual({ | ||
| prototype: true, | ||
| }) | ||
| }) | ||
| it('replaces the global after it was restored', () => { | ||
| const restoreGlobal = globalsRegistry.replaceGlobal('foo', { | ||
| original: false, | ||
| }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| globalsRegistry.replaceGlobal('foo', { original: false }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| }) | ||
| it('warns on replacing a non-existing global', () => { | ||
| vi.spyOn(console, 'warn').mockImplementation(() => {}) | ||
| globalsRegistry.replaceGlobal( | ||
| // @ts-expect-error Intentionally invalid value. | ||
| 'NON-EXISTING', | ||
| { original: false } | ||
| ) | ||
| expect(console.warn).toHaveBeenCalledExactlyOnceWith( | ||
| 'Failed to replace a global value at "NON-EXISTING": not a global value.' | ||
| ) | ||
| }) | ||
| it('throws if replacing an already replaced global', () => { | ||
| globalsRegistry.replaceGlobal('foo', { original: false }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| expect(() => | ||
| globalsRegistry.replaceGlobal('foo', { original: false }) | ||
| ).toThrow('Failed to replace a global value at "foo": already replaced.') | ||
| }) | ||
| it('does nothing if restoring an already restored global', () => { | ||
| const restoreGlobal = globalsRegistry.replaceGlobal('foo', { | ||
| original: false, | ||
| }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| }) | ||
| it('restores the global', () => { | ||
| const restoreGlobal = globalsRegistry.replaceGlobal('foo', { | ||
| original: false, | ||
| }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| }) | ||
| it('restores the global set on the prototype', () => { | ||
| function FakeGlobalScope() {} | ||
| FakeGlobalScope.prototype.foo = { prototype: true } | ||
| Object.setPrototypeOf(global, FakeGlobalScope.prototype) | ||
| Reflect.deleteProperty(global, 'foo') | ||
| expect(global.foo).toEqual({ prototype: true }) | ||
| const restoreGlobal = globalsRegistry.replaceGlobal('foo', { | ||
| original: false, | ||
| }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ prototype: true }) | ||
| }) | ||
| it('restores global to the original property descriptor', () => { | ||
| const descriptor: PropertyDescriptor = { | ||
| value: { original: true }, | ||
| enumerable: false, | ||
| configurable: true, | ||
| writable: false, | ||
| } | ||
| Object.defineProperty(global, 'foo', descriptor) | ||
| expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual(descriptor) | ||
| const restoreGlobal = globalsRegistry.replaceGlobal('foo', { | ||
| original: false, | ||
| }) | ||
| expect(global.foo).toEqual({ original: false }) | ||
| expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual({ | ||
| value: { original: false }, | ||
| enumerable: true, | ||
| configurable: true, | ||
| writable: false, | ||
| }) | ||
| restoreGlobal() | ||
| expect(global.foo).toEqual({ original: true }) | ||
| expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual(descriptor) | ||
| }) |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
1629599
1.48%19693
1.42%3
-25%