@mswjs/interceptors 0.32.2 → 0.33.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/README.md +35 -7
- package/lib/browser/{chunk-732REFPX.mjs → chunk-5ETVT6GU.mjs} +28 -79
- package/lib/browser/chunk-5ETVT6GU.mjs.map +1 -0
- package/lib/browser/chunk-6MBJUL74.js +142 -0
- package/lib/browser/chunk-6MBJUL74.js.map +1 -0
- package/lib/browser/chunk-7A4UJNSW.mjs +196 -0
- package/lib/browser/chunk-7A4UJNSW.mjs.map +1 -0
- package/lib/browser/{chunk-PSX5J3RF.js → chunk-7GVJEW45.js} +30 -81
- package/lib/browser/chunk-7GVJEW45.js.map +1 -0
- package/lib/browser/{chunk-2CRB3JAQ.js → chunk-FXSPMSSQ.js} +1 -1
- package/lib/browser/chunk-FXSPMSSQ.js.map +1 -0
- package/lib/browser/{chunk-OMISYKWR.mjs → chunk-GGUENBDN.mjs} +1 -1
- package/lib/browser/chunk-GGUENBDN.mjs.map +1 -0
- package/lib/browser/chunk-NU2MPFD6.mjs +142 -0
- package/lib/browser/chunk-NU2MPFD6.mjs.map +1 -0
- package/lib/browser/chunk-VRKVKT62.js +196 -0
- package/lib/browser/chunk-VRKVKT62.js.map +1 -0
- package/lib/browser/glossary-7d7adb4b.d.ts +66 -0
- package/lib/browser/index.d.ts +1 -1
- package/lib/browser/index.js +2 -2
- package/lib/browser/index.mjs +1 -1
- package/lib/browser/interceptors/XMLHttpRequest/index.d.ts +2 -6
- package/lib/browser/interceptors/XMLHttpRequest/index.js +4 -4
- package/lib/browser/interceptors/XMLHttpRequest/index.mjs +3 -3
- package/lib/browser/interceptors/fetch/index.d.ts +1 -1
- package/lib/browser/interceptors/fetch/index.js +4 -4
- package/lib/browser/interceptors/fetch/index.mjs +3 -3
- package/lib/browser/presets/browser.d.ts +1 -1
- package/lib/browser/presets/browser.js +6 -6
- package/lib/browser/presets/browser.mjs +4 -4
- package/lib/node/{BatchInterceptor-2badedde.d.ts → BatchInterceptor-13d40c95.d.ts} +1 -1
- package/lib/node/{Interceptor-88ee47c0.d.ts → Interceptor-a31b1217.d.ts} +35 -13
- package/lib/node/RemoteHttpInterceptor.d.ts +2 -2
- package/lib/node/RemoteHttpInterceptor.js +55 -52
- package/lib/node/RemoteHttpInterceptor.js.map +1 -1
- package/lib/node/RemoteHttpInterceptor.mjs +53 -50
- package/lib/node/RemoteHttpInterceptor.mjs.map +1 -1
- package/lib/node/{chunk-CFRXZJO4.js → chunk-2MWIWEWV.js} +31 -72
- package/lib/node/chunk-2MWIWEWV.js.map +1 -0
- package/lib/node/{chunk-2COJKQQB.js → chunk-42632LKH.js} +3 -3
- package/lib/node/chunk-5WWNCLB3.js +196 -0
- package/lib/node/chunk-5WWNCLB3.js.map +1 -0
- package/lib/node/{chunk-TGTPXCLF.mjs → chunk-BUCULLYM.mjs} +1 -1
- package/lib/node/{chunk-TGTPXCLF.mjs.map → chunk-BUCULLYM.mjs.map} +1 -1
- package/lib/node/{chunk-OJ6O4LSC.mjs → chunk-BZ3Y7YV5.mjs} +1 -1
- package/lib/node/chunk-BZ3Y7YV5.mjs.map +1 -0
- package/lib/node/{chunk-CMVICWQS.mjs → chunk-CU3YXMM4.mjs} +23 -64
- package/lib/node/chunk-CU3YXMM4.mjs.map +1 -0
- package/lib/node/{chunk-PNWPIDEL.mjs → chunk-HGQLG7KE.mjs} +2 -2
- package/lib/node/{chunk-EIBTX65O.js → chunk-IDEEMJ3F.js} +1 -1
- package/lib/node/chunk-IDEEMJ3F.js.map +1 -0
- package/lib/node/chunk-KY3RJ2M3.mjs +196 -0
- package/lib/node/chunk-KY3RJ2M3.mjs.map +1 -0
- package/lib/node/{chunk-PYD4E2EJ.js → chunk-P6QG76R3.js} +34 -85
- package/lib/node/chunk-P6QG76R3.js.map +1 -0
- package/lib/node/{chunk-DV4PBH4D.mjs → chunk-TOV4TYIX.mjs} +29 -80
- package/lib/node/chunk-TOV4TYIX.mjs.map +1 -0
- package/lib/node/{chunk-BFLYGQ6D.js → chunk-YGM3BCJU.js} +1 -1
- package/lib/node/chunk-YGM3BCJU.js.map +1 -0
- package/lib/node/index.d.ts +2 -2
- package/lib/node/index.js +4 -4
- package/lib/node/index.mjs +3 -3
- package/lib/node/interceptors/ClientRequest/index.d.ts +2 -2
- package/lib/node/interceptors/ClientRequest/index.js +4 -4
- package/lib/node/interceptors/ClientRequest/index.mjs +3 -3
- package/lib/node/interceptors/XMLHttpRequest/index.d.ts +2 -6
- 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 -1
- package/lib/node/interceptors/fetch/index.js +52 -123
- package/lib/node/interceptors/fetch/index.js.map +1 -1
- package/lib/node/interceptors/fetch/index.mjs +50 -121
- package/lib/node/interceptors/fetch/index.mjs.map +1 -1
- package/lib/node/presets/node.d.ts +1 -1
- package/lib/node/presets/node.js +7 -7
- package/lib/node/presets/node.mjs +5 -5
- package/package.json +2 -2
- package/src/InterceptorError.ts +7 -0
- package/src/RemoteHttpInterceptor.ts +62 -57
- package/src/RequestController.test.ts +49 -0
- package/src/RequestController.ts +81 -0
- package/src/glossary.ts +4 -6
- package/src/interceptors/ClientRequest/MockHttpSocket.ts +1 -1
- package/src/interceptors/ClientRequest/index.test.ts +2 -33
- package/src/interceptors/ClientRequest/index.ts +21 -82
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts +1 -1
- package/src/interceptors/XMLHttpRequest/XMLHttpRequestProxy.ts +27 -108
- package/src/interceptors/XMLHttpRequest/index.ts +0 -6
- package/src/interceptors/fetch/index.ts +52 -169
- package/src/utils/handleRequest.ts +213 -0
- package/src/utils/responseUtils.ts +4 -4
- package/lib/browser/chunk-2CRB3JAQ.js.map +0 -1
- package/lib/browser/chunk-732REFPX.mjs.map +0 -1
- package/lib/browser/chunk-MAEPOYB6.mjs +0 -213
- package/lib/browser/chunk-MAEPOYB6.mjs.map +0 -1
- package/lib/browser/chunk-MQJ3JOOK.js +0 -49
- package/lib/browser/chunk-MQJ3JOOK.js.map +0 -1
- package/lib/browser/chunk-OMISYKWR.mjs.map +0 -1
- package/lib/browser/chunk-OUWBQF3Z.mjs +0 -49
- package/lib/browser/chunk-OUWBQF3Z.mjs.map +0 -1
- package/lib/browser/chunk-PSX5J3RF.js.map +0 -1
- package/lib/browser/chunk-WBHIW62P.js +0 -213
- package/lib/browser/chunk-WBHIW62P.js.map +0 -1
- package/lib/browser/glossary-1c204f45.d.ts +0 -44
- package/lib/node/chunk-BFLYGQ6D.js.map +0 -1
- package/lib/node/chunk-CFRXZJO4.js.map +0 -1
- package/lib/node/chunk-CMVICWQS.mjs.map +0 -1
- package/lib/node/chunk-DV4PBH4D.mjs.map +0 -1
- package/lib/node/chunk-EIBTX65O.js.map +0 -1
- package/lib/node/chunk-KWV3JXSI.mjs +0 -49
- package/lib/node/chunk-KWV3JXSI.mjs.map +0 -1
- package/lib/node/chunk-OJ6O4LSC.mjs.map +0 -1
- package/lib/node/chunk-PYD4E2EJ.js.map +0 -1
- package/lib/node/chunk-UXCYRE4F.js +0 -49
- package/lib/node/chunk-UXCYRE4F.js.map +0 -1
- package/src/utils/toInteractiveRequest.ts +0 -23
- /package/lib/node/{chunk-2COJKQQB.js.map → chunk-42632LKH.js.map} +0 -0
- /package/lib/node/{chunk-PNWPIDEL.mjs.map → chunk-HGQLG7KE.mjs.map} +0 -0
package/src/glossary.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RequestController } from './RequestController'
|
|
2
2
|
|
|
3
3
|
export const IS_PATCHED_MODULE: unique symbol = Symbol('isPatchedModule')
|
|
4
4
|
|
|
@@ -7,8 +7,9 @@ export type RequestCredentials = 'omit' | 'include' | 'same-origin'
|
|
|
7
7
|
export type HttpRequestEventMap = {
|
|
8
8
|
request: [
|
|
9
9
|
args: {
|
|
10
|
-
request:
|
|
10
|
+
request: Request
|
|
11
11
|
requestId: string
|
|
12
|
+
controller: RequestController
|
|
12
13
|
}
|
|
13
14
|
]
|
|
14
15
|
response: [
|
|
@@ -24,10 +25,7 @@ export type HttpRequestEventMap = {
|
|
|
24
25
|
error: unknown
|
|
25
26
|
request: Request
|
|
26
27
|
requestId: string
|
|
27
|
-
controller:
|
|
28
|
-
respondWith(response: Response): void
|
|
29
|
-
errorWith(error?: Error): void
|
|
30
|
-
}
|
|
28
|
+
controller: RequestController
|
|
31
29
|
}
|
|
32
30
|
]
|
|
33
31
|
}
|
|
@@ -30,43 +30,12 @@ afterAll(async () => {
|
|
|
30
30
|
await httpServer.close()
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
it('forbids calling "respondWith" multiple times for the same request', async () => {
|
|
34
|
-
const requestUrl = httpServer.http.url('/')
|
|
35
|
-
|
|
36
|
-
interceptor.on('request', function firstRequestListener({ request }) {
|
|
37
|
-
request.respondWith(new Response())
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
const secondRequestEmitted = new DeferredPromise<void>()
|
|
41
|
-
interceptor.on('request', function secondRequestListener({ request }) {
|
|
42
|
-
expect(() =>
|
|
43
|
-
request.respondWith(new Response(null, { status: 301 }))
|
|
44
|
-
).toThrow(
|
|
45
|
-
`Failed to respond to "GET ${requestUrl}" request: the "request" event has already been responded to.`
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
secondRequestEmitted.resolve()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const request = http.get(requestUrl)
|
|
52
|
-
await secondRequestEmitted
|
|
53
|
-
|
|
54
|
-
const responseReceived = new DeferredPromise<http.IncomingMessage>()
|
|
55
|
-
request.on('response', (response) => {
|
|
56
|
-
responseReceived.resolve(response)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const response = await responseReceived
|
|
60
|
-
expect(response.statusCode).toBe(200)
|
|
61
|
-
expect(response.statusMessage).toBe('OK')
|
|
62
|
-
})
|
|
63
|
-
|
|
64
33
|
it('abort the request if the abort signal is emitted', async () => {
|
|
65
34
|
const requestUrl = httpServer.http.url('/')
|
|
66
35
|
|
|
67
|
-
interceptor.on('request', async function delayedResponse({
|
|
36
|
+
interceptor.on('request', async function delayedResponse({ controller }) {
|
|
68
37
|
await sleep(1_000)
|
|
69
|
-
|
|
38
|
+
controller.respondWith(new Response())
|
|
70
39
|
})
|
|
71
40
|
|
|
72
41
|
const abortController = new AbortController()
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import http from 'node:http'
|
|
2
2
|
import https from 'node:https'
|
|
3
|
-
import { until } from '@open-draft/until'
|
|
4
3
|
import { Interceptor } from '../../Interceptor'
|
|
5
4
|
import type { HttpRequestEventMap } from '../../glossary'
|
|
6
5
|
import {
|
|
@@ -9,11 +8,10 @@ import {
|
|
|
9
8
|
MockHttpSocketResponseCallback,
|
|
10
9
|
} from './MockHttpSocket'
|
|
11
10
|
import { MockAgent, MockHttpsAgent } from './agents'
|
|
11
|
+
import { RequestController } from '../../RequestController'
|
|
12
12
|
import { emitAsync } from '../../utils/emitAsync'
|
|
13
|
-
import { toInteractiveRequest } from '../../utils/toInteractiveRequest'
|
|
14
13
|
import { normalizeClientRequestArgs } from './utils/normalizeClientRequestArgs'
|
|
15
|
-
import {
|
|
16
|
-
import { createServerErrorResponse } from '../../utils/responseUtils'
|
|
14
|
+
import { handleRequest } from '../../utils/handleRequest'
|
|
17
15
|
import {
|
|
18
16
|
recordRawFetchHeaders,
|
|
19
17
|
restoreHeadersPrototype,
|
|
@@ -129,88 +127,29 @@ export class ClientRequestInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
129
127
|
socket,
|
|
130
128
|
}) => {
|
|
131
129
|
const requestId = Reflect.get(request, kRequestId)
|
|
132
|
-
const
|
|
133
|
-
toInteractiveRequest(request)
|
|
134
|
-
|
|
135
|
-
// TODO: Abstract this bit. We are using it everywhere.
|
|
136
|
-
this.emitter.once('request', ({ requestId: pendingRequestId }) => {
|
|
137
|
-
if (pendingRequestId !== requestId) {
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (requestController.responsePromise.state === 'pending') {
|
|
142
|
-
this.logger.info(
|
|
143
|
-
'request has not been handled in listeners, executing fail-safe listener...'
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
requestController.responsePromise.resolve(undefined)
|
|
147
|
-
}
|
|
148
|
-
})
|
|
130
|
+
const controller = new RequestController(request)
|
|
149
131
|
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Allow mocking Node-like errors.
|
|
167
|
-
if (isNodeLikeError(listenerResult.error)) {
|
|
168
|
-
socket.errorWith(listenerResult.error)
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Emit the "unhandledException" event to allow the client
|
|
173
|
-
// to opt-out from the default handling of exceptions
|
|
174
|
-
// as 500 error responses.
|
|
175
|
-
if (this.emitter.listenerCount('unhandledException') > 0) {
|
|
176
|
-
await emitAsync(this.emitter, 'unhandledException', {
|
|
177
|
-
error: listenerResult.error,
|
|
178
|
-
request,
|
|
179
|
-
requestId,
|
|
180
|
-
controller: {
|
|
181
|
-
respondWith: socket.respondWith.bind(socket),
|
|
182
|
-
errorWith: socket.errorWith.bind(socket),
|
|
183
|
-
},
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
// After the listeners are done, if the socket is
|
|
187
|
-
// not connecting anymore, the response was mocked.
|
|
188
|
-
// If the socket has been destroyed, the error was mocked.
|
|
189
|
-
// Treat both as the result of the listener's call.
|
|
190
|
-
if (!socket.connecting || socket.destroyed) {
|
|
191
|
-
return
|
|
132
|
+
const isRequestHandled = await handleRequest({
|
|
133
|
+
request,
|
|
134
|
+
requestId,
|
|
135
|
+
controller,
|
|
136
|
+
emitter: this.emitter,
|
|
137
|
+
onResponse: (response) => {
|
|
138
|
+
socket.respondWith(response)
|
|
139
|
+
},
|
|
140
|
+
onRequestError: (response) => {
|
|
141
|
+
socket.respondWith(response)
|
|
142
|
+
},
|
|
143
|
+
onError: (error) => {
|
|
144
|
+
if (error instanceof Error) {
|
|
145
|
+
socket.errorWith(error)
|
|
192
146
|
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Unhandled exceptions in the request listeners are
|
|
196
|
-
// synonymous to unhandled exceptions on the server.
|
|
197
|
-
// Those are represented as 500 error responses.
|
|
198
|
-
socket.respondWith(createServerErrorResponse(listenerResult.error))
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const mockedResponse = listenerResult.data
|
|
147
|
+
},
|
|
148
|
+
})
|
|
203
149
|
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
* @note The `.respondWith()` method will handle "Response.error()".
|
|
207
|
-
* Maybe we should make all interceptors do that?
|
|
208
|
-
*/
|
|
209
|
-
socket.respondWith(mockedResponse)
|
|
210
|
-
return
|
|
150
|
+
if (!isRequestHandled) {
|
|
151
|
+
return socket.passthrough()
|
|
211
152
|
}
|
|
212
|
-
|
|
213
|
-
socket.passthrough()
|
|
214
153
|
}
|
|
215
154
|
|
|
216
155
|
public onResponse: MockHttpSocketResponseCallback = async ({
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import { until } from '@open-draft/until'
|
|
2
1
|
import type { Logger } from '@open-draft/logger'
|
|
3
2
|
import { XMLHttpRequestEmitter } from '.'
|
|
4
|
-
import {
|
|
5
|
-
import { emitAsync } from '../../utils/emitAsync'
|
|
3
|
+
import { RequestController } from '../../RequestController'
|
|
6
4
|
import { XMLHttpRequestController } from './XMLHttpRequestController'
|
|
7
|
-
import {
|
|
8
|
-
createServerErrorResponse,
|
|
9
|
-
isResponseError,
|
|
10
|
-
} from '../../utils/responseUtils'
|
|
5
|
+
import { handleRequest } from '../../utils/handleRequest'
|
|
11
6
|
|
|
12
7
|
export interface XMLHttpRequestProxyOptions {
|
|
13
8
|
emitter: XMLHttpRequestEmitter
|
|
@@ -57,116 +52,40 @@ export function createXMLHttpRequestProxy({
|
|
|
57
52
|
)
|
|
58
53
|
|
|
59
54
|
xhrRequestController.onRequest = async function ({ request, requestId }) {
|
|
60
|
-
const
|
|
61
|
-
toInteractiveRequest(request)
|
|
55
|
+
const controller = new RequestController(request)
|
|
62
56
|
|
|
63
57
|
this.logger.info('awaiting mocked response...')
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (requestController.responsePromise.state === 'pending') {
|
|
71
|
-
requestController.respondWith(undefined)
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const resolverResult = await until(async () => {
|
|
76
|
-
this.logger.info(
|
|
77
|
-
'emitting the "request" event for %s listener(s)...',
|
|
78
|
-
emitter.listenerCount('request')
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
await emitAsync(emitter, 'request', {
|
|
82
|
-
request: interactiveRequest,
|
|
83
|
-
requestId,
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
this.logger.info('all "request" listeners settled!')
|
|
87
|
-
|
|
88
|
-
const mockedResponse = await requestController.responsePromise
|
|
89
|
-
|
|
90
|
-
this.logger.info('event.respondWith called with:', mockedResponse)
|
|
91
|
-
|
|
92
|
-
return mockedResponse
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
if (resolverResult.error) {
|
|
96
|
-
this.logger.info(
|
|
97
|
-
'request listener threw an exception, aborting request...',
|
|
98
|
-
resolverResult.error
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
// Treat thrown Responses as mocked responses.
|
|
102
|
-
if (resolverResult.error instanceof Response) {
|
|
103
|
-
if (isResponseError(resolverResult.error)) {
|
|
104
|
-
xhrRequestController.errorWith(new TypeError('Network error'))
|
|
105
|
-
} else {
|
|
106
|
-
this.respondWith(resolverResult.error)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (emitter.listenerCount('unhandledException') > 0) {
|
|
113
|
-
// Emit the "unhandledException" event so the client can opt-out
|
|
114
|
-
// from the default exception handling (producing 500 error responses).
|
|
115
|
-
await emitAsync(emitter, 'unhandledException', {
|
|
116
|
-
error: resolverResult.error,
|
|
117
|
-
request,
|
|
118
|
-
requestId,
|
|
119
|
-
controller: {
|
|
120
|
-
respondWith:
|
|
121
|
-
xhrRequestController.respondWith.bind(xhrRequestController),
|
|
122
|
-
errorWith:
|
|
123
|
-
xhrRequestController.errorWith.bind(xhrRequestController),
|
|
124
|
-
},
|
|
125
|
-
})
|
|
59
|
+
this.logger.info(
|
|
60
|
+
'emitting the "request" event for %s listener(s)...',
|
|
61
|
+
emitter.listenerCount('request')
|
|
62
|
+
)
|
|
126
63
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
64
|
+
const isRequestHandled = await handleRequest({
|
|
65
|
+
request,
|
|
66
|
+
requestId,
|
|
67
|
+
controller,
|
|
68
|
+
emitter,
|
|
69
|
+
onResponse: (response) => {
|
|
70
|
+
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)
|
|
133
80
|
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Unhandled exceptions in the request listeners are
|
|
137
|
-
// synonymous to unhandled exceptions on the server.
|
|
138
|
-
// Those are represented as 500 error responses.
|
|
139
|
-
xhrRequestController.respondWith(
|
|
140
|
-
createServerErrorResponse(resolverResult.error)
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const mockedResponse = resolverResult.data
|
|
81
|
+
},
|
|
82
|
+
})
|
|
147
83
|
|
|
148
|
-
if (
|
|
84
|
+
if (!isRequestHandled) {
|
|
149
85
|
this.logger.info(
|
|
150
|
-
'
|
|
151
|
-
mockedResponse.status,
|
|
152
|
-
mockedResponse.statusText
|
|
86
|
+
'no mocked response received, performing request as-is...'
|
|
153
87
|
)
|
|
154
|
-
|
|
155
|
-
if (isResponseError(mockedResponse)) {
|
|
156
|
-
this.logger.info(
|
|
157
|
-
'received a network error response, rejecting the request promise...'
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
xhrRequestController.errorWith(new TypeError('Network error'))
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return xhrRequestController.respondWith(mockedResponse)
|
|
165
88
|
}
|
|
166
|
-
|
|
167
|
-
this.logger.info(
|
|
168
|
-
'no mocked response received, performing request as-is...'
|
|
169
|
-
)
|
|
170
89
|
}
|
|
171
90
|
|
|
172
91
|
xhrRequestController.onResponse = async function ({
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { invariant } from 'outvariant'
|
|
2
2
|
import { Emitter } from 'strict-event-emitter'
|
|
3
3
|
import { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'
|
|
4
|
-
import { InteractiveRequest } from '../../utils/toInteractiveRequest'
|
|
5
4
|
import { Interceptor } from '../../Interceptor'
|
|
6
5
|
import { createXMLHttpRequestProxy } from './XMLHttpRequestProxy'
|
|
7
6
|
|
|
8
|
-
export type XMLHttpRequestEventListener = (args: {
|
|
9
|
-
request: InteractiveRequest
|
|
10
|
-
requestId: string
|
|
11
|
-
}) => Promise<void> | void
|
|
12
|
-
|
|
13
7
|
export type XMLHttpRequestEmitter = Emitter<HttpRequestEventMap>
|
|
14
8
|
|
|
15
9
|
export class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { invariant } from 'outvariant'
|
|
2
2
|
import { DeferredPromise } from '@open-draft/deferred-promise'
|
|
3
|
-
import { until } from '@open-draft/until'
|
|
4
3
|
import { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'
|
|
5
4
|
import { Interceptor } from '../../Interceptor'
|
|
6
|
-
import {
|
|
5
|
+
import { RequestController } from '../../RequestController'
|
|
7
6
|
import { emitAsync } from '../../utils/emitAsync'
|
|
7
|
+
import { handleRequest } from '../../utils/handleRequest'
|
|
8
8
|
import { canParseUrl } from '../../utils/canParseUrl'
|
|
9
9
|
import { createRequestId } from '../../createRequestId'
|
|
10
|
-
import {
|
|
11
|
-
createServerErrorResponse,
|
|
12
|
-
isResponseError,
|
|
13
|
-
} from '../../utils/responseUtils'
|
|
14
10
|
|
|
15
11
|
export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
16
12
|
static symbol = Symbol('fetch')
|
|
@@ -51,185 +47,72 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
51
47
|
: input
|
|
52
48
|
|
|
53
49
|
const request = new Request(resolvedInput, init)
|
|
50
|
+
const responsePromise = new DeferredPromise<Response>()
|
|
51
|
+
const controller = new RequestController(request)
|
|
54
52
|
|
|
55
53
|
this.logger.info('[%s] %s', request.method, request.url)
|
|
56
|
-
|
|
57
|
-
const { interactiveRequest, requestController } =
|
|
58
|
-
toInteractiveRequest(request)
|
|
54
|
+
this.logger.info('awaiting for the mocked response...')
|
|
59
55
|
|
|
60
56
|
this.logger.info(
|
|
61
|
-
'emitting the "request" event for %
|
|
57
|
+
'emitting the "request" event for %s listener(s)...',
|
|
62
58
|
this.emitter.listenerCount('request')
|
|
63
59
|
)
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
this.logger.info('awaiting for the mocked response...')
|
|
76
|
-
|
|
77
|
-
const signal = interactiveRequest.signal
|
|
78
|
-
const requestAborted = new DeferredPromise()
|
|
79
|
-
|
|
80
|
-
// Signal isn't always defined in react-native.
|
|
81
|
-
if (signal) {
|
|
82
|
-
signal.addEventListener(
|
|
83
|
-
'abort',
|
|
84
|
-
() => {
|
|
85
|
-
requestAborted.reject(signal.reason)
|
|
86
|
-
},
|
|
87
|
-
{ once: true }
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const responsePromise = new DeferredPromise<Response>()
|
|
92
|
-
|
|
93
|
-
const respondWith = (response: Response): void => {
|
|
94
|
-
this.logger.info('responding with a mock response:', response)
|
|
95
|
-
|
|
96
|
-
if (this.emitter.listenerCount('response') > 0) {
|
|
97
|
-
this.logger.info('emitting the "response" event...')
|
|
98
|
-
|
|
99
|
-
// Clone the mocked response for the "response" event listener.
|
|
100
|
-
// This way, the listener can read the response and not lock its body
|
|
101
|
-
// for the actual fetch consumer.
|
|
102
|
-
const responseClone = response.clone()
|
|
103
|
-
|
|
104
|
-
this.emitter.emit('response', {
|
|
105
|
-
response: responseClone,
|
|
106
|
-
isMockedResponse: true,
|
|
107
|
-
request: interactiveRequest,
|
|
108
|
-
requestId,
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Set the "response.url" property to equal the intercepted request URL.
|
|
113
|
-
Object.defineProperty(response, 'url', {
|
|
114
|
-
writable: false,
|
|
115
|
-
enumerable: true,
|
|
116
|
-
configurable: false,
|
|
117
|
-
value: request.url,
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
responsePromise.resolve(response)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const errorWith = (reason: unknown): void => {
|
|
124
|
-
responsePromise.reject(reason)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const resolverResult = await until<unknown, Response | undefined>(
|
|
128
|
-
async () => {
|
|
129
|
-
const listenersFinished = emitAsync(this.emitter, 'request', {
|
|
130
|
-
request: interactiveRequest,
|
|
131
|
-
requestId,
|
|
61
|
+
const isRequestHandled = await handleRequest({
|
|
62
|
+
request,
|
|
63
|
+
requestId,
|
|
64
|
+
emitter: this.emitter,
|
|
65
|
+
controller,
|
|
66
|
+
onResponse: async (response) => {
|
|
67
|
+
this.logger.info('received mocked response!', {
|
|
68
|
+
response,
|
|
132
69
|
})
|
|
133
70
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
if (requestAborted.state === 'rejected') {
|
|
153
|
-
this.logger.info(
|
|
154
|
-
'request has been aborted:',
|
|
155
|
-
requestAborted.rejectionReason
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
responsePromise.reject(requestAborted.rejectionReason)
|
|
159
|
-
return responsePromise
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (resolverResult.error) {
|
|
163
|
-
this.logger.info(
|
|
164
|
-
'request listerner threw an error:',
|
|
165
|
-
resolverResult.error
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
// Treat thrown Responses as mocked responses.
|
|
169
|
-
if (resolverResult.error instanceof Response) {
|
|
170
|
-
// Treat thrown Response.error() as a request error.
|
|
171
|
-
if (isResponseError(resolverResult.error)) {
|
|
172
|
-
errorWith(createNetworkError(resolverResult.error))
|
|
173
|
-
} else {
|
|
174
|
-
// Treat the rest of thrown Responses as mocked responses.
|
|
175
|
-
respondWith(resolverResult.error)
|
|
71
|
+
if (this.emitter.listenerCount('response') > 0) {
|
|
72
|
+
this.logger.info('emitting the "response" event...')
|
|
73
|
+
|
|
74
|
+
// Await the response listeners to finish before resolving
|
|
75
|
+
// the response promise. This ensures all your logic finishes
|
|
76
|
+
// before the interceptor resolves the pending response.
|
|
77
|
+
await emitAsync(this.emitter, 'response', {
|
|
78
|
+
// Clone the mocked response for the "response" event listener.
|
|
79
|
+
// This way, the listener can read the response and not lock its body
|
|
80
|
+
// for the actual fetch consumer.
|
|
81
|
+
response: response.clone(),
|
|
82
|
+
isMockedResponse: true,
|
|
83
|
+
request,
|
|
84
|
+
requestId,
|
|
85
|
+
})
|
|
176
86
|
}
|
|
177
|
-
}
|
|
178
87
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
request,
|
|
186
|
-
requestId,
|
|
187
|
-
controller: {
|
|
188
|
-
respondWith,
|
|
189
|
-
errorWith,
|
|
190
|
-
},
|
|
88
|
+
// Set the "response.url" property to equal the intercepted request URL.
|
|
89
|
+
Object.defineProperty(response, 'url', {
|
|
90
|
+
writable: false,
|
|
91
|
+
enumerable: true,
|
|
92
|
+
configurable: false,
|
|
93
|
+
value: request.url,
|
|
191
94
|
})
|
|
192
95
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const mockedResponse = resolverResult.data
|
|
206
|
-
|
|
207
|
-
if (mockedResponse && !request.signal?.aborted) {
|
|
208
|
-
this.logger.info('received mocked response:', mockedResponse)
|
|
209
|
-
|
|
210
|
-
// Reject the request Promise on mocked "Response.error" responses.
|
|
211
|
-
if (isResponseError(mockedResponse)) {
|
|
212
|
-
this.logger.info(
|
|
213
|
-
'received a network error response, rejecting the request promise...'
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Set the cause of the request promise rejection to the
|
|
218
|
-
* network error Response instance. This differs from Undici.
|
|
219
|
-
* Undici will forward the "response.error" custom property
|
|
220
|
-
* as the rejection reason but for "Response.error()" static method
|
|
221
|
-
* "response.error" will equal to undefined, making "cause" an empty Error.
|
|
222
|
-
* @see https://github.com/nodejs/undici/blob/83cb522ae0157a19d149d72c7d03d46e34510d0a/lib/fetch/response.js#L344
|
|
223
|
-
*/
|
|
224
|
-
errorWith(createNetworkError(mockedResponse))
|
|
225
|
-
} else {
|
|
226
|
-
respondWith(mockedResponse)
|
|
227
|
-
}
|
|
96
|
+
responsePromise.resolve(response)
|
|
97
|
+
},
|
|
98
|
+
onRequestError: (response) => {
|
|
99
|
+
this.logger.info('request has errored!', { response })
|
|
100
|
+
responsePromise.reject(createNetworkError(response))
|
|
101
|
+
},
|
|
102
|
+
onError: (error) => {
|
|
103
|
+
this.logger.info('request has been aborted!', { error })
|
|
104
|
+
responsePromise.reject(error)
|
|
105
|
+
},
|
|
106
|
+
})
|
|
228
107
|
|
|
108
|
+
if (isRequestHandled) {
|
|
109
|
+
this.logger.info('request has been handled, returning mock promise...')
|
|
229
110
|
return responsePromise
|
|
230
111
|
}
|
|
231
112
|
|
|
232
|
-
this.logger.info(
|
|
113
|
+
this.logger.info(
|
|
114
|
+
'no mocked response received, performing request as-is...'
|
|
115
|
+
)
|
|
233
116
|
|
|
234
117
|
return pureFetch(request).then((response) => {
|
|
235
118
|
this.logger.info('original fetch performed', response)
|
|
@@ -242,7 +125,7 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
242
125
|
this.emitter.emit('response', {
|
|
243
126
|
response: responseClone,
|
|
244
127
|
isMockedResponse: false,
|
|
245
|
-
request
|
|
128
|
+
request,
|
|
246
129
|
requestId,
|
|
247
130
|
})
|
|
248
131
|
}
|