@mswjs/interceptors 0.32.1 → 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-5JMJ55U7.js → chunk-2MWIWEWV.js} +144 -113
- 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-3OJLYEWA.mjs → chunk-CU3YXMM4.mjs} +138 -107
- 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 +22 -16
- package/src/interceptors/ClientRequest/index.test.ts +2 -33
- package/src/interceptors/ClientRequest/index.ts +32 -82
- package/src/interceptors/ClientRequest/utils/recordRawHeaders.ts +170 -0
- 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-3OJLYEWA.mjs.map +0 -1
- package/lib/node/chunk-5JMJ55U7.js.map +0 -1
- package/lib/node/chunk-BFLYGQ6D.js.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/getRawFetchHeaders.test.ts +0 -50
- package/src/utils/getRawFetchHeaders.ts +0 -56
- 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
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
type HeaderTuple = [string, string]
|
|
2
|
+
type RawHeaders = Array<HeaderTuple>
|
|
3
|
+
|
|
4
|
+
const kRawHeaders = Symbol('kRawHeaders')
|
|
5
|
+
const kRestorePatches = Symbol('kRestorePatches')
|
|
6
|
+
|
|
7
|
+
function recordRawHeader(headers: Headers, args: HeaderTuple) {
|
|
8
|
+
if (Reflect.get(headers, kRawHeaders) == null) {
|
|
9
|
+
Object.defineProperty(headers, kRawHeaders, {
|
|
10
|
+
value: [],
|
|
11
|
+
enumerable: false,
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
const rawHeaders = Reflect.get(headers, kRawHeaders) as RawHeaders
|
|
15
|
+
rawHeaders.push(args)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Patch the global `Headers` class to store raw headers.
|
|
20
|
+
* This is for compatibility with `IncomingMessage.prototype.rawHeaders`.
|
|
21
|
+
*
|
|
22
|
+
* @note Node.js has their own raw headers symbol but it
|
|
23
|
+
* only records the first header name in case of multi-value headers.
|
|
24
|
+
* Any other headers are normalized before comparing. This makes it
|
|
25
|
+
* incompatible with the `rawHeaders` format.
|
|
26
|
+
*
|
|
27
|
+
* let h = new Headers()
|
|
28
|
+
* h.append('X-Custom', 'one')
|
|
29
|
+
* h.append('x-custom', 'two')
|
|
30
|
+
* h[Symbol('headers map')] // Map { 'X-Custom' => 'one, two' }
|
|
31
|
+
*/
|
|
32
|
+
export function recordRawFetchHeaders() {
|
|
33
|
+
// Prevent patching the Headers prototype multiple times.
|
|
34
|
+
if (Reflect.get(Headers, kRestorePatches)) {
|
|
35
|
+
return Reflect.get(Headers, kRestorePatches)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { Request: OriginalRequest, Response: OriginalResponse } = globalThis
|
|
39
|
+
const { set, append, delete: headersDeleteMethod } = Headers.prototype
|
|
40
|
+
|
|
41
|
+
Object.defineProperty(Headers, kRestorePatches, {
|
|
42
|
+
value: () => {
|
|
43
|
+
Headers.prototype.set = set
|
|
44
|
+
Headers.prototype.append = append
|
|
45
|
+
Headers.prototype.delete = headersDeleteMethod
|
|
46
|
+
|
|
47
|
+
globalThis.Request = OriginalRequest
|
|
48
|
+
globalThis.Response = OriginalResponse
|
|
49
|
+
},
|
|
50
|
+
enumerable: false,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
Headers = new Proxy(Headers, {
|
|
54
|
+
construct(target, args, newTarget) {
|
|
55
|
+
const headers = Reflect.construct(target, args, newTarget)
|
|
56
|
+
const initialHeaders = args[0] || []
|
|
57
|
+
const initialRawHeaders = Array.isArray(initialHeaders)
|
|
58
|
+
? initialHeaders
|
|
59
|
+
: Object.entries(initialHeaders)
|
|
60
|
+
|
|
61
|
+
// Request/Response constructors will set the symbol
|
|
62
|
+
// upon creating a new instance, using the raw developer
|
|
63
|
+
// input as the raw headers. Skip the symbol altogether
|
|
64
|
+
// in those cases because the input to Headers will be normalized.
|
|
65
|
+
if (!Reflect.has(headers, kRawHeaders)) {
|
|
66
|
+
Object.defineProperty(headers, kRawHeaders, {
|
|
67
|
+
value: initialRawHeaders,
|
|
68
|
+
enumerable: false,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return headers
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
Headers.prototype.set = new Proxy(Headers.prototype.set, {
|
|
77
|
+
apply(target, thisArg, args: HeaderTuple) {
|
|
78
|
+
recordRawHeader(thisArg, args)
|
|
79
|
+
return Reflect.apply(target, thisArg, args)
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
Headers.prototype.append = new Proxy(Headers.prototype.append, {
|
|
84
|
+
apply(target, thisArg, args: HeaderTuple) {
|
|
85
|
+
recordRawHeader(thisArg, args)
|
|
86
|
+
return Reflect.apply(target, thisArg, args)
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
Headers.prototype.delete = new Proxy(Headers.prototype.delete, {
|
|
91
|
+
apply(target, thisArg, args: [string]) {
|
|
92
|
+
const rawHeaders = Reflect.get(thisArg, kRawHeaders) as RawHeaders
|
|
93
|
+
|
|
94
|
+
if (rawHeaders) {
|
|
95
|
+
for (let index = rawHeaders.length - 1; index >= 0; index--) {
|
|
96
|
+
if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) {
|
|
97
|
+
rawHeaders.splice(index, 1)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return Reflect.apply(target, thisArg, args)
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
Request = new Proxy(Request, {
|
|
107
|
+
construct(target, args, newTarget) {
|
|
108
|
+
const request = Reflect.construct(target, args, newTarget)
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
typeof args[1] === 'object' &&
|
|
112
|
+
args[1].headers != null &&
|
|
113
|
+
!request.headers[kRawHeaders]
|
|
114
|
+
) {
|
|
115
|
+
request.headers[kRawHeaders] = inferRawHeaders(args[1].headers)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return request
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
Response = new Proxy(Response, {
|
|
123
|
+
construct(target, args, newTarget) {
|
|
124
|
+
const response = Reflect.construct(target, args, newTarget)
|
|
125
|
+
|
|
126
|
+
if (typeof args[1] === 'object' && args[1].headers != null) {
|
|
127
|
+
/**
|
|
128
|
+
* @note Pass the init argument directly because it gets
|
|
129
|
+
* transformed into a normalized Headers instance once it
|
|
130
|
+
* passes the Response constructor.
|
|
131
|
+
*/
|
|
132
|
+
response.headers[kRawHeaders] = inferRawHeaders(args[1].headers)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return response
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function restoreHeadersPrototype() {
|
|
141
|
+
if (!Reflect.get(Headers, kRestorePatches)) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
Reflect.get(Headers, kRestorePatches)()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function getRawFetchHeaders(headers: Headers): RawHeaders {
|
|
149
|
+
// Return the raw headers, if recorded (i.e. `.set()` or `.append()` was called).
|
|
150
|
+
// If no raw headers were recorded, return all the headers.
|
|
151
|
+
return Reflect.get(headers, kRawHeaders) || Array.from(headers.entries())
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Infers the raw headers from the given `HeadersInit` provided
|
|
156
|
+
* to the Request/Response constructor.
|
|
157
|
+
*
|
|
158
|
+
* If the `init.headers` is a Headers instance, use it directly.
|
|
159
|
+
* That means the headers were created standalone and already have
|
|
160
|
+
* the raw headers stored.
|
|
161
|
+
* If the `init.headers` is a HeadersInit, create a new Headers
|
|
162
|
+
* instace out of it.
|
|
163
|
+
*/
|
|
164
|
+
function inferRawHeaders(headers: HeadersInit): RawHeaders {
|
|
165
|
+
if (headers instanceof Headers) {
|
|
166
|
+
return Reflect.get(headers, kRawHeaders)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return Reflect.get(new Headers(headers), kRawHeaders)
|
|
170
|
+
}
|
|
@@ -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
|
}
|