@mswjs/interceptors 0.39.8 → 0.40.0
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-E3CCOBRX.js → chunk-2MCNQOY3.js} +54 -49
- package/lib/browser/chunk-2MCNQOY3.js.map +1 -0
- package/lib/browser/chunk-57RIRQUY.js +218 -0
- package/lib/browser/chunk-57RIRQUY.js.map +1 -0
- package/lib/browser/chunk-FW45TRCB.js +178 -0
- package/lib/browser/chunk-FW45TRCB.js.map +1 -0
- package/lib/browser/{chunk-TIPR373R.js → chunk-JQ2S7G56.js} +19 -3
- package/lib/browser/chunk-JQ2S7G56.js.map +1 -0
- package/lib/browser/{chunk-3RXCRGL2.mjs → chunk-LIKZF2VU.mjs} +102 -1
- package/lib/browser/chunk-LIKZF2VU.mjs.map +1 -0
- package/lib/browser/{chunk-H74PGQ4Y.js → chunk-MNT2FUCH.js} +58 -53
- package/lib/browser/chunk-MNT2FUCH.js.map +1 -0
- package/lib/browser/chunk-VOUOVDAW.mjs +178 -0
- package/lib/browser/chunk-VOUOVDAW.mjs.map +1 -0
- package/lib/browser/{chunk-E7UVBHVO.mjs → chunk-WADP6VHN.mjs} +48 -43
- package/lib/browser/chunk-WADP6VHN.mjs.map +1 -0
- package/lib/browser/{chunk-Q7K2XAEP.mjs → chunk-WOWPV4GR.mjs} +50 -45
- package/lib/browser/chunk-WOWPV4GR.mjs.map +1 -0
- package/lib/browser/{chunk-QED3Q6Z2.mjs → chunk-Z5TSB3T6.mjs} +17 -1
- package/lib/browser/{chunk-QED3Q6Z2.mjs.map → chunk-Z5TSB3T6.mjs.map} +1 -1
- package/lib/browser/{glossary-7152281e.d.ts → glossary-f7ee1c9d.d.ts} +22 -17
- package/lib/browser/index.d.ts +1 -2
- package/lib/browser/index.js +6 -4
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/index.mjs +4 -2
- package/lib/browser/index.mjs.map +1 -1
- package/lib/browser/interceptors/WebSocket/index.js +3 -3
- package/lib/browser/interceptors/WebSocket/index.mjs +1 -1
- package/lib/browser/interceptors/XMLHttpRequest/index.d.ts +1 -2
- package/lib/browser/interceptors/XMLHttpRequest/index.js +5 -5
- package/lib/browser/interceptors/XMLHttpRequest/index.mjs +4 -4
- package/lib/browser/interceptors/fetch/index.d.ts +1 -2
- package/lib/browser/interceptors/fetch/index.js +5 -5
- package/lib/browser/interceptors/fetch/index.mjs +4 -4
- package/lib/browser/presets/browser.d.ts +1 -2
- package/lib/browser/presets/browser.js +7 -7
- package/lib/browser/presets/browser.mjs +5 -5
- package/lib/node/{BatchInterceptor-5b72232f.d.ts → BatchInterceptor-cb9a2eee.d.ts} +1 -1
- package/lib/node/{Interceptor-bc5a9d8e.d.ts → Interceptor-dc0a39b5.d.ts} +22 -16
- package/lib/node/RemoteHttpInterceptor.d.ts +2 -3
- package/lib/node/RemoteHttpInterceptor.js +31 -27
- package/lib/node/RemoteHttpInterceptor.js.map +1 -1
- package/lib/node/RemoteHttpInterceptor.mjs +28 -24
- package/lib/node/RemoteHttpInterceptor.mjs.map +1 -1
- package/lib/node/{chunk-EKNRB5ZS.mjs → chunk-5UGIB6OX.mjs} +40 -29
- package/lib/node/chunk-5UGIB6OX.mjs.map +1 -0
- package/lib/node/{chunk-4NEYTVWD.mjs → chunk-5V3SIIW2.mjs} +48 -43
- package/lib/node/chunk-5V3SIIW2.mjs.map +1 -0
- package/lib/node/{chunk-VV2LUF5K.js → chunk-6B3ZQOO2.js} +51 -46
- package/lib/node/chunk-6B3ZQOO2.js.map +1 -0
- package/lib/node/chunk-7Q53NNPV.js +189 -0
- package/lib/node/chunk-7Q53NNPV.js.map +1 -0
- package/lib/node/{chunk-A7U44ARP.js → chunk-DOWWQYXZ.js} +104 -3
- package/lib/node/chunk-DOWWQYXZ.js.map +1 -0
- package/lib/node/{chunk-Z5LWCBZS.js → chunk-FRZQJNBO.js} +56 -51
- package/lib/node/chunk-FRZQJNBO.js.map +1 -0
- package/lib/node/{chunk-TJDMZZXE.mjs → chunk-GKN5RBVR.mjs} +2 -2
- package/lib/node/{chunk-R6JVCM7X.js → chunk-J5MULIHT.js} +3 -3
- package/lib/node/{chunk-IHJSPMYM.mjs → chunk-JXGB54LE.mjs} +102 -1
- package/lib/node/chunk-JXGB54LE.mjs.map +1 -0
- package/lib/node/{chunk-3CNGDJFB.mjs → chunk-OFW5L5ET.mjs} +50 -45
- package/lib/node/chunk-OFW5L5ET.mjs.map +1 -0
- package/lib/node/{chunk-A7Q4RTDJ.mjs → chunk-R6T7CL5E.mjs} +55 -115
- package/lib/node/chunk-R6T7CL5E.mjs.map +1 -0
- package/lib/node/{chunk-RC2XPCC4.mjs → chunk-SQ6RHTJR.mjs} +2 -2
- package/lib/node/chunk-SRMAQGPM.js +30 -0
- package/lib/node/chunk-SRMAQGPM.js.map +1 -0
- package/lib/node/{chunk-4YBV77DG.js → chunk-T3TW4P64.js} +3 -3
- package/lib/node/{chunk-N4ZZFE24.js → chunk-VYO5XDY2.js} +56 -45
- package/lib/node/chunk-VYO5XDY2.js.map +1 -0
- package/lib/node/chunk-YWNGXXUQ.mjs +30 -0
- package/lib/node/{chunk-3GJB4JDF.mjs.map → chunk-YWNGXXUQ.mjs.map} +1 -1
- package/lib/node/index.d.ts +2 -3
- package/lib/node/index.js +6 -4
- package/lib/node/index.js.map +1 -1
- package/lib/node/index.mjs +5 -3
- package/lib/node/index.mjs.map +1 -1
- package/lib/node/interceptors/ClientRequest/index.d.ts +1 -2
- package/lib/node/interceptors/ClientRequest/index.js +6 -6
- package/lib/node/interceptors/ClientRequest/index.mjs +5 -5
- package/lib/node/interceptors/XMLHttpRequest/index.d.ts +1 -2
- package/lib/node/interceptors/XMLHttpRequest/index.js +5 -5
- package/lib/node/interceptors/XMLHttpRequest/index.mjs +4 -4
- package/lib/node/interceptors/fetch/index.d.ts +1 -2
- package/lib/node/interceptors/fetch/index.js +5 -5
- package/lib/node/interceptors/fetch/index.mjs +4 -4
- package/lib/node/presets/node.d.ts +1 -2
- package/lib/node/presets/node.js +10 -10
- package/lib/node/presets/node.mjs +7 -7
- package/lib/node/utils/node/index.js +3 -3
- package/lib/node/utils/node/index.mjs +2 -2
- package/package.json +2 -1
- package/src/RemoteHttpInterceptor.ts +18 -13
- package/src/RequestController.test.ts +78 -31
- package/src/RequestController.ts +63 -39
- package/src/index.ts +4 -0
- package/src/interceptors/ClientRequest/MockHttpSocket.ts +24 -3
- package/src/interceptors/ClientRequest/index.ts +14 -18
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +45 -35
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +24 -21
- package/src/interceptors/fetch/index.ts +61 -50
- package/src/utils/handleRequest.ts +65 -95
- package/lib/browser/chunk-2QICSCCS.js +0 -238
- package/lib/browser/chunk-2QICSCCS.js.map +0 -1
- package/lib/browser/chunk-3RXCRGL2.mjs.map +0 -1
- package/lib/browser/chunk-E3CCOBRX.js.map +0 -1
- package/lib/browser/chunk-E7UVBHVO.mjs.map +0 -1
- package/lib/browser/chunk-H74PGQ4Y.js.map +0 -1
- package/lib/browser/chunk-PTTUYYVR.mjs +0 -238
- package/lib/browser/chunk-PTTUYYVR.mjs.map +0 -1
- package/lib/browser/chunk-Q7K2XAEP.mjs.map +0 -1
- package/lib/browser/chunk-T7TBRNJZ.js +0 -117
- package/lib/browser/chunk-T7TBRNJZ.js.map +0 -1
- package/lib/browser/chunk-TIPR373R.js.map +0 -1
- package/lib/node/chunk-3CNGDJFB.mjs.map +0 -1
- package/lib/node/chunk-3GJB4JDF.mjs +0 -14
- package/lib/node/chunk-4NEYTVWD.mjs.map +0 -1
- package/lib/node/chunk-72ZIHMEB.js +0 -249
- package/lib/node/chunk-72ZIHMEB.js.map +0 -1
- package/lib/node/chunk-A7Q4RTDJ.mjs.map +0 -1
- package/lib/node/chunk-A7U44ARP.js.map +0 -1
- package/lib/node/chunk-EKNRB5ZS.mjs.map +0 -1
- package/lib/node/chunk-IHJSPMYM.mjs.map +0 -1
- package/lib/node/chunk-N4ZZFE24.js.map +0 -1
- package/lib/node/chunk-SMXZPJEA.js +0 -14
- package/lib/node/chunk-SMXZPJEA.js.map +0 -1
- package/lib/node/chunk-VV2LUF5K.js.map +0 -1
- package/lib/node/chunk-Z5LWCBZS.js.map +0 -1
- package/src/utils/RequestController.ts +0 -21
- /package/lib/node/{chunk-TJDMZZXE.mjs.map → chunk-GKN5RBVR.mjs.map} +0 -0
- /package/lib/node/{chunk-R6JVCM7X.js.map → chunk-J5MULIHT.js.map} +0 -0
- /package/lib/node/{chunk-RC2XPCC4.mjs.map → chunk-SQ6RHTJR.mjs.map} +0 -0
- /package/lib/node/{chunk-4YBV77DG.js.map → chunk-T3TW4P64.js.map} +0 -0
|
@@ -153,30 +153,26 @@ export class ClientRequestInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
153
153
|
request,
|
|
154
154
|
socket,
|
|
155
155
|
}) => {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const isRequestHandled = await handleRequest({
|
|
160
|
-
request,
|
|
161
|
-
requestId,
|
|
162
|
-
controller,
|
|
163
|
-
emitter: this.emitter,
|
|
164
|
-
onResponse: (response) => {
|
|
165
|
-
socket.respondWith(response)
|
|
156
|
+
const controller = new RequestController(request, {
|
|
157
|
+
passthrough() {
|
|
158
|
+
socket.passthrough()
|
|
166
159
|
},
|
|
167
|
-
|
|
168
|
-
socket.respondWith(response)
|
|
160
|
+
async respondWith(response) {
|
|
161
|
+
await socket.respondWith(response)
|
|
169
162
|
},
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
socket.errorWith(
|
|
163
|
+
errorWith(reason) {
|
|
164
|
+
if (reason instanceof Error) {
|
|
165
|
+
socket.errorWith(reason)
|
|
173
166
|
}
|
|
174
167
|
},
|
|
175
168
|
})
|
|
176
169
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
170
|
+
await handleRequest({
|
|
171
|
+
request,
|
|
172
|
+
requestId: Reflect.get(request, kRequestId),
|
|
173
|
+
controller,
|
|
174
|
+
emitter: this.emitter,
|
|
175
|
+
})
|
|
180
176
|
}
|
|
181
177
|
|
|
182
178
|
public onResponse: MockHttpSocketResponseCallback = async ({
|
|
@@ -57,7 +57,10 @@ export class XMLHttpRequestController {
|
|
|
57
57
|
Array<Function>
|
|
58
58
|
>
|
|
59
59
|
|
|
60
|
-
constructor(
|
|
60
|
+
constructor(
|
|
61
|
+
readonly initialRequest: XMLHttpRequest,
|
|
62
|
+
public logger: Logger
|
|
63
|
+
) {
|
|
61
64
|
this[kIsRequestHandled] = false
|
|
62
65
|
|
|
63
66
|
this.events = new Map()
|
|
@@ -111,7 +114,7 @@ export class XMLHttpRequestController {
|
|
|
111
114
|
case 'addEventListener': {
|
|
112
115
|
const [eventName, listener] = args as [
|
|
113
116
|
keyof XMLHttpRequestEventTargetEventMap,
|
|
114
|
-
Function
|
|
117
|
+
Function,
|
|
115
118
|
]
|
|
116
119
|
|
|
117
120
|
this.registerEvent(eventName, listener)
|
|
@@ -131,7 +134,7 @@ export class XMLHttpRequestController {
|
|
|
131
134
|
|
|
132
135
|
case 'send': {
|
|
133
136
|
const [body] = args as [
|
|
134
|
-
body?: XMLHttpRequestBodyInit | Document | null
|
|
137
|
+
body?: XMLHttpRequestBodyInit | Document | null,
|
|
135
138
|
]
|
|
136
139
|
|
|
137
140
|
this.request.addEventListener('load', () => {
|
|
@@ -166,38 +169,44 @@ export class XMLHttpRequestController {
|
|
|
166
169
|
const fetchRequest = this.toFetchApiRequest(requestBody)
|
|
167
170
|
this[kFetchRequest] = fetchRequest.clone()
|
|
168
171
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
* to process it once again.
|
|
187
|
-
*
|
|
188
|
-
* For instance, XMLHttpRequest is often implemented via "http.ClientRequest"
|
|
189
|
-
* and we don't want for both XHR and ClientRequest interceptors to
|
|
190
|
-
* handle the same request at the same time (e.g. emit the "response" event twice).
|
|
191
|
-
*/
|
|
192
|
-
if (IS_NODE) {
|
|
193
|
-
this.request.setRequestHeader(
|
|
194
|
-
INTERNAL_REQUEST_ID_HEADER_NAME,
|
|
195
|
-
this.requestId!
|
|
172
|
+
/**
|
|
173
|
+
* @note Start request handling on the next tick so that the user
|
|
174
|
+
* could add event listeners for "loadend" before the interceptor fires it.
|
|
175
|
+
*/
|
|
176
|
+
queueMicrotask(() => {
|
|
177
|
+
const onceRequestSettled =
|
|
178
|
+
this.onRequest?.call(this, {
|
|
179
|
+
request: fetchRequest,
|
|
180
|
+
requestId: this.requestId!,
|
|
181
|
+
}) || Promise.resolve()
|
|
182
|
+
|
|
183
|
+
onceRequestSettled.finally(() => {
|
|
184
|
+
// If the consumer didn't handle the request (called `.respondWith()`) perform it as-is.
|
|
185
|
+
if (!this[kIsRequestHandled]) {
|
|
186
|
+
this.logger.info(
|
|
187
|
+
'request callback settled but request has not been handled (readystate %d), performing as-is...',
|
|
188
|
+
this.request.readyState
|
|
196
189
|
)
|
|
197
|
-
}
|
|
198
190
|
|
|
199
|
-
|
|
200
|
-
|
|
191
|
+
/**
|
|
192
|
+
* @note Set the intercepted request ID on the original request in Node.js
|
|
193
|
+
* so that if it triggers any other interceptors, they don't attempt
|
|
194
|
+
* to process it once again.
|
|
195
|
+
*
|
|
196
|
+
* For instance, XMLHttpRequest is often implemented via "http.ClientRequest"
|
|
197
|
+
* and we don't want for both XHR and ClientRequest interceptors to
|
|
198
|
+
* handle the same request at the same time (e.g. emit the "response" event twice).
|
|
199
|
+
*/
|
|
200
|
+
if (IS_NODE) {
|
|
201
|
+
this.request.setRequestHeader(
|
|
202
|
+
INTERNAL_REQUEST_ID_HEADER_NAME,
|
|
203
|
+
this.requestId!
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return invoke()
|
|
208
|
+
}
|
|
209
|
+
})
|
|
201
210
|
})
|
|
202
211
|
|
|
203
212
|
break
|
|
@@ -241,7 +250,7 @@ export class XMLHttpRequestController {
|
|
|
241
250
|
case 'addEventListener': {
|
|
242
251
|
const [eventName, listener] = args as [
|
|
243
252
|
keyof XMLHttpRequestEventTargetEventMap,
|
|
244
|
-
Function
|
|
253
|
+
Function,
|
|
245
254
|
]
|
|
246
255
|
this.registerUploadEvent(eventName, listener)
|
|
247
256
|
this.logger.info('upload.addEventListener', eventName, listener)
|
|
@@ -312,6 +321,7 @@ export class XMLHttpRequestController {
|
|
|
312
321
|
loaded: totalRequestBodyLength,
|
|
313
322
|
total: totalRequestBodyLength,
|
|
314
323
|
})
|
|
324
|
+
|
|
315
325
|
this.trigger('loadend', this.request.upload, {
|
|
316
326
|
loaded: totalRequestBodyLength,
|
|
317
327
|
total: totalRequestBodyLength,
|
|
@@ -614,7 +624,7 @@ export class XMLHttpRequestController {
|
|
|
614
624
|
private trigger<
|
|
615
625
|
EventName extends keyof (XMLHttpRequestEventTargetEventMap & {
|
|
616
626
|
readystatechange: ProgressEvent<XMLHttpRequestEventTarget>
|
|
617
|
-
})
|
|
627
|
+
}),
|
|
618
628
|
>(
|
|
619
629
|
eventName: EventName,
|
|
620
630
|
target: XMLHttpRequest | XMLHttpRequestUpload,
|
|
@@ -3,6 +3,7 @@ import { XMLHttpRequestEmitter } from '.'
|
|
|
3
3
|
import { RequestController } from '../../RequestController'
|
|
4
4
|
import { XMLHttpRequestController } from './XMLHttpRequestController'
|
|
5
5
|
import { handleRequest } from '../../utils/handleRequest'
|
|
6
|
+
import { isResponseError } from '../../utils/responseUtils'
|
|
6
7
|
|
|
7
8
|
export interface XMLHttpRequestProxyOptions {
|
|
8
9
|
emitter: XMLHttpRequestEmitter
|
|
@@ -52,7 +53,28 @@ export function createXMLHttpRequestProxy({
|
|
|
52
53
|
)
|
|
53
54
|
|
|
54
55
|
xhrRequestController.onRequest = async function ({ request, requestId }) {
|
|
55
|
-
const controller = new RequestController(request
|
|
56
|
+
const controller = new RequestController(request, {
|
|
57
|
+
passthrough: () => {
|
|
58
|
+
this.logger.info(
|
|
59
|
+
'no mocked response received, performing request as-is...'
|
|
60
|
+
)
|
|
61
|
+
},
|
|
62
|
+
respondWith: async (response) => {
|
|
63
|
+
if (isResponseError(response)) {
|
|
64
|
+
this.errorWith(new TypeError('Network error'))
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await this.respondWith(response)
|
|
69
|
+
},
|
|
70
|
+
errorWith: (reason) => {
|
|
71
|
+
this.logger.info('request errored!', { error: reason })
|
|
72
|
+
|
|
73
|
+
if (reason instanceof Error) {
|
|
74
|
+
this.errorWith(reason)
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
})
|
|
56
78
|
|
|
57
79
|
this.logger.info('awaiting mocked response...')
|
|
58
80
|
|
|
@@ -61,31 +83,12 @@ export function createXMLHttpRequestProxy({
|
|
|
61
83
|
emitter.listenerCount('request')
|
|
62
84
|
)
|
|
63
85
|
|
|
64
|
-
|
|
86
|
+
await handleRequest({
|
|
65
87
|
request,
|
|
66
88
|
requestId,
|
|
67
89
|
controller,
|
|
68
90
|
emitter,
|
|
69
|
-
onResponse: async (response) => {
|
|
70
|
-
await this.respondWith(response)
|
|
71
|
-
},
|
|
72
|
-
onRequestError: () => {
|
|
73
|
-
this.errorWith(new TypeError('Network error'))
|
|
74
|
-
},
|
|
75
|
-
onError: (error) => {
|
|
76
|
-
this.logger.info('request errored!', { error })
|
|
77
|
-
|
|
78
|
-
if (error instanceof Error) {
|
|
79
|
-
this.errorWith(error)
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
91
|
})
|
|
83
|
-
|
|
84
|
-
if (!isRequestHandled) {
|
|
85
|
-
this.logger.info(
|
|
86
|
-
'no mocked response received, performing request as-is...'
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
xhrRequestController.onResponse = async function ({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { invariant } from 'outvariant'
|
|
2
|
+
import { until } from '@open-draft/until'
|
|
2
3
|
import { DeferredPromise } from '@open-draft/deferred-promise'
|
|
3
4
|
import { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'
|
|
4
5
|
import { Interceptor } from '../../Interceptor'
|
|
@@ -13,6 +14,7 @@ import { decompressResponse } from './utils/decompression'
|
|
|
13
14
|
import { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'
|
|
14
15
|
import { FetchResponse } from '../../utils/fetchUtils'
|
|
15
16
|
import { setRawRequest } from '../../getRawRequest'
|
|
17
|
+
import { isResponseError } from '../../utils/responseUtils'
|
|
16
18
|
|
|
17
19
|
export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
18
20
|
static symbol = Symbol('fetch')
|
|
@@ -59,22 +61,54 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
const responsePromise = new DeferredPromise<Response>()
|
|
62
|
-
const controller = new RequestController(request)
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
const controller = new RequestController(request, {
|
|
66
|
+
passthrough: async () => {
|
|
67
|
+
this.logger.info('request has not been handled, passthrough...')
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
/**
|
|
70
|
+
* @note Clone the request instance right before performing it.
|
|
71
|
+
* This preserves any modifications made to the intercepted request
|
|
72
|
+
* in the "request" listener. This also allows the user to read the
|
|
73
|
+
* request body in the "response" listener (otherwise "unusable").
|
|
74
|
+
*/
|
|
75
|
+
const requestCloneForResponseEvent = request.clone()
|
|
76
|
+
|
|
77
|
+
// Perform the intercepted request as-is.
|
|
78
|
+
const { error: responseError, data: originalResponse } = await until(
|
|
79
|
+
() => pureFetch(request)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if (responseError) {
|
|
83
|
+
return responsePromise.reject(responseError)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.logger.info('original fetch performed', originalResponse)
|
|
87
|
+
|
|
88
|
+
if (this.emitter.listenerCount('response') > 0) {
|
|
89
|
+
this.logger.info('emitting the "response" event...')
|
|
90
|
+
|
|
91
|
+
const responseClone = originalResponse.clone()
|
|
92
|
+
await emitAsync(this.emitter, 'response', {
|
|
93
|
+
response: responseClone,
|
|
94
|
+
isMockedResponse: false,
|
|
95
|
+
request: requestCloneForResponseEvent,
|
|
96
|
+
requestId,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Resolve the response promise with the original response
|
|
101
|
+
// since the `fetch()` return this internal promise.
|
|
102
|
+
responsePromise.resolve(originalResponse)
|
|
103
|
+
},
|
|
104
|
+
respondWith: async (rawResponse) => {
|
|
105
|
+
// Handle mocked `Response.error()` (i.e. request errors).
|
|
106
|
+
if (isResponseError(rawResponse)) {
|
|
107
|
+
this.logger.info('request has errored!', { response: rawResponse })
|
|
108
|
+
responsePromise.reject(createNetworkError(rawResponse))
|
|
109
|
+
return
|
|
110
|
+
}
|
|
71
111
|
|
|
72
|
-
const isRequestHandled = await handleRequest({
|
|
73
|
-
request,
|
|
74
|
-
requestId,
|
|
75
|
-
emitter: this.emitter,
|
|
76
|
-
controller,
|
|
77
|
-
onResponse: async (rawResponse) => {
|
|
78
112
|
this.logger.info('received mocked response!', {
|
|
79
113
|
rawResponse,
|
|
80
114
|
})
|
|
@@ -134,51 +168,28 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
134
168
|
|
|
135
169
|
responsePromise.resolve(response)
|
|
136
170
|
},
|
|
137
|
-
|
|
138
|
-
this.logger.info('request has
|
|
139
|
-
responsePromise.reject(
|
|
140
|
-
},
|
|
141
|
-
onError: (error) => {
|
|
142
|
-
this.logger.info('request has been aborted!', { error })
|
|
143
|
-
responsePromise.reject(error)
|
|
171
|
+
errorWith: (reason) => {
|
|
172
|
+
this.logger.info('request has been aborted!', { reason })
|
|
173
|
+
responsePromise.reject(reason)
|
|
144
174
|
},
|
|
145
175
|
})
|
|
146
176
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return responsePromise
|
|
150
|
-
}
|
|
177
|
+
this.logger.info('[%s] %s', request.method, request.url)
|
|
178
|
+
this.logger.info('awaiting for the mocked response...')
|
|
151
179
|
|
|
152
180
|
this.logger.info(
|
|
153
|
-
'
|
|
181
|
+
'emitting the "request" event for %s listener(s)...',
|
|
182
|
+
this.emitter.listenerCount('request')
|
|
154
183
|
)
|
|
155
184
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
*/
|
|
162
|
-
const requestCloneForResponseEvent = request.clone()
|
|
163
|
-
|
|
164
|
-
return pureFetch(request).then(async (response) => {
|
|
165
|
-
this.logger.info('original fetch performed', response)
|
|
166
|
-
|
|
167
|
-
if (this.emitter.listenerCount('response') > 0) {
|
|
168
|
-
this.logger.info('emitting the "response" event...')
|
|
169
|
-
|
|
170
|
-
const responseClone = response.clone()
|
|
171
|
-
|
|
172
|
-
await emitAsync(this.emitter, 'response', {
|
|
173
|
-
response: responseClone,
|
|
174
|
-
isMockedResponse: false,
|
|
175
|
-
request: requestCloneForResponseEvent,
|
|
176
|
-
requestId,
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return response
|
|
185
|
+
await handleRequest({
|
|
186
|
+
request,
|
|
187
|
+
requestId,
|
|
188
|
+
emitter: this.emitter,
|
|
189
|
+
controller,
|
|
181
190
|
})
|
|
191
|
+
|
|
192
|
+
return responsePromise
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {
|
|
@@ -3,12 +3,11 @@ import { DeferredPromise } from '@open-draft/deferred-promise'
|
|
|
3
3
|
import { until } from '@open-draft/until'
|
|
4
4
|
import type { HttpRequestEventMap } from '../glossary'
|
|
5
5
|
import { emitAsync } from './emitAsync'
|
|
6
|
-
import {
|
|
6
|
+
import { RequestController } from '../RequestController'
|
|
7
7
|
import {
|
|
8
8
|
createServerErrorResponse,
|
|
9
9
|
isResponseError,
|
|
10
10
|
isResponseLike,
|
|
11
|
-
ResponseError,
|
|
12
11
|
} from './responseUtils'
|
|
13
12
|
import { InterceptorError } from '../InterceptorError'
|
|
14
13
|
import { isNodeLikeError } from './isNodeLikeError'
|
|
@@ -19,43 +18,22 @@ interface HandleRequestOptions {
|
|
|
19
18
|
request: Request
|
|
20
19
|
emitter: Emitter<HttpRequestEventMap>
|
|
21
20
|
controller: RequestController
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Called when the request has been handled
|
|
25
|
-
* with the given `Response` instance.
|
|
26
|
-
*/
|
|
27
|
-
onResponse: (response: Response) => void | Promise<void>
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Called when the request has been handled
|
|
31
|
-
* with the given `Response.error()` instance.
|
|
32
|
-
*/
|
|
33
|
-
onRequestError: (response: ResponseError) => void
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Called when an unhandled error happens during the
|
|
37
|
-
* request handling. This is never a thrown error/response.
|
|
38
|
-
*/
|
|
39
|
-
onError: (error: unknown) => void
|
|
40
21
|
}
|
|
41
22
|
|
|
42
|
-
/**
|
|
43
|
-
* @returns {Promise<boolean>} Indicates whether the request has been handled.
|
|
44
|
-
*/
|
|
45
23
|
export async function handleRequest(
|
|
46
24
|
options: HandleRequestOptions
|
|
47
|
-
): Promise<
|
|
25
|
+
): Promise<void> {
|
|
48
26
|
const handleResponse = async (
|
|
49
27
|
response: Response | Error | Record<string, any>
|
|
50
28
|
) => {
|
|
51
29
|
if (response instanceof Error) {
|
|
52
|
-
options.
|
|
30
|
+
await options.controller.errorWith(response)
|
|
53
31
|
return true
|
|
54
32
|
}
|
|
55
33
|
|
|
56
34
|
// Handle "Response.error()" instances.
|
|
57
35
|
if (isResponseError(response)) {
|
|
58
|
-
options.
|
|
36
|
+
await options.controller.respondWith(response)
|
|
59
37
|
return true
|
|
60
38
|
}
|
|
61
39
|
|
|
@@ -65,13 +43,13 @@ export async function handleRequest(
|
|
|
65
43
|
* since Response instances are, in fact, objects.
|
|
66
44
|
*/
|
|
67
45
|
if (isResponseLike(response)) {
|
|
68
|
-
await options.
|
|
46
|
+
await options.controller.respondWith(response)
|
|
69
47
|
return true
|
|
70
48
|
}
|
|
71
49
|
|
|
72
50
|
// Handle arbitrary objects provided to `.errorWith(reason)`.
|
|
73
51
|
if (isObject(response)) {
|
|
74
|
-
options.
|
|
52
|
+
await options.controller.errorWith(response)
|
|
75
53
|
return true
|
|
76
54
|
}
|
|
77
55
|
|
|
@@ -87,7 +65,7 @@ export async function handleRequest(
|
|
|
87
65
|
|
|
88
66
|
// Support mocking Node.js-like errors.
|
|
89
67
|
if (isNodeLikeError(error)) {
|
|
90
|
-
options.
|
|
68
|
+
await options.controller.errorWith(error)
|
|
91
69
|
return true
|
|
92
70
|
}
|
|
93
71
|
|
|
@@ -102,15 +80,14 @@ export async function handleRequest(
|
|
|
102
80
|
// Add the last "request" listener to check if the request
|
|
103
81
|
// has been handled in any way. If it hasn't, resolve the
|
|
104
82
|
// response promise with undefined.
|
|
105
|
-
options.emitter.once('request', ({ requestId: pendingRequestId }) => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
})
|
|
83
|
+
// options.emitter.once('request', async ({ requestId: pendingRequestId }) => {
|
|
84
|
+
// if (
|
|
85
|
+
// pendingRequestId === options.requestId &&
|
|
86
|
+
// options.controller.readyState === RequestController.PENDING
|
|
87
|
+
// ) {
|
|
88
|
+
// await options.controller.passthrough()
|
|
89
|
+
// }
|
|
90
|
+
// })
|
|
114
91
|
|
|
115
92
|
const requestAbortPromise = new DeferredPromise<void, unknown>()
|
|
116
93
|
|
|
@@ -119,16 +96,17 @@ export async function handleRequest(
|
|
|
119
96
|
*/
|
|
120
97
|
if (options.request.signal) {
|
|
121
98
|
if (options.request.signal.aborted) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
options.request.signal.addEventListener(
|
|
125
|
-
'abort',
|
|
126
|
-
() => {
|
|
127
|
-
requestAbortPromise.reject(options.request.signal.reason)
|
|
128
|
-
},
|
|
129
|
-
{ once: true }
|
|
130
|
-
)
|
|
99
|
+
await options.controller.errorWith(options.request.signal.reason)
|
|
100
|
+
return
|
|
131
101
|
}
|
|
102
|
+
|
|
103
|
+
options.request.signal.addEventListener(
|
|
104
|
+
'abort',
|
|
105
|
+
() => {
|
|
106
|
+
requestAbortPromise.reject(options.request.signal.reason)
|
|
107
|
+
},
|
|
108
|
+
{ once: true }
|
|
109
|
+
)
|
|
132
110
|
}
|
|
133
111
|
|
|
134
112
|
const result = await until(async () => {
|
|
@@ -146,25 +124,21 @@ export async function handleRequest(
|
|
|
146
124
|
// Short-circuit the request handling promise if the request gets aborted.
|
|
147
125
|
requestAbortPromise,
|
|
148
126
|
requestListenersPromise,
|
|
149
|
-
options.controller
|
|
127
|
+
options.controller.handled,
|
|
150
128
|
])
|
|
151
|
-
|
|
152
|
-
// The response promise will settle immediately once
|
|
153
|
-
// the developer calls either "respondWith" or "errorWith".
|
|
154
|
-
return await options.controller[kResponsePromise]
|
|
155
129
|
})
|
|
156
130
|
|
|
157
131
|
// Handle the request being aborted while waiting for the request listeners.
|
|
158
132
|
if (requestAbortPromise.state === 'rejected') {
|
|
159
|
-
options.
|
|
160
|
-
return
|
|
133
|
+
await options.controller.errorWith(requestAbortPromise.rejectionReason)
|
|
134
|
+
return
|
|
161
135
|
}
|
|
162
136
|
|
|
163
137
|
if (result.error) {
|
|
164
138
|
// Handle the error during the request listener execution.
|
|
165
139
|
// These can be thrown responses or request errors.
|
|
166
140
|
if (await handleResponseError(result.error)) {
|
|
167
|
-
return
|
|
141
|
+
return
|
|
168
142
|
}
|
|
169
143
|
|
|
170
144
|
// If the developer has added "unhandledException" listeners,
|
|
@@ -175,7 +149,28 @@ export async function handleRequest(
|
|
|
175
149
|
// This is needed because the original controller might have been already
|
|
176
150
|
// interacted with (e.g. "respondWith" or "errorWith" called on it).
|
|
177
151
|
const unhandledExceptionController = new RequestController(
|
|
178
|
-
options.request
|
|
152
|
+
options.request,
|
|
153
|
+
{
|
|
154
|
+
/**
|
|
155
|
+
* @note Intentionally empty passthrough handle.
|
|
156
|
+
* This controller is created within another controller and we only need
|
|
157
|
+
* to know if `unhandledException` listeners handled the request.
|
|
158
|
+
*/
|
|
159
|
+
passthrough() {},
|
|
160
|
+
async respondWith(response) {
|
|
161
|
+
await handleResponse(response)
|
|
162
|
+
},
|
|
163
|
+
async errorWith(reason) {
|
|
164
|
+
/**
|
|
165
|
+
* @note Handle the result of the unhandled controller
|
|
166
|
+
* in the same way as the original request controller.
|
|
167
|
+
* The exception here is that thrown errors within the
|
|
168
|
+
* "unhandledException" event do NOT result in another
|
|
169
|
+
* emit of the same event. They are forwarded as-is.
|
|
170
|
+
*/
|
|
171
|
+
await options.controller.errorWith(reason)
|
|
172
|
+
},
|
|
173
|
+
}
|
|
179
174
|
)
|
|
180
175
|
|
|
181
176
|
await emitAsync(options.emitter, 'unhandledException', {
|
|
@@ -183,53 +178,28 @@ export async function handleRequest(
|
|
|
183
178
|
request: options.request,
|
|
184
179
|
requestId: options.requestId,
|
|
185
180
|
controller: unhandledExceptionController,
|
|
186
|
-
}).then(() => {
|
|
187
|
-
// If all the "unhandledException" listeners have finished
|
|
188
|
-
// but have not handled the response in any way, preemptively
|
|
189
|
-
// resolve the pending response promise from the new controller.
|
|
190
|
-
// This prevents it from hanging forever.
|
|
191
|
-
if (
|
|
192
|
-
unhandledExceptionController[kResponsePromise].state === 'pending'
|
|
193
|
-
) {
|
|
194
|
-
unhandledExceptionController[kResponsePromise].resolve(undefined)
|
|
195
|
-
}
|
|
196
181
|
})
|
|
197
182
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
* in the same way as the original request controller.
|
|
205
|
-
* The exception here is that thrown errors within the
|
|
206
|
-
* "unhandledException" event do NOT result in another
|
|
207
|
-
* emit of the same event. They are forwarded as-is.
|
|
208
|
-
*/
|
|
209
|
-
if (nextResult.error) {
|
|
210
|
-
return handleResponseError(nextResult.error)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (nextResult.data) {
|
|
214
|
-
return handleResponse(nextResult.data)
|
|
183
|
+
// If all the "unhandledException" listeners have finished
|
|
184
|
+
// but have not handled the request in any way, passthrough.
|
|
185
|
+
if (
|
|
186
|
+
unhandledExceptionController.readyState !== RequestController.PENDING
|
|
187
|
+
) {
|
|
188
|
+
return
|
|
215
189
|
}
|
|
216
190
|
}
|
|
217
191
|
|
|
218
192
|
// Otherwise, coerce unhandled exceptions to a 500 Internal Server Error response.
|
|
219
|
-
options.
|
|
220
|
-
|
|
193
|
+
await options.controller.respondWith(
|
|
194
|
+
createServerErrorResponse(result.error)
|
|
195
|
+
)
|
|
196
|
+
return
|
|
221
197
|
}
|
|
222
198
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
* the developer called "errorWith". This differentiates
|
|
227
|
-
* unhandled exceptions from intended errors.
|
|
228
|
-
*/
|
|
229
|
-
if (result.data) {
|
|
230
|
-
return handleResponse(result.data)
|
|
199
|
+
// If the request hasn't been handled by this point, passthrough.
|
|
200
|
+
if (options.controller.readyState === RequestController.PENDING) {
|
|
201
|
+
return await options.controller.passthrough()
|
|
231
202
|
}
|
|
232
203
|
|
|
233
|
-
|
|
234
|
-
return false
|
|
204
|
+
return options.controller.handled
|
|
235
205
|
}
|