@mswjs/interceptors 0.28.1 → 0.28.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/{chunk-UJZOJSMP.mjs → chunk-6HYIRFX2.mjs} +2 -15
- package/lib/browser/chunk-6HYIRFX2.mjs.map +1 -0
- package/lib/browser/chunk-F2F5QHHJ.js +39 -0
- package/lib/browser/chunk-F2F5QHHJ.js.map +1 -0
- package/lib/browser/{chunk-BUK5B6JV.js → chunk-LAEV5ZGV.js} +17 -25
- package/lib/browser/chunk-LAEV5ZGV.js.map +1 -0
- package/lib/browser/{chunk-OJ2CN4LS.js → chunk-LK6DILFK.js} +2 -15
- package/lib/browser/chunk-LK6DILFK.js.map +1 -0
- package/lib/browser/{chunk-LRLSJWIP.mjs → chunk-NIWUC7GF.mjs} +42 -46
- package/lib/browser/chunk-NIWUC7GF.mjs.map +1 -0
- package/lib/browser/{chunk-DJQVIQNN.mjs → chunk-PXSYFJ7G.mjs} +11 -19
- package/lib/browser/chunk-PXSYFJ7G.mjs.map +1 -0
- package/lib/browser/{chunk-4RNZUHJE.js → chunk-RLGVQZ5O.js} +44 -48
- package/lib/browser/chunk-RLGVQZ5O.js.map +1 -0
- package/lib/browser/chunk-VISYSKLR.mjs +39 -0
- package/lib/browser/chunk-VISYSKLR.mjs.map +1 -0
- package/lib/browser/index.js +3 -3
- package/lib/browser/index.mjs +5 -5
- package/lib/browser/interceptors/XMLHttpRequest/index.js +4 -4
- package/lib/browser/interceptors/XMLHttpRequest/index.mjs +3 -3
- package/lib/browser/interceptors/fetch/index.js +3 -3
- package/lib/browser/interceptors/fetch/index.mjs +2 -2
- package/lib/browser/presets/browser.js +6 -6
- package/lib/browser/presets/browser.mjs +4 -4
- package/lib/node/RemoteHttpInterceptor.js +8 -9
- package/lib/node/RemoteHttpInterceptor.js.map +1 -1
- package/lib/node/RemoteHttpInterceptor.mjs +4 -5
- package/lib/node/RemoteHttpInterceptor.mjs.map +1 -1
- package/lib/node/{chunk-GUY7XK43.mjs → chunk-2SC4AD6S.mjs} +2 -2
- package/lib/node/{chunk-QED3Q6Z2.mjs → chunk-DQ5DO3KN.mjs} +34 -2
- package/lib/node/chunk-DQ5DO3KN.mjs.map +1 -0
- package/lib/node/{chunk-GXADOCKV.mjs → chunk-KGNKRQ7B.mjs} +10 -20
- package/lib/node/chunk-KGNKRQ7B.mjs.map +1 -0
- package/lib/node/{chunk-TIPR373R.js → chunk-KRDNUBDZ.js} +34 -2
- package/lib/node/chunk-KRDNUBDZ.js.map +1 -0
- package/lib/node/{chunk-MMDNOPXM.mjs → chunk-L576JLIX.mjs} +13 -25
- package/lib/node/chunk-L576JLIX.mjs.map +1 -0
- package/lib/node/{chunk-SUPZUWTB.js → chunk-M4JXH4RP.js} +13 -23
- package/lib/node/chunk-M4JXH4RP.js.map +1 -0
- package/lib/node/{chunk-PVWK3YQT.js → chunk-UXEUSYDY.js} +16 -28
- package/lib/node/chunk-UXEUSYDY.js.map +1 -0
- package/lib/node/{chunk-LMCO6WE2.js → chunk-Z2DPXZWN.js} +3 -3
- package/lib/node/index.js +4 -6
- package/lib/node/index.js.map +1 -1
- package/lib/node/index.mjs +4 -6
- package/lib/node/index.mjs.map +1 -1
- package/lib/node/interceptors/ClientRequest/index.js +3 -4
- package/lib/node/interceptors/ClientRequest/index.mjs +2 -3
- package/lib/node/interceptors/XMLHttpRequest/index.js +3 -4
- package/lib/node/interceptors/XMLHttpRequest/index.mjs +2 -3
- package/lib/node/interceptors/fetch/index.js +42 -46
- package/lib/node/interceptors/fetch/index.js.map +1 -1
- package/lib/node/interceptors/fetch/index.mjs +41 -45
- package/lib/node/interceptors/fetch/index.mjs.map +1 -1
- package/lib/node/presets/node.js +5 -6
- package/lib/node/presets/node.js.map +1 -1
- package/lib/node/presets/node.mjs +3 -4
- package/lib/node/presets/node.mjs.map +1 -1
- package/package.json +1 -1
- package/src/interceptors/ClientRequest/NodeClientRequest.ts +16 -22
- package/src/interceptors/ClientRequest/index.test.ts +1 -1
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +10 -22
- package/src/interceptors/fetch/index.ts +53 -54
- package/src/utils/responseUtils.ts +24 -0
- package/lib/browser/chunk-4RNZUHJE.js.map +0 -1
- package/lib/browser/chunk-BUK5B6JV.js.map +0 -1
- package/lib/browser/chunk-DJQVIQNN.mjs.map +0 -1
- package/lib/browser/chunk-FZJKKO5H.js +0 -7
- package/lib/browser/chunk-FZJKKO5H.js.map +0 -1
- package/lib/browser/chunk-HAGW22AN.mjs +0 -7
- package/lib/browser/chunk-HAGW22AN.mjs.map +0 -1
- package/lib/browser/chunk-LRLSJWIP.mjs.map +0 -1
- package/lib/browser/chunk-OJ2CN4LS.js.map +0 -1
- package/lib/browser/chunk-UJZOJSMP.mjs.map +0 -1
- package/lib/node/chunk-GXADOCKV.mjs.map +0 -1
- package/lib/node/chunk-IBYBTTYK.mjs +0 -16
- package/lib/node/chunk-IBYBTTYK.mjs.map +0 -1
- package/lib/node/chunk-JSSEHRRB.js +0 -16
- package/lib/node/chunk-JSSEHRRB.js.map +0 -1
- package/lib/node/chunk-MMDNOPXM.mjs.map +0 -1
- package/lib/node/chunk-PVWK3YQT.js.map +0 -1
- package/lib/node/chunk-QED3Q6Z2.mjs.map +0 -1
- package/lib/node/chunk-SUPZUWTB.js.map +0 -1
- package/lib/node/chunk-TIPR373R.js.map +0 -1
- /package/lib/node/{chunk-GUY7XK43.mjs.map → chunk-2SC4AD6S.mjs.map} +0 -0
- /package/lib/node/{chunk-LMCO6WE2.js.map → chunk-Z2DPXZWN.js.map} +0 -0
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
} from "../../chunk-OUWBQF3Z.mjs";
|
|
11
11
|
import {
|
|
12
12
|
Interceptor,
|
|
13
|
-
createRequestId
|
|
14
|
-
|
|
13
|
+
createRequestId,
|
|
14
|
+
createServerErrorResponse
|
|
15
|
+
} from "../../chunk-DQ5DO3KN.mjs";
|
|
15
16
|
|
|
16
17
|
// src/interceptors/fetch/index.ts
|
|
17
18
|
import { invariant } from "outvariant";
|
|
@@ -73,42 +74,50 @@ var _FetchInterceptor = class extends Interceptor {
|
|
|
73
74
|
{ once: true }
|
|
74
75
|
);
|
|
75
76
|
}
|
|
76
|
-
const
|
|
77
|
-
const
|
|
77
|
+
const respondWith = (response) => {
|
|
78
|
+
const responseClone = response.clone();
|
|
79
|
+
this.emitter.emit("response", {
|
|
80
|
+
response: responseClone,
|
|
81
|
+
isMockedResponse: true,
|
|
78
82
|
request: interactiveRequest,
|
|
79
83
|
requestId
|
|
80
84
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
Object.defineProperty(response, "url", {
|
|
86
|
+
writable: false,
|
|
87
|
+
enumerable: true,
|
|
88
|
+
configurable: false,
|
|
89
|
+
value: request.url
|
|
90
|
+
});
|
|
91
|
+
return response;
|
|
92
|
+
};
|
|
93
|
+
const resolverResult = await until(
|
|
94
|
+
async () => {
|
|
95
|
+
const listenersFinished = emitAsync(this.emitter, "request", {
|
|
96
|
+
request: interactiveRequest,
|
|
97
|
+
requestId
|
|
98
|
+
});
|
|
99
|
+
await Promise.race([
|
|
100
|
+
requestAborted,
|
|
101
|
+
// Put the listeners invocation Promise in the same race condition
|
|
102
|
+
// with the request abort Promise because otherwise awaiting the listeners
|
|
103
|
+
// would always yield some response (or undefined).
|
|
104
|
+
listenersFinished,
|
|
105
|
+
requestController.responsePromise
|
|
106
|
+
]);
|
|
107
|
+
this.logger.info("all request listeners have been resolved!");
|
|
108
|
+
const mockedResponse2 = await requestController.responsePromise;
|
|
109
|
+
this.logger.info("event.respondWith called with:", mockedResponse2);
|
|
110
|
+
return mockedResponse2;
|
|
111
|
+
}
|
|
112
|
+
);
|
|
94
113
|
if (requestAborted.state === "rejected") {
|
|
95
114
|
return Promise.reject(requestAborted.rejectionReason);
|
|
96
115
|
}
|
|
97
116
|
if (resolverResult.error) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
stack: resolverResult.error.stack
|
|
103
|
-
}),
|
|
104
|
-
{
|
|
105
|
-
status: 500,
|
|
106
|
-
statusText: "Unhandled Exception",
|
|
107
|
-
headers: {
|
|
108
|
-
"Content-Type": "application/json"
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
);
|
|
117
|
+
if (resolverResult.error instanceof Response) {
|
|
118
|
+
return respondWith(resolverResult.error);
|
|
119
|
+
}
|
|
120
|
+
return createServerErrorResponse(resolverResult.error);
|
|
112
121
|
}
|
|
113
122
|
const mockedResponse = resolverResult.data;
|
|
114
123
|
if (mockedResponse && !((_a = request.signal) == null ? void 0 : _a.aborted)) {
|
|
@@ -119,20 +128,7 @@ var _FetchInterceptor = class extends Interceptor {
|
|
|
119
128
|
);
|
|
120
129
|
return Promise.reject(createNetworkError(mockedResponse));
|
|
121
130
|
}
|
|
122
|
-
|
|
123
|
-
this.emitter.emit("response", {
|
|
124
|
-
response: responseClone,
|
|
125
|
-
isMockedResponse: true,
|
|
126
|
-
request: interactiveRequest,
|
|
127
|
-
requestId
|
|
128
|
-
});
|
|
129
|
-
Object.defineProperty(mockedResponse, "url", {
|
|
130
|
-
writable: false,
|
|
131
|
-
enumerable: true,
|
|
132
|
-
configurable: false,
|
|
133
|
-
value: request.url
|
|
134
|
-
});
|
|
135
|
-
return mockedResponse;
|
|
131
|
+
return respondWith(mockedResponse);
|
|
136
132
|
}
|
|
137
133
|
this.logger.info("no mocked response received!");
|
|
138
134
|
return pureFetch(request).then((response) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/interceptors/fetch/index.ts","../../../../src/utils/canParseUrl.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { toInteractiveRequest } from '../../utils/toInteractiveRequest'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { isPropertyAccessible } from '../../utils/isPropertyAccessible'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\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 (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.fetch !== 'undefined'\n )\n }\n\n protected async setup() {\n const pureFetch = globalThis.fetch\n\n invariant(\n !(pureFetch as any)[IS_PATCHED_MODULE],\n 'Failed to patch the \"fetch\" module: already patched.'\n )\n\n globalThis.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.origin)\n : input\n\n const request = new Request(resolvedInput, init)\n\n this.logger.info('[%s] %s', request.method, request.url)\n\n const { interactiveRequest, requestController } =\n toInteractiveRequest(request)\n\n this.logger.info(\n 'emitting the \"request\" event for %d listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n this.emitter.once('request', ({ requestId: pendingRequestId }) => {\n if (pendingRequestId !== requestId) {\n return\n }\n\n if (requestController.responsePromise.state === 'pending') {\n requestController.responsePromise.resolve(undefined)\n }\n })\n\n this.logger.info('awaiting for the mocked response...')\n\n const signal = interactiveRequest.signal\n const requestAborted = new DeferredPromise()\n\n // Signal isn't always defined in react-native.\n if (signal) {\n signal.addEventListener(\n 'abort',\n () => {\n requestAborted.reject(signal.reason)\n },\n { once: true }\n )\n }\n\n const resolverResult = await until(async () => {\n const listenersFinished = emitAsync(this.emitter, 'request', {\n request: interactiveRequest,\n requestId,\n })\n\n await Promise.race([\n requestAborted,\n // Put the listeners invocation Promise in the same race condition\n // with the request abort Promise because otherwise awaiting the listeners\n // would always yield some response (or undefined).\n listenersFinished,\n requestController.responsePromise,\n ])\n\n this.logger.info('all request listeners have been resolved!')\n\n const mockedResponse = await requestController.responsePromise\n this.logger.info('event.respondWith called with:', mockedResponse)\n\n return mockedResponse\n })\n\n if (requestAborted.state === 'rejected') {\n return Promise.reject(requestAborted.rejectionReason)\n }\n\n if (resolverResult.error) {\n // Treat unhandled exceptions from the \"request\" listeners\n // as 500 errors from the server. Fetch API doesn't respect\n // Node.js internal errors so no special treatment for those.\n return new Response(\n JSON.stringify({\n name: resolverResult.error.name,\n message: resolverResult.error.message,\n stack: resolverResult.error.stack,\n }),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n }\n\n const mockedResponse = resolverResult.data\n\n if (mockedResponse && !request.signal?.aborted) {\n this.logger.info('received mocked response:', mockedResponse)\n\n // Reject the request Promise on mocked \"Response.error\" responses.\n if (\n isPropertyAccessible(mockedResponse, 'type') &&\n mockedResponse.type === 'error'\n ) {\n this.logger.info(\n 'received a network error response, rejecting the request promise...'\n )\n\n /**\n * Set the cause of the request promise rejection to the\n * network error Response instance. This different from Undici.\n * Undici will forward the \"response.error\" custom property\n * as the rejection reason but for \"Response.error()\" static method\n * \"response.error\" will equal to undefined, making \"cause\" an empty Error.\n * @see https://github.com/nodejs/undici/blob/83cb522ae0157a19d149d72c7d03d46e34510d0a/lib/fetch/response.js#L344\n */\n return Promise.reject(createNetworkError(mockedResponse))\n }\n\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 const responseClone = mockedResponse.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: true,\n request: interactiveRequest,\n requestId,\n })\n\n // Set the \"response.url\" property to equal the intercepted request URL.\n Object.defineProperty(mockedResponse, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n return mockedResponse\n }\n\n this.logger.info('no mocked response received!')\n\n return pureFetch(request).then((response) => {\n const responseClone = response.clone()\n this.logger.info('original fetch performed', responseClone)\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: false,\n request: interactiveRequest,\n requestId,\n })\n\n return response\n })\n }\n\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n enumerable: true,\n configurable: true,\n value: true,\n })\n\n this.subscriptions.push(() => {\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n value: undefined,\n })\n\n globalThis.fetch = pureFetch\n\n this.logger.info(\n 'restored native \"globalThis.fetch\"!',\n globalThis.fetch.name\n )\n })\n }\n}\n\nfunction createNetworkError(cause: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\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"],"mappings":"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","names":["mockedResponse"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/interceptors/fetch/index.ts","../../../../src/utils/canParseUrl.ts"],"sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { toInteractiveRequest } from '../../utils/toInteractiveRequest'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { isPropertyAccessible } from '../../utils/isPropertyAccessible'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\nimport { createServerErrorResponse } from '../../utils/responseUtils'\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 (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.fetch !== 'undefined'\n )\n }\n\n protected async setup() {\n const pureFetch = globalThis.fetch\n\n invariant(\n !(pureFetch as any)[IS_PATCHED_MODULE],\n 'Failed to patch the \"fetch\" module: already patched.'\n )\n\n globalThis.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.origin)\n : input\n\n const request = new Request(resolvedInput, init)\n\n this.logger.info('[%s] %s', request.method, request.url)\n\n const { interactiveRequest, requestController } =\n toInteractiveRequest(request)\n\n this.logger.info(\n 'emitting the \"request\" event for %d listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n this.emitter.once('request', ({ requestId: pendingRequestId }) => {\n if (pendingRequestId !== requestId) {\n return\n }\n\n if (requestController.responsePromise.state === 'pending') {\n requestController.responsePromise.resolve(undefined)\n }\n })\n\n this.logger.info('awaiting for the mocked response...')\n\n const signal = interactiveRequest.signal\n const requestAborted = new DeferredPromise()\n\n // Signal isn't always defined in react-native.\n if (signal) {\n signal.addEventListener(\n 'abort',\n () => {\n requestAborted.reject(signal.reason)\n },\n { once: true }\n )\n }\n\n const respondWith = (response: Response): 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 const responseClone = response.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: true,\n request: interactiveRequest,\n requestId,\n })\n\n // Set the \"response.url\" property to equal the intercepted request URL.\n Object.defineProperty(response, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n return response\n }\n\n const resolverResult = await until<unknown, Response | undefined>(\n async () => {\n const listenersFinished = emitAsync(this.emitter, 'request', {\n request: interactiveRequest,\n requestId,\n })\n\n await Promise.race([\n requestAborted,\n // Put the listeners invocation Promise in the same race condition\n // with the request abort Promise because otherwise awaiting the listeners\n // would always yield some response (or undefined).\n listenersFinished,\n requestController.responsePromise,\n ])\n\n this.logger.info('all request listeners have been resolved!')\n\n const mockedResponse = await requestController.responsePromise\n this.logger.info('event.respondWith called with:', mockedResponse)\n\n return mockedResponse\n }\n )\n\n if (requestAborted.state === 'rejected') {\n return Promise.reject(requestAborted.rejectionReason)\n }\n\n if (resolverResult.error) {\n // Treat thrown Responses as mocked responses.\n if (resolverResult.error instanceof Response) {\n return respondWith(resolverResult.error)\n }\n\n // Unhandled exceptions in the request listeners are\n // synonymous to unhandled exceptions on the server.\n // Those are represented as 500 error responses.\n return createServerErrorResponse(resolverResult.error)\n }\n\n const mockedResponse = resolverResult.data\n\n if (mockedResponse && !request.signal?.aborted) {\n this.logger.info('received mocked response:', mockedResponse)\n\n // Reject the request Promise on mocked \"Response.error\" responses.\n if (\n isPropertyAccessible(mockedResponse, 'type') &&\n mockedResponse.type === 'error'\n ) {\n this.logger.info(\n 'received a network error response, rejecting the request promise...'\n )\n\n /**\n * Set the cause of the request promise rejection to the\n * network error Response instance. This differs from Undici.\n * Undici will forward the \"response.error\" custom property\n * as the rejection reason but for \"Response.error()\" static method\n * \"response.error\" will equal to undefined, making \"cause\" an empty Error.\n * @see https://github.com/nodejs/undici/blob/83cb522ae0157a19d149d72c7d03d46e34510d0a/lib/fetch/response.js#L344\n */\n return Promise.reject(createNetworkError(mockedResponse))\n }\n\n return respondWith(mockedResponse)\n }\n\n this.logger.info('no mocked response received!')\n\n return pureFetch(request).then((response) => {\n const responseClone = response.clone()\n this.logger.info('original fetch performed', responseClone)\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: false,\n request: interactiveRequest,\n requestId,\n })\n\n return response\n })\n }\n\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n enumerable: true,\n configurable: true,\n value: true,\n })\n\n this.subscriptions.push(() => {\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n value: undefined,\n })\n\n globalThis.fetch = pureFetch\n\n this.logger.info(\n 'restored native \"globalThis.fetch\"!',\n globalThis.fetch.name\n )\n })\n }\n}\n\nfunction createNetworkError(cause: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\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"],"mappings":"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","names":["mockedResponse"]}
|
package/lib/node/presets/node.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkUXEUSYDYjs = require('../chunk-UXEUSYDY.js');
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunkM4JXH4RPjs = require('../chunk-M4JXH4RP.js');
|
|
7
7
|
require('../chunk-LK6DILFK.js');
|
|
8
|
-
require('../chunk-JSSEHRRB.js');
|
|
9
8
|
require('../chunk-Y6GRL6UD.js');
|
|
10
9
|
require('../chunk-FZJKKO5H.js');
|
|
11
10
|
require('../chunk-MQJ3JOOK.js');
|
|
12
|
-
require('../chunk-
|
|
11
|
+
require('../chunk-KRDNUBDZ.js');
|
|
13
12
|
|
|
14
13
|
// src/presets/node.ts
|
|
15
14
|
var node_default = [
|
|
16
|
-
new (0,
|
|
17
|
-
new (0,
|
|
15
|
+
new (0, _chunkUXEUSYDYjs.ClientRequestInterceptor)(),
|
|
16
|
+
new (0, _chunkM4JXH4RPjs.XMLHttpRequestInterceptor)()
|
|
18
17
|
];
|
|
19
18
|
|
|
20
19
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presets/node.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/presets/node.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAOA,IAAO,eAAQ;AAAA,EACb,IAAI,yBAAyB;AAAA,EAC7B,IAAI,0BAA0B;AAChC","sourcesContent":["import { ClientRequestInterceptor } from '../interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'\n\n/**\n * The default preset provisions the interception of requests\n * regardless of their type (http/https/XMLHttpRequest).\n */\nexport default [\n new ClientRequestInterceptor(),\n new XMLHttpRequestInterceptor(),\n] as const\n"]}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ClientRequestInterceptor
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-L576JLIX.mjs";
|
|
4
4
|
import {
|
|
5
5
|
XMLHttpRequestInterceptor
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-KGNKRQ7B.mjs";
|
|
7
7
|
import "../chunk-6HYIRFX2.mjs";
|
|
8
|
-
import "../chunk-IBYBTTYK.mjs";
|
|
9
8
|
import "../chunk-DERTLGL3.mjs";
|
|
10
9
|
import "../chunk-HAGW22AN.mjs";
|
|
11
10
|
import "../chunk-OUWBQF3Z.mjs";
|
|
12
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-DQ5DO3KN.mjs";
|
|
13
12
|
|
|
14
13
|
// src/presets/node.ts
|
|
15
14
|
var node_default = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presets/node.ts"],"sourcesContent":["import { ClientRequestInterceptor } from '../interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'\n\n/**\n * The default preset provisions the interception of requests\n * regardless of their type (http/https/XMLHttpRequest).\n */\nexport default [\n new ClientRequestInterceptor(),\n new XMLHttpRequestInterceptor(),\n] as const\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/presets/node.ts"],"sourcesContent":["import { ClientRequestInterceptor } from '../interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from '../interceptors/XMLHttpRequest'\n\n/**\n * The default preset provisions the interception of requests\n * regardless of their type (http/https/XMLHttpRequest).\n */\nexport default [\n new ClientRequestInterceptor(),\n new XMLHttpRequestInterceptor(),\n] as const\n"],"mappings":";;;;;;;;;;;;;AAOA,IAAO,eAAQ;AAAA,EACb,IAAI,yBAAyB;AAAA,EAC7B,IAAI,0BAA0B;AAChC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mswjs/interceptors",
|
|
3
3
|
"description": "Low-level HTTP/HTTPS/XHR/fetch request interception library.",
|
|
4
|
-
"version": "0.28.
|
|
4
|
+
"version": "0.28.3",
|
|
5
5
|
"main": "./lib/node/index.js",
|
|
6
6
|
"module": "./lib/node/index.mjs",
|
|
7
7
|
"types": "./lib/node/index.d.ts",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClientRequest, IncomingMessage } from 'node:http'
|
|
1
|
+
import { ClientRequest, IncomingMessage, STATUS_CODES } from 'node:http'
|
|
2
2
|
import type { Logger } from '@open-draft/logger'
|
|
3
3
|
import { until } from '@open-draft/until'
|
|
4
4
|
import { DeferredPromise } from '@open-draft/deferred-promise'
|
|
@@ -23,6 +23,7 @@ import { isPropertyAccessible } from '../../utils/isPropertyAccessible'
|
|
|
23
23
|
import { isNodeLikeError } from '../../utils/isNodeLikeError'
|
|
24
24
|
import { INTERNAL_REQUEST_ID_HEADER_NAME } from '../../Interceptor'
|
|
25
25
|
import { createRequestId } from '../../createRequestId'
|
|
26
|
+
import { createServerErrorResponse } from '../../utils/responseUtils'
|
|
26
27
|
|
|
27
28
|
export type Protocol = 'http' | 'https'
|
|
28
29
|
|
|
@@ -220,7 +221,7 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
220
221
|
|
|
221
222
|
// Execute the resolver Promise like a side-effect.
|
|
222
223
|
// Node.js 16 forces "ClientRequest.end" to be synchronous and return "this".
|
|
223
|
-
until(async () => {
|
|
224
|
+
until<unknown, Response | undefined>(async () => {
|
|
224
225
|
// Notify the interceptor about the request.
|
|
225
226
|
// This will call any "request" listeners the users have.
|
|
226
227
|
this.logger.info(
|
|
@@ -266,31 +267,24 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
266
267
|
resolverResult.error
|
|
267
268
|
)
|
|
268
269
|
|
|
270
|
+
// Treat thrown Responses as mocked responses.
|
|
271
|
+
if (resolverResult.error instanceof Response) {
|
|
272
|
+
this.respondWith(resolverResult.error)
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
269
276
|
// Allow throwing Node.js-like errors, like connection rejection errors.
|
|
270
277
|
// Treat them as request errors.
|
|
271
278
|
if (isNodeLikeError(resolverResult.error)) {
|
|
272
279
|
this.errorWith(resolverResult.error)
|
|
273
|
-
|
|
274
|
-
// Coerce unhandled exceptions in the "request" listeners
|
|
275
|
-
// as 500 responses.
|
|
276
|
-
this.respondWith(
|
|
277
|
-
new Response(
|
|
278
|
-
JSON.stringify({
|
|
279
|
-
name: resolverResult.error.name,
|
|
280
|
-
message: resolverResult.error.message,
|
|
281
|
-
stack: resolverResult.error.stack,
|
|
282
|
-
}),
|
|
283
|
-
{
|
|
284
|
-
status: 500,
|
|
285
|
-
statusText: 'Unhandled Exception',
|
|
286
|
-
headers: {
|
|
287
|
-
'Content-Type': 'application/json',
|
|
288
|
-
},
|
|
289
|
-
}
|
|
290
|
-
)
|
|
291
|
-
)
|
|
280
|
+
return this
|
|
292
281
|
}
|
|
293
282
|
|
|
283
|
+
// Unhandled exceptions in the request listeners are
|
|
284
|
+
// synonymous to unhandled exceptions on the server.
|
|
285
|
+
// Those are represented as 500 error responses.
|
|
286
|
+
this.respondWith(createServerErrorResponse(resolverResult.error))
|
|
287
|
+
|
|
294
288
|
return this
|
|
295
289
|
}
|
|
296
290
|
|
|
@@ -522,7 +516,7 @@ export class NodeClientRequest extends ClientRequest {
|
|
|
522
516
|
|
|
523
517
|
const { status, statusText, headers, body } = mockedResponse
|
|
524
518
|
this.response.statusCode = status
|
|
525
|
-
this.response.statusMessage = statusText
|
|
519
|
+
this.response.statusMessage = statusText || STATUS_CODES[status]
|
|
526
520
|
|
|
527
521
|
// Try extracting the raw headers from the headers instance.
|
|
528
522
|
// If not possible, fallback to the headers instance as-is.
|
|
@@ -54,7 +54,7 @@ it('forbids calling "respondWith" multiple times for the same request', async ()
|
|
|
54
54
|
|
|
55
55
|
const response = await responseReceived
|
|
56
56
|
expect(response.statusCode).toBe(200)
|
|
57
|
-
expect(response.statusMessage).toBe('')
|
|
57
|
+
expect(response.statusMessage).toBe('OK')
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
it('abort the request if the abort signal is emitted', async () => {
|
|
@@ -4,6 +4,7 @@ import { XMLHttpRequestEmitter } from '.'
|
|
|
4
4
|
import { toInteractiveRequest } from '../../utils/toInteractiveRequest'
|
|
5
5
|
import { emitAsync } from '../../utils/emitAsync'
|
|
6
6
|
import { XMLHttpRequestController } from './XMLHttpRequestController'
|
|
7
|
+
import { createServerErrorResponse } from '../../utils/responseUtils'
|
|
7
8
|
|
|
8
9
|
export interface XMLHttpRequestProxyOptions {
|
|
9
10
|
emitter: XMLHttpRequestEmitter
|
|
@@ -94,31 +95,18 @@ export function createXMLHttpRequestProxy({
|
|
|
94
95
|
resolverResult.error
|
|
95
96
|
)
|
|
96
97
|
|
|
97
|
-
// Treat
|
|
98
|
-
|
|
98
|
+
// Treat thrown Responses as mocked responses.
|
|
99
|
+
if (resolverResult.error instanceof Response) {
|
|
100
|
+
this.respondWith(resolverResult.error)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
// Unhandled exceptions in the request listeners are
|
|
104
|
+
// synonymous to unhandled exceptions on the server.
|
|
105
|
+
// Those are represented as 500 error responses.
|
|
99
106
|
xhrRequestController.respondWith(
|
|
100
|
-
|
|
101
|
-
JSON.stringify({
|
|
102
|
-
name: resolverResult.error.name,
|
|
103
|
-
message: resolverResult.error.message,
|
|
104
|
-
stack: resolverResult.error.stack,
|
|
105
|
-
}),
|
|
106
|
-
{
|
|
107
|
-
status: 500,
|
|
108
|
-
statusText: 'Unhandled Exception',
|
|
109
|
-
headers: {
|
|
110
|
-
'Content-Type': 'application/json',
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
)
|
|
107
|
+
createServerErrorResponse(resolverResult.error)
|
|
114
108
|
)
|
|
115
109
|
|
|
116
|
-
/**
|
|
117
|
-
* @todo Consider forwarding this error to the stderr as well
|
|
118
|
-
* since not all consumers are expecting to handle errors.
|
|
119
|
-
* If they don't, this error will be swallowed.
|
|
120
|
-
*/
|
|
121
|
-
// xhrRequestController.errorWith(resolverResult.error)
|
|
122
110
|
return
|
|
123
111
|
}
|
|
124
112
|
|
|
@@ -8,6 +8,7 @@ import { emitAsync } from '../../utils/emitAsync'
|
|
|
8
8
|
import { isPropertyAccessible } from '../../utils/isPropertyAccessible'
|
|
9
9
|
import { canParseUrl } from '../../utils/canParseUrl'
|
|
10
10
|
import { createRequestId } from '../../createRequestId'
|
|
11
|
+
import { createServerErrorResponse } from '../../utils/responseUtils'
|
|
11
12
|
|
|
12
13
|
export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
13
14
|
static symbol = Symbol('fetch')
|
|
@@ -85,51 +86,69 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
85
86
|
)
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
const
|
|
89
|
-
|
|
89
|
+
const respondWith = (response: Response): Response => {
|
|
90
|
+
// Clone the mocked response for the "response" event listener.
|
|
91
|
+
// This way, the listener can read the response and not lock its body
|
|
92
|
+
// for the actual fetch consumer.
|
|
93
|
+
const responseClone = response.clone()
|
|
94
|
+
|
|
95
|
+
this.emitter.emit('response', {
|
|
96
|
+
response: responseClone,
|
|
97
|
+
isMockedResponse: true,
|
|
90
98
|
request: interactiveRequest,
|
|
91
99
|
requestId,
|
|
92
100
|
})
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
// Set the "response.url" property to equal the intercepted request URL.
|
|
103
|
+
Object.defineProperty(response, 'url', {
|
|
104
|
+
writable: false,
|
|
105
|
+
enumerable: true,
|
|
106
|
+
configurable: false,
|
|
107
|
+
value: request.url,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return response
|
|
111
|
+
}
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
const resolverResult = await until<unknown, Response | undefined>(
|
|
114
|
+
async () => {
|
|
115
|
+
const listenersFinished = emitAsync(this.emitter, 'request', {
|
|
116
|
+
request: interactiveRequest,
|
|
117
|
+
requestId,
|
|
118
|
+
})
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
await Promise.race([
|
|
121
|
+
requestAborted,
|
|
122
|
+
// Put the listeners invocation Promise in the same race condition
|
|
123
|
+
// with the request abort Promise because otherwise awaiting the listeners
|
|
124
|
+
// would always yield some response (or undefined).
|
|
125
|
+
listenersFinished,
|
|
126
|
+
requestController.responsePromise,
|
|
127
|
+
])
|
|
107
128
|
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
this.logger.info('all request listeners have been resolved!')
|
|
130
|
+
|
|
131
|
+
const mockedResponse = await requestController.responsePromise
|
|
132
|
+
this.logger.info('event.respondWith called with:', mockedResponse)
|
|
133
|
+
|
|
134
|
+
return mockedResponse
|
|
135
|
+
}
|
|
136
|
+
)
|
|
110
137
|
|
|
111
138
|
if (requestAborted.state === 'rejected') {
|
|
112
139
|
return Promise.reject(requestAborted.rejectionReason)
|
|
113
140
|
}
|
|
114
141
|
|
|
115
142
|
if (resolverResult.error) {
|
|
116
|
-
// Treat
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{
|
|
126
|
-
status: 500,
|
|
127
|
-
statusText: 'Unhandled Exception',
|
|
128
|
-
headers: {
|
|
129
|
-
'Content-Type': 'application/json',
|
|
130
|
-
},
|
|
131
|
-
}
|
|
132
|
-
)
|
|
143
|
+
// Treat thrown Responses as mocked responses.
|
|
144
|
+
if (resolverResult.error instanceof Response) {
|
|
145
|
+
return respondWith(resolverResult.error)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Unhandled exceptions in the request listeners are
|
|
149
|
+
// synonymous to unhandled exceptions on the server.
|
|
150
|
+
// Those are represented as 500 error responses.
|
|
151
|
+
return createServerErrorResponse(resolverResult.error)
|
|
133
152
|
}
|
|
134
153
|
|
|
135
154
|
const mockedResponse = resolverResult.data
|
|
@@ -148,7 +167,7 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
148
167
|
|
|
149
168
|
/**
|
|
150
169
|
* Set the cause of the request promise rejection to the
|
|
151
|
-
* network error Response instance. This
|
|
170
|
+
* network error Response instance. This differs from Undici.
|
|
152
171
|
* Undici will forward the "response.error" custom property
|
|
153
172
|
* as the rejection reason but for "Response.error()" static method
|
|
154
173
|
* "response.error" will equal to undefined, making "cause" an empty Error.
|
|
@@ -157,27 +176,7 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
157
176
|
return Promise.reject(createNetworkError(mockedResponse))
|
|
158
177
|
}
|
|
159
178
|
|
|
160
|
-
|
|
161
|
-
// This way, the listener can read the response and not lock its body
|
|
162
|
-
// for the actual fetch consumer.
|
|
163
|
-
const responseClone = mockedResponse.clone()
|
|
164
|
-
|
|
165
|
-
this.emitter.emit('response', {
|
|
166
|
-
response: responseClone,
|
|
167
|
-
isMockedResponse: true,
|
|
168
|
-
request: interactiveRequest,
|
|
169
|
-
requestId,
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
// Set the "response.url" property to equal the intercepted request URL.
|
|
173
|
-
Object.defineProperty(mockedResponse, 'url', {
|
|
174
|
-
writable: false,
|
|
175
|
-
enumerable: true,
|
|
176
|
-
configurable: false,
|
|
177
|
-
value: request.url,
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
return mockedResponse
|
|
179
|
+
return respondWith(mockedResponse)
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
this.logger.info('no mocked response received!')
|
|
@@ -13,3 +13,27 @@ export const RESPONSE_STATUS_CODES_WITHOUT_BODY = new Set([
|
|
|
13
13
|
export function isResponseWithoutBody(status: number): boolean {
|
|
14
14
|
return RESPONSE_STATUS_CODES_WITHOUT_BODY.has(status)
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a generic 500 Unhandled Exception response.
|
|
19
|
+
*/
|
|
20
|
+
export function createServerErrorResponse(body: unknown): Response {
|
|
21
|
+
return new Response(
|
|
22
|
+
JSON.stringify(
|
|
23
|
+
body instanceof Error
|
|
24
|
+
? {
|
|
25
|
+
name: body.name,
|
|
26
|
+
message: body.message,
|
|
27
|
+
stack: body.stack,
|
|
28
|
+
}
|
|
29
|
+
: body
|
|
30
|
+
),
|
|
31
|
+
{
|
|
32
|
+
status: 500,
|
|
33
|
+
statusText: 'Unhandled Exception',
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/interceptors/fetch/index.ts","../../src/utils/isPropertyAccessible.ts","../../src/utils/canParseUrl.ts"],"names":["mockedResponse"],"mappings":"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","sourcesContent":["import { invariant } from 'outvariant'\nimport { DeferredPromise } from '@open-draft/deferred-promise'\nimport { until } from '@open-draft/until'\nimport { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'\nimport { Interceptor } from '../../Interceptor'\nimport { toInteractiveRequest } from '../../utils/toInteractiveRequest'\nimport { emitAsync } from '../../utils/emitAsync'\nimport { isPropertyAccessible } from '../../utils/isPropertyAccessible'\nimport { canParseUrl } from '../../utils/canParseUrl'\nimport { createRequestId } from '../../createRequestId'\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 (\n typeof globalThis !== 'undefined' &&\n typeof globalThis.fetch !== 'undefined'\n )\n }\n\n protected async setup() {\n const pureFetch = globalThis.fetch\n\n invariant(\n !(pureFetch as any)[IS_PATCHED_MODULE],\n 'Failed to patch the \"fetch\" module: already patched.'\n )\n\n globalThis.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.origin)\n : input\n\n const request = new Request(resolvedInput, init)\n\n this.logger.info('[%s] %s', request.method, request.url)\n\n const { interactiveRequest, requestController } =\n toInteractiveRequest(request)\n\n this.logger.info(\n 'emitting the \"request\" event for %d listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n this.emitter.once('request', ({ requestId: pendingRequestId }) => {\n if (pendingRequestId !== requestId) {\n return\n }\n\n if (requestController.responsePromise.state === 'pending') {\n requestController.responsePromise.resolve(undefined)\n }\n })\n\n this.logger.info('awaiting for the mocked response...')\n\n const signal = interactiveRequest.signal\n const requestAborted = new DeferredPromise()\n\n // Signal isn't always defined in react-native.\n if (signal) {\n signal.addEventListener(\n 'abort',\n () => {\n requestAborted.reject(signal.reason)\n },\n { once: true }\n )\n }\n\n const resolverResult = await until(async () => {\n const listenersFinished = emitAsync(this.emitter, 'request', {\n request: interactiveRequest,\n requestId,\n })\n\n await Promise.race([\n requestAborted,\n // Put the listeners invocation Promise in the same race condition\n // with the request abort Promise because otherwise awaiting the listeners\n // would always yield some response (or undefined).\n listenersFinished,\n requestController.responsePromise,\n ])\n\n this.logger.info('all request listeners have been resolved!')\n\n const mockedResponse = await requestController.responsePromise\n this.logger.info('event.respondWith called with:', mockedResponse)\n\n return mockedResponse\n })\n\n if (requestAborted.state === 'rejected') {\n return Promise.reject(requestAborted.rejectionReason)\n }\n\n if (resolverResult.error) {\n // Treat unhandled exceptions from the \"request\" listeners\n // as 500 errors from the server. Fetch API doesn't respect\n // Node.js internal errors so no special treatment for those.\n return new Response(\n JSON.stringify({\n name: resolverResult.error.name,\n message: resolverResult.error.message,\n stack: resolverResult.error.stack,\n }),\n {\n status: 500,\n statusText: 'Unhandled Exception',\n headers: {\n 'Content-Type': 'application/json',\n },\n }\n )\n }\n\n const mockedResponse = resolverResult.data\n\n if (mockedResponse && !request.signal?.aborted) {\n this.logger.info('received mocked response:', mockedResponse)\n\n // Reject the request Promise on mocked \"Response.error\" responses.\n if (\n isPropertyAccessible(mockedResponse, 'type') &&\n mockedResponse.type === 'error'\n ) {\n this.logger.info(\n 'received a network error response, rejecting the request promise...'\n )\n\n /**\n * Set the cause of the request promise rejection to the\n * network error Response instance. This different from Undici.\n * Undici will forward the \"response.error\" custom property\n * as the rejection reason but for \"Response.error()\" static method\n * \"response.error\" will equal to undefined, making \"cause\" an empty Error.\n * @see https://github.com/nodejs/undici/blob/83cb522ae0157a19d149d72c7d03d46e34510d0a/lib/fetch/response.js#L344\n */\n return Promise.reject(createNetworkError(mockedResponse))\n }\n\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 const responseClone = mockedResponse.clone()\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: true,\n request: interactiveRequest,\n requestId,\n })\n\n // Set the \"response.url\" property to equal the intercepted request URL.\n Object.defineProperty(mockedResponse, 'url', {\n writable: false,\n enumerable: true,\n configurable: false,\n value: request.url,\n })\n\n return mockedResponse\n }\n\n this.logger.info('no mocked response received!')\n\n return pureFetch(request).then((response) => {\n const responseClone = response.clone()\n this.logger.info('original fetch performed', responseClone)\n\n this.emitter.emit('response', {\n response: responseClone,\n isMockedResponse: false,\n request: interactiveRequest,\n requestId,\n })\n\n return response\n })\n }\n\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n enumerable: true,\n configurable: true,\n value: true,\n })\n\n this.subscriptions.push(() => {\n Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {\n value: undefined,\n })\n\n globalThis.fetch = pureFetch\n\n this.logger.info(\n 'restored native \"globalThis.fetch\"!',\n globalThis.fetch.name\n )\n })\n }\n}\n\nfunction createNetworkError(cause: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\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","/**\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"]}
|