@mswjs/interceptors 0.41.6 → 0.41.8
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/{XMLHttpRequest-DNly-bu0.cjs → XMLHttpRequest-B11I9TBx.cjs} +10 -8
- package/lib/browser/{XMLHttpRequest-DNly-bu0.cjs.map → XMLHttpRequest-B11I9TBx.cjs.map} +1 -1
- package/lib/browser/{XMLHttpRequest-DaQ73vVt.mjs → XMLHttpRequest-DHEkSoRN.mjs} +10 -8
- package/lib/{node/XMLHttpRequest-Cj07Pos7.mjs.map → browser/XMLHttpRequest-DHEkSoRN.mjs.map} +1 -1
- package/lib/browser/{fetch-BS6cxWzo.mjs → fetch-BJwfdses.mjs} +11 -8
- package/lib/browser/{fetch-BS6cxWzo.mjs.map → fetch-BJwfdses.mjs.map} +1 -1
- package/lib/browser/{fetch-BmrYPMQ0.cjs → fetch-M8iXkZ3L.cjs} +11 -8
- package/lib/browser/{fetch-BmrYPMQ0.cjs.map → fetch-M8iXkZ3L.cjs.map} +1 -1
- package/lib/browser/{getRawRequest-BP--nUwn.mjs → getRawRequest-B1BqgWG6.mjs} +39 -15
- package/lib/browser/getRawRequest-B1BqgWG6.mjs.map +1 -0
- package/lib/browser/{getRawRequest-B6GjFKAr.cjs → getRawRequest-DdfaiPVH.cjs} +39 -15
- package/lib/browser/getRawRequest-DdfaiPVH.cjs.map +1 -0
- package/lib/browser/{handleRequest-CnDA8MVO.mjs → handleRequest-1Yqu_ho_.mjs} +3 -3
- package/lib/browser/{handleRequest-CnDA8MVO.mjs.map → handleRequest-1Yqu_ho_.mjs.map} +1 -1
- package/lib/browser/{handleRequest-BIte-8l8.cjs → handleRequest-Cz4_wmQ9.cjs} +3 -3
- package/lib/browser/{handleRequest-BIte-8l8.cjs.map → handleRequest-Cz4_wmQ9.cjs.map} +1 -1
- package/lib/browser/{hasConfigurableGlobal-B39W0thh.mjs → hasConfigurableGlobal-C8zq1MCg.mjs} +34 -23
- package/lib/browser/hasConfigurableGlobal-C8zq1MCg.mjs.map +1 -0
- package/lib/browser/{hasConfigurableGlobal-B5o6PHq3.cjs → hasConfigurableGlobal-CS7adEvV.cjs} +37 -26
- package/lib/browser/hasConfigurableGlobal-CS7adEvV.cjs.map +1 -0
- package/lib/browser/index.cjs +1 -1
- package/lib/browser/index.d.cts +2 -0
- package/lib/browser/index.d.mts +2 -0
- package/lib/browser/index.mjs +1 -1
- package/lib/browser/interceptors/WebSocket/index.cjs +2 -2
- package/lib/browser/interceptors/WebSocket/index.cjs.map +1 -1
- package/lib/browser/interceptors/WebSocket/index.mjs +2 -2
- package/lib/browser/interceptors/WebSocket/index.mjs.map +1 -1
- package/lib/browser/interceptors/XMLHttpRequest/index.cjs +4 -4
- package/lib/browser/interceptors/XMLHttpRequest/index.mjs +4 -4
- package/lib/browser/interceptors/fetch/index.cjs +4 -4
- package/lib/browser/interceptors/fetch/index.mjs +4 -4
- package/lib/browser/presets/browser.cjs +5 -5
- package/lib/browser/presets/browser.mjs +5 -5
- package/lib/node/{BatchInterceptor-0prNZ0wK.mjs → BatchInterceptor-DdMNfSUY.mjs} +2 -2
- package/lib/node/{BatchInterceptor-0prNZ0wK.mjs.map → BatchInterceptor-DdMNfSUY.mjs.map} +1 -1
- package/lib/node/{BatchInterceptor-Bo8JunSK.cjs → BatchInterceptor-Z1lwK23r.cjs} +2 -2
- package/lib/node/{BatchInterceptor-Bo8JunSK.cjs.map → BatchInterceptor-Z1lwK23r.cjs.map} +1 -1
- package/lib/node/{ClientRequest-OY5NnrOh.cjs → ClientRequest-DFs7FPNi.cjs} +8 -7
- package/lib/node/ClientRequest-DFs7FPNi.cjs.map +1 -0
- package/lib/node/{ClientRequest-BquJl6T-.mjs → ClientRequest-dRpI9v1o.mjs} +8 -7
- package/lib/node/ClientRequest-dRpI9v1o.mjs.map +1 -0
- package/lib/node/RemoteHttpInterceptor.cjs +7 -7
- package/lib/node/RemoteHttpInterceptor.mjs +7 -7
- package/lib/node/{XMLHttpRequest-Cj07Pos7.mjs → XMLHttpRequest-DhupxLHX.mjs} +10 -8
- package/lib/{browser/XMLHttpRequest-DaQ73vVt.mjs.map → node/XMLHttpRequest-DhupxLHX.mjs.map} +1 -1
- package/lib/node/{XMLHttpRequest-C1KTngEF.cjs → XMLHttpRequest-Dyi6ahGz.cjs} +10 -8
- package/lib/node/{XMLHttpRequest-C1KTngEF.cjs.map → XMLHttpRequest-Dyi6ahGz.cjs.map} +1 -1
- package/lib/node/{fetch-UxWd1aQX.mjs → fetch-BqMcbuYR.mjs} +11 -8
- package/lib/node/{fetch-UxWd1aQX.mjs.map → fetch-BqMcbuYR.mjs.map} +1 -1
- package/lib/node/{fetch-B9UiBg-M.cjs → fetch-aQJu1Gf_.cjs} +11 -8
- package/lib/node/{fetch-B9UiBg-M.cjs.map → fetch-aQJu1Gf_.cjs.map} +1 -1
- package/lib/node/{fetchUtils-Bq0Mdmkv.mjs → fetchUtils-BKJ1XmiO.mjs} +39 -15
- package/lib/node/{fetchUtils-Bq0Mdmkv.mjs.map → fetchUtils-BKJ1XmiO.mjs.map} +1 -1
- package/lib/node/{fetchUtils-BAlG765A.cjs → fetchUtils-umV5xXBy.cjs} +39 -15
- package/lib/node/{fetchUtils-BAlG765A.cjs.map → fetchUtils-umV5xXBy.cjs.map} +1 -1
- package/lib/node/{handleRequest-u6jpjgrr.mjs → handleRequest-DCLzePtS.mjs} +2 -2
- package/lib/node/{handleRequest-u6jpjgrr.mjs.map → handleRequest-DCLzePtS.mjs.map} +1 -1
- package/lib/node/{handleRequest-DUPo40Df.cjs → handleRequest-DVOthWJo.cjs} +2 -2
- package/lib/node/{handleRequest-DUPo40Df.cjs.map → handleRequest-DVOthWJo.cjs.map} +1 -1
- package/lib/node/{hasConfigurableGlobal-jN_-v3gp.mjs → hasConfigurableGlobal-BiTmog1u.mjs} +34 -23
- package/lib/node/hasConfigurableGlobal-BiTmog1u.mjs.map +1 -0
- package/lib/node/{hasConfigurableGlobal-Befl_QJD.cjs → hasConfigurableGlobal-MjY06_Ok.cjs} +37 -26
- package/lib/node/hasConfigurableGlobal-MjY06_Ok.cjs.map +1 -0
- package/lib/node/index.cjs +2 -2
- package/lib/node/index.d.cts +2 -0
- package/lib/node/index.d.mts +2 -0
- package/lib/node/index.mjs +2 -2
- package/lib/node/interceptors/ClientRequest/index.cjs +3 -3
- package/lib/node/interceptors/ClientRequest/index.mjs +3 -3
- package/lib/node/interceptors/XMLHttpRequest/index.cjs +4 -4
- package/lib/node/interceptors/XMLHttpRequest/index.mjs +4 -4
- package/lib/node/interceptors/fetch/index.cjs +4 -4
- package/lib/node/interceptors/fetch/index.mjs +4 -4
- package/lib/node/presets/node.cjs +6 -6
- package/lib/node/presets/node.mjs +6 -6
- package/package.json +1 -1
- package/src/interceptors/ClientRequest/utils/recordRawHeaders.test.ts +33 -0
- package/src/interceptors/ClientRequest/utils/recordRawHeaders.ts +19 -4
- package/src/interceptors/WebSocket/index.ts +2 -2
- package/src/interceptors/XMLHttpRequest/index.ts +4 -5
- package/src/interceptors/fetch/index.ts +13 -8
- package/src/utils/fetchUtils.test.ts +22 -0
- package/src/utils/fetchUtils.ts +61 -16
- package/src/utils/hasConfigurableGlobal.ts +1 -1
- package/src/utils/patchesRegistry.test.ts +240 -0
- package/src/utils/patchesRegistry.ts +134 -0
- package/lib/browser/getRawRequest-B6GjFKAr.cjs.map +0 -1
- package/lib/browser/getRawRequest-BP--nUwn.mjs.map +0 -1
- package/lib/browser/hasConfigurableGlobal-B39W0thh.mjs.map +0 -1
- package/lib/browser/hasConfigurableGlobal-B5o6PHq3.cjs.map +0 -1
- package/lib/node/ClientRequest-BquJl6T-.mjs.map +0 -1
- package/lib/node/ClientRequest-OY5NnrOh.cjs.map +0 -1
- package/lib/node/hasConfigurableGlobal-Befl_QJD.cjs.map +0 -1
- package/lib/node/hasConfigurableGlobal-jN_-v3gp.mjs.map +0 -1
- package/src/utils/globalUtils.test.ts +0 -144
- package/src/utils/globalsRegistry.ts +0 -107
|
@@ -14,7 +14,7 @@ import { hasConfigurableGlobal } from '../../utils/hasConfigurableGlobal'
|
|
|
14
14
|
import { FetchRequest, FetchResponse } from '../../utils/fetchUtils'
|
|
15
15
|
import { setRawRequest } from '../../getRawRequest'
|
|
16
16
|
import { isResponseError } from '../../utils/responseUtils'
|
|
17
|
-
import {
|
|
17
|
+
import { patchesRegistry } from '../../utils/patchesRegistry'
|
|
18
18
|
|
|
19
19
|
export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
20
20
|
static symbol = Symbol('fetch')
|
|
@@ -112,12 +112,15 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
112
112
|
|
|
113
113
|
// Decompress the mocked response body, if applicable.
|
|
114
114
|
const decompressedStream = decompressResponse(rawResponse)
|
|
115
|
-
const response =
|
|
116
|
-
decompressedStream
|
|
117
|
-
|
|
118
|
-
:
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
const response = new FetchResponse(
|
|
116
|
+
decompressedStream || rawResponse.body,
|
|
117
|
+
{
|
|
118
|
+
url: request.url,
|
|
119
|
+
status: rawResponse.status,
|
|
120
|
+
statusText: rawResponse.statusText,
|
|
121
|
+
headers: rawResponse.headers,
|
|
122
|
+
}
|
|
123
|
+
)
|
|
121
124
|
|
|
122
125
|
/**
|
|
123
126
|
* Undici's handling of following redirect responses.
|
|
@@ -191,7 +194,9 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
|
|
|
191
194
|
|
|
192
195
|
logger.info('patching global fetch...')
|
|
193
196
|
|
|
194
|
-
this.subscriptions.push(
|
|
197
|
+
this.subscriptions.push(
|
|
198
|
+
patchesRegistry.applyPatch(globalThis, 'fetch', () => fetchProxy)
|
|
199
|
+
)
|
|
195
200
|
|
|
196
201
|
logger.info('global fetch patched!', globalThis.fetch.name)
|
|
197
202
|
}
|
|
@@ -106,4 +106,26 @@ describe('FetchResponse', () => {
|
|
|
106
106
|
stack: error.stack,
|
|
107
107
|
})
|
|
108
108
|
})
|
|
109
|
+
|
|
110
|
+
it('sets the response URL', () => {
|
|
111
|
+
const response = new Response('hello world')
|
|
112
|
+
FetchResponse.setUrl('https://example.com/', response)
|
|
113
|
+
|
|
114
|
+
expect(response.url).toBe('https://example.com/')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('preserves a custom response URL after cloning Response', () => {
|
|
118
|
+
const response = new FetchResponse('hello world')
|
|
119
|
+
FetchResponse.setUrl('https://example.com/', response)
|
|
120
|
+
|
|
121
|
+
expect(response.clone().url).toBe('https://example.com/')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('preserves a custom response URL after cloning FetchResponse', () => {
|
|
125
|
+
const response = new FetchResponse('hello world', {
|
|
126
|
+
url: 'https://example.com/',
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
expect(response.clone().url).toBe('https://example.com/')
|
|
130
|
+
})
|
|
109
131
|
})
|
package/src/utils/fetchUtils.ts
CHANGED
|
@@ -160,6 +160,9 @@ interface UndiciResponseState {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
const kStatus = Symbol('kStatus')
|
|
164
|
+
const kUrl = Symbol('kUrl')
|
|
165
|
+
|
|
163
166
|
export class FetchResponse extends Response {
|
|
164
167
|
/**
|
|
165
168
|
* Response status codes for responses that cannot have body.
|
|
@@ -185,6 +188,33 @@ export class FetchResponse extends Response {
|
|
|
185
188
|
return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)
|
|
186
189
|
}
|
|
187
190
|
|
|
191
|
+
static setStatus(status: number, response: Response): void {
|
|
192
|
+
/**
|
|
193
|
+
* @note Undici keeps an internal "Symbol(state)" that holds
|
|
194
|
+
* the actual value of response status. Update that in Node.js.
|
|
195
|
+
*/
|
|
196
|
+
const internalState = getValueBySymbol<UndiciResponseState>(
|
|
197
|
+
'state',
|
|
198
|
+
response
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if (internalState) {
|
|
202
|
+
internalState.status = status
|
|
203
|
+
} else {
|
|
204
|
+
Object.defineProperty(response, 'status', {
|
|
205
|
+
value: status,
|
|
206
|
+
enumerable: true,
|
|
207
|
+
configurable: true,
|
|
208
|
+
writable: false,
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
Object.defineProperty(response, kStatus, {
|
|
213
|
+
value: status,
|
|
214
|
+
enumerable: false,
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
188
218
|
static setUrl(url: string | undefined, response: Response): void {
|
|
189
219
|
if (!url || url === 'about:' || !canParseUrl(url)) {
|
|
190
220
|
return
|
|
@@ -205,6 +235,11 @@ export class FetchResponse extends Response {
|
|
|
205
235
|
writable: false,
|
|
206
236
|
})
|
|
207
237
|
}
|
|
238
|
+
|
|
239
|
+
Object.defineProperty(response, kUrl, {
|
|
240
|
+
value: url,
|
|
241
|
+
enumerable: false,
|
|
242
|
+
})
|
|
208
243
|
}
|
|
209
244
|
|
|
210
245
|
/**
|
|
@@ -212,9 +247,11 @@ export class FetchResponse extends Response {
|
|
|
212
247
|
*/
|
|
213
248
|
static parseRawHeaders(rawHeaders: Array<string>): Headers {
|
|
214
249
|
const headers = new Headers()
|
|
250
|
+
|
|
215
251
|
for (let line = 0; line < rawHeaders.length; line += 2) {
|
|
216
252
|
headers.append(rawHeaders[line], rawHeaders[line + 1])
|
|
217
253
|
}
|
|
254
|
+
|
|
218
255
|
return headers
|
|
219
256
|
}
|
|
220
257
|
|
|
@@ -259,25 +296,33 @@ export class FetchResponse extends Response {
|
|
|
259
296
|
headers: init.headers,
|
|
260
297
|
})
|
|
261
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Since Node.js v24, Undici stores the Response state in an inaccessible field "#state".
|
|
301
|
+
* Forward the modified status/URL to the cloned response manually.
|
|
302
|
+
* @see https://github.com/nodejs/undici/blob/f734c87280e626c75f59aad55b65eb6a89cef392/lib/web/fetch/response.js#L242
|
|
303
|
+
*/
|
|
262
304
|
if (status !== safeStatus) {
|
|
263
|
-
|
|
264
|
-
* @note Undici keeps an internal "Symbol(state)" that holds
|
|
265
|
-
* the actual value of response status. Update that in Node.js.
|
|
266
|
-
*/
|
|
267
|
-
const state = getValueBySymbol<UndiciResponseState>('state', this)
|
|
268
|
-
|
|
269
|
-
if (state) {
|
|
270
|
-
state.status = status
|
|
271
|
-
} else {
|
|
272
|
-
Object.defineProperty(this, 'status', {
|
|
273
|
-
value: status,
|
|
274
|
-
enumerable: true,
|
|
275
|
-
configurable: true,
|
|
276
|
-
writable: false,
|
|
277
|
-
})
|
|
278
|
-
}
|
|
305
|
+
FetchResponse.setStatus(status, this)
|
|
279
306
|
}
|
|
280
307
|
|
|
281
308
|
FetchResponse.setUrl(init.url, this)
|
|
282
309
|
}
|
|
310
|
+
|
|
311
|
+
public clone() {
|
|
312
|
+
const clonedResponse = super.clone()
|
|
313
|
+
|
|
314
|
+
const customStatus = Reflect.get(this, kStatus) as number | undefined
|
|
315
|
+
|
|
316
|
+
if (customStatus) {
|
|
317
|
+
FetchResponse.setStatus(customStatus, clonedResponse)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const customUrl = Reflect.get(this, kUrl) as string | undefined
|
|
321
|
+
|
|
322
|
+
if (customUrl) {
|
|
323
|
+
FetchResponse.setUrl(customUrl, clonedResponse)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return clonedResponse
|
|
327
|
+
}
|
|
283
328
|
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { it, beforeEach, afterEach, expect, vi } from 'vitest'
|
|
2
|
+
import { patchesRegistry } from './patchesRegistry'
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
var foo: { original: boolean }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const realGlobalPrototype = Object.getPrototypeOf(global)
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
Object.defineProperty(global, 'foo', {
|
|
12
|
+
value: { original: true },
|
|
13
|
+
writable: true,
|
|
14
|
+
enumerable: true,
|
|
15
|
+
configurable: true,
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
Object.setPrototypeOf(global, realGlobalPrototype)
|
|
21
|
+
patchesRegistry.restoreAllPatches()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('replaces the global', () => {
|
|
25
|
+
patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
|
|
26
|
+
expect(global.foo).toEqual({ original: false })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('exposes the real value to the replacement callback', () => {
|
|
30
|
+
patchesRegistry.applyPatch(global, 'foo', (realValue) => {
|
|
31
|
+
expect(realValue).toEqual({ original: true })
|
|
32
|
+
return { original: false }
|
|
33
|
+
})
|
|
34
|
+
expect(global.foo).toEqual({ original: false })
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('replaces the global set on the prototype', () => {
|
|
38
|
+
function FakeGlobalScope() {}
|
|
39
|
+
FakeGlobalScope.prototype.foo = { prototype: true }
|
|
40
|
+
Object.setPrototypeOf(global, FakeGlobalScope.prototype)
|
|
41
|
+
Reflect.deleteProperty(global, 'foo')
|
|
42
|
+
|
|
43
|
+
expect(global.foo).toEqual({ prototype: true })
|
|
44
|
+
expect(FakeGlobalScope.prototype.foo).toEqual({ prototype: true })
|
|
45
|
+
|
|
46
|
+
patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
|
|
47
|
+
|
|
48
|
+
expect(global.foo).toEqual({ original: false })
|
|
49
|
+
expect(FakeGlobalScope.prototype.foo, 'Preserves prototype value').toEqual({
|
|
50
|
+
prototype: true,
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('replaces the global after it was restored', () => {
|
|
55
|
+
const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
|
|
56
|
+
original: false,
|
|
57
|
+
}))
|
|
58
|
+
expect(global.foo).toEqual({ original: false })
|
|
59
|
+
|
|
60
|
+
restoreGlobal()
|
|
61
|
+
expect(global.foo).toEqual({ original: true })
|
|
62
|
+
|
|
63
|
+
patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
|
|
64
|
+
expect(global.foo).toEqual({ original: false })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('replaces a property on a custom owner', () => {
|
|
68
|
+
const owner = { bar: { original: true } }
|
|
69
|
+
const restoreGlobal = patchesRegistry.applyPatch(owner, 'bar', () => ({
|
|
70
|
+
original: false,
|
|
71
|
+
}))
|
|
72
|
+
|
|
73
|
+
expect(owner.bar).toEqual({ original: false })
|
|
74
|
+
expect(global.foo).toEqual({ original: true })
|
|
75
|
+
|
|
76
|
+
restoreGlobal()
|
|
77
|
+
expect(owner.bar).toEqual({ original: true })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('tracks replacements per owner independently', () => {
|
|
81
|
+
const ownerA = { shared: 'a-original' }
|
|
82
|
+
const ownerB = { shared: 'b-original' }
|
|
83
|
+
|
|
84
|
+
const restoreA = patchesRegistry.applyPatch(ownerA, 'shared', () => 'a-next')
|
|
85
|
+
const restoreB = patchesRegistry.applyPatch(ownerB, 'shared', () => 'b-next')
|
|
86
|
+
|
|
87
|
+
expect(ownerA.shared).toBe('a-next')
|
|
88
|
+
expect(ownerB.shared).toBe('b-next')
|
|
89
|
+
|
|
90
|
+
restoreA()
|
|
91
|
+
expect(ownerA.shared).toBe('a-original')
|
|
92
|
+
expect(ownerB.shared).toBe('b-next')
|
|
93
|
+
|
|
94
|
+
restoreB()
|
|
95
|
+
expect(ownerB.shared).toBe('b-original')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('warns on replacing a non-existing global', () => {
|
|
99
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
100
|
+
|
|
101
|
+
patchesRegistry.applyPatch(
|
|
102
|
+
global,
|
|
103
|
+
// @ts-expect-error Intentionally invalid value.
|
|
104
|
+
'NON-EXISTING',
|
|
105
|
+
() => ({ original: false })
|
|
106
|
+
)
|
|
107
|
+
expect(console.warn).toHaveBeenCalledExactlyOnceWith(
|
|
108
|
+
'Failed to replace a global value at "NON-EXISTING": not a global value.'
|
|
109
|
+
)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('throws if replacing an already replaced global', () => {
|
|
113
|
+
patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
|
|
114
|
+
expect(global.foo).toEqual({ original: false })
|
|
115
|
+
|
|
116
|
+
expect(() =>
|
|
117
|
+
patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
|
|
118
|
+
).toThrow('Failed to replace a global value at "foo": already replaced.')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('does nothing if restoring an already restored global', () => {
|
|
122
|
+
const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
|
|
123
|
+
original: false,
|
|
124
|
+
}))
|
|
125
|
+
|
|
126
|
+
expect(global.foo).toEqual({ original: false })
|
|
127
|
+
|
|
128
|
+
restoreGlobal()
|
|
129
|
+
expect(global.foo).toEqual({ original: true })
|
|
130
|
+
|
|
131
|
+
restoreGlobal()
|
|
132
|
+
expect(global.foo).toEqual({ original: true })
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('restores the global', () => {
|
|
136
|
+
const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
|
|
137
|
+
original: false,
|
|
138
|
+
}))
|
|
139
|
+
expect(global.foo).toEqual({ original: false })
|
|
140
|
+
|
|
141
|
+
restoreGlobal()
|
|
142
|
+
expect(global.foo).toEqual({ original: true })
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('restores the global set on the prototype', () => {
|
|
146
|
+
function FakeGlobalScope() {}
|
|
147
|
+
FakeGlobalScope.prototype.foo = { prototype: true }
|
|
148
|
+
Object.setPrototypeOf(global, FakeGlobalScope.prototype)
|
|
149
|
+
Reflect.deleteProperty(global, 'foo')
|
|
150
|
+
|
|
151
|
+
expect(global.foo).toEqual({ prototype: true })
|
|
152
|
+
|
|
153
|
+
const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
|
|
154
|
+
original: false,
|
|
155
|
+
}))
|
|
156
|
+
|
|
157
|
+
expect(global.foo).toEqual({ original: false })
|
|
158
|
+
|
|
159
|
+
restoreGlobal()
|
|
160
|
+
expect(global.foo).toEqual({ prototype: true })
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('restores global to the original property descriptor', () => {
|
|
164
|
+
const descriptor: PropertyDescriptor = {
|
|
165
|
+
value: { original: true },
|
|
166
|
+
enumerable: false,
|
|
167
|
+
configurable: true,
|
|
168
|
+
writable: false,
|
|
169
|
+
}
|
|
170
|
+
Object.defineProperty(global, 'foo', descriptor)
|
|
171
|
+
expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual(descriptor)
|
|
172
|
+
|
|
173
|
+
const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
|
|
174
|
+
original: false,
|
|
175
|
+
}))
|
|
176
|
+
|
|
177
|
+
expect(global.foo).toEqual({ original: false })
|
|
178
|
+
expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual({
|
|
179
|
+
value: { original: false },
|
|
180
|
+
enumerable: true,
|
|
181
|
+
configurable: true,
|
|
182
|
+
writable: false,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
restoreGlobal()
|
|
186
|
+
|
|
187
|
+
expect(global.foo).toEqual({ original: true })
|
|
188
|
+
expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual(descriptor)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('replaces a non-configurable writable property', () => {
|
|
192
|
+
const owner = {} as { foo: { original: boolean } }
|
|
193
|
+
const descriptor: PropertyDescriptor = {
|
|
194
|
+
value: { original: true },
|
|
195
|
+
enumerable: true,
|
|
196
|
+
writable: true,
|
|
197
|
+
configurable: false,
|
|
198
|
+
}
|
|
199
|
+
Object.defineProperty(owner, 'foo', descriptor)
|
|
200
|
+
|
|
201
|
+
const restoreGlobal = patchesRegistry.applyPatch(owner, 'foo', () => ({
|
|
202
|
+
original: false,
|
|
203
|
+
}))
|
|
204
|
+
|
|
205
|
+
expect(owner.foo).toEqual({ original: false })
|
|
206
|
+
expect(
|
|
207
|
+
Object.getOwnPropertyDescriptor(owner, 'foo'),
|
|
208
|
+
'Preserves the original descriptor'
|
|
209
|
+
).toEqual({
|
|
210
|
+
value: { original: false },
|
|
211
|
+
enumerable: true,
|
|
212
|
+
configurable: false,
|
|
213
|
+
writable: true,
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
restoreGlobal()
|
|
217
|
+
|
|
218
|
+
expect(owner.foo).toEqual({ original: true })
|
|
219
|
+
expect(
|
|
220
|
+
Object.getOwnPropertyDescriptor(owner, 'foo'),
|
|
221
|
+
'Restores the original descriptor'
|
|
222
|
+
).toEqual(descriptor)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('throws when replacing a non-configurable non-writable property', () => {
|
|
226
|
+
let owner = {} as { foo: { original: boolean } }
|
|
227
|
+
const descriptor: PropertyDescriptor = {
|
|
228
|
+
value: { original: true },
|
|
229
|
+
enumerable: true,
|
|
230
|
+
writable: false,
|
|
231
|
+
configurable: false,
|
|
232
|
+
}
|
|
233
|
+
Object.defineProperty(owner, 'foo', descriptor)
|
|
234
|
+
|
|
235
|
+
expect(() =>
|
|
236
|
+
patchesRegistry.applyPatch(owner, 'foo', () => ({
|
|
237
|
+
original: false,
|
|
238
|
+
}))
|
|
239
|
+
).toThrow('Failed to patch a non-configurable non-writable property "foo"')
|
|
240
|
+
})
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { invariant } from 'outvariant'
|
|
2
|
+
|
|
3
|
+
class PatchesRegistry {
|
|
4
|
+
#replacements = new Map<object, Map<PropertyKey, () => void>>()
|
|
5
|
+
|
|
6
|
+
public applyPatch<Owner extends object, K extends keyof Owner>(
|
|
7
|
+
owner: Owner,
|
|
8
|
+
key: K,
|
|
9
|
+
getNextValue: (realValue: Owner[K]) => Owner[K]
|
|
10
|
+
): () => void {
|
|
11
|
+
const ownerReplacements = this.#replacements.get(owner)
|
|
12
|
+
|
|
13
|
+
invariant(
|
|
14
|
+
!ownerReplacements?.has(key),
|
|
15
|
+
`Failed to replace a global value at "${String(key)}": already replaced.`
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const match = getDeepPropertyDescriptor(owner, key)
|
|
19
|
+
|
|
20
|
+
if (typeof match === 'undefined') {
|
|
21
|
+
console.warn(
|
|
22
|
+
`Failed to replace a global value at "${String(key)}": not a global value.`
|
|
23
|
+
)
|
|
24
|
+
return () => {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (match.descriptor.configurable) {
|
|
28
|
+
Object.defineProperty(owner, key, {
|
|
29
|
+
value: getNextValue(owner[key]),
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
})
|
|
33
|
+
} else if (match.descriptor.writable) {
|
|
34
|
+
owner[key] = getNextValue(owner[key])
|
|
35
|
+
} else {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Failed to patch a non-configurable non-writable property "${key.toString()}"`
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const restorePatch = () => {
|
|
42
|
+
const currentReplacements = this.#replacements.get(owner)
|
|
43
|
+
|
|
44
|
+
if (!currentReplacements?.has(key)) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (match.owner === owner) {
|
|
49
|
+
/**
|
|
50
|
+
* @note Restoring non-configurable properties works as long as "writable: true"
|
|
51
|
+
* and none of the other descriptor properties except for "value" have changed.
|
|
52
|
+
*/
|
|
53
|
+
Object.defineProperty(match.owner, key, match.descriptor)
|
|
54
|
+
} else {
|
|
55
|
+
/**
|
|
56
|
+
* @todo Delete the proxy property set by the registry.
|
|
57
|
+
* If the match's owner isn't the original owner, the property is likely nested in the prototype.
|
|
58
|
+
* The registry does not meddle with those, they are left intact.
|
|
59
|
+
*/
|
|
60
|
+
Reflect.deleteProperty(owner, key)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
currentReplacements.delete(key)
|
|
64
|
+
|
|
65
|
+
if (currentReplacements.size === 0) {
|
|
66
|
+
this.#replacements.delete(owner)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (ownerReplacements) {
|
|
71
|
+
ownerReplacements.set(key, restorePatch)
|
|
72
|
+
} else {
|
|
73
|
+
this.#replacements.set(owner, new Map([[key, restorePatch]]))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return restorePatch
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public restoreAllPatches(): void {
|
|
80
|
+
const errors: Array<Error> = []
|
|
81
|
+
|
|
82
|
+
for (const [, ownerReplacements] of this.#replacements) {
|
|
83
|
+
for (const [, restorePatch] of ownerReplacements) {
|
|
84
|
+
try {
|
|
85
|
+
restorePatch()
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
errors.push(error)
|
|
89
|
+
} else {
|
|
90
|
+
throw error
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (errors.length > 0) {
|
|
97
|
+
throw new AggregateError(errors, 'FOO!')
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const patchesRegistry = new PatchesRegistry()
|
|
103
|
+
|
|
104
|
+
interface DeepDescriptorMatch {
|
|
105
|
+
owner: object
|
|
106
|
+
descriptor: PropertyDescriptor
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns a property descriptor for the given property on the owner.
|
|
111
|
+
* Walks down the prototype chain if the property does not exist on the owner.
|
|
112
|
+
* Handy for getting a global property descriptor where `globalThis` is
|
|
113
|
+
* replaced with a controlled class (e.g. ServiceWorkerGlobalScope).
|
|
114
|
+
*/
|
|
115
|
+
export function getDeepPropertyDescriptor<Owner extends object>(
|
|
116
|
+
owner: Owner,
|
|
117
|
+
key: keyof Owner
|
|
118
|
+
): DeepDescriptorMatch | undefined {
|
|
119
|
+
let currentOwner: Owner | null = owner
|
|
120
|
+
let descriptor: PropertyDescriptor | undefined
|
|
121
|
+
|
|
122
|
+
while (currentOwner) {
|
|
123
|
+
descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)
|
|
124
|
+
|
|
125
|
+
if (descriptor) {
|
|
126
|
+
return {
|
|
127
|
+
owner: currentOwner,
|
|
128
|
+
descriptor,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
currentOwner = Object.getPrototypeOf(currentOwner)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getRawRequest-B6GjFKAr.cjs","names":["request: Request","source: RequestControllerSource","DeferredPromise","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts","../../src/getRawRequest.ts"],"sourcesContent":["export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n if (status !== safeStatus) {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const state = getValueBySymbol<UndiciResponseState>('state', this)\n\n if (state) {\n state.status = status\n } else {\n Object.defineProperty(this, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n}\n","const kRawRequest = Symbol('kRawRequest')\n\n/**\n * Returns a raw request instance associated with this request.\n *\n * @example\n * interceptor.on('request', ({ request }) => {\n * const rawRequest = getRawRequest(request)\n *\n * if (rawRequest instanceof http.ClientRequest) {\n * console.log(rawRequest.rawHeaders)\n * }\n * })\n */\nexport function getRawRequest(request: Request): unknown | undefined {\n return Reflect.get(request, kRawRequest)\n}\n\nexport function setRawRequest(request: Request, rawRequest: unknown): void {\n Reflect.set(request, kRawRequest, rawRequest)\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getRawRequest-BP--nUwn.mjs","names":["request: Request","source: RequestControllerSource","#handled","symbol","#resolveProperty","bodyInit: { body?: BodyInit | null }","#setInternalProperty","authority: string"],"sources":["../../src/InterceptorError.ts","../../src/RequestController.ts","../../src/utils/canParseUrl.ts","../../src/utils/getValueBySymbol.ts","../../src/utils/fetchUtils.ts","../../src/getRawRequest.ts"],"sourcesContent":["export class InterceptorError extends Error {\n constructor(message?: string) {\n super(message)\n this.name = 'InterceptorError'\n Object.setPrototypeOf(this, InterceptorError.prototype)\n }\n}\n","import { DeferredPromise } from '@open-draft/deferred-promise'\nimport { invariant } from 'outvariant'\nimport { InterceptorError } from './InterceptorError'\n\nexport interface RequestControllerSource {\n passthrough(): void\n respondWith(response: Response): void\n errorWith(reason?: unknown): void\n}\n\nexport class RequestController {\n static PENDING = 0 as const\n static PASSTHROUGH = 1 as const\n static RESPONSE = 2 as const\n static ERROR = 3 as const\n\n public readyState: number\n\n /**\n * A Promise that resolves when this controller handles a request.\n * See `controller.readyState` for more information on the handling result.\n */\n public handled: Promise<void>\n\n constructor(\n protected readonly request: Request,\n protected readonly source: RequestControllerSource\n ) {\n this.readyState = RequestController.PENDING\n this.handled = new DeferredPromise<void>()\n }\n\n get #handled() {\n return this.handled as DeferredPromise<void>\n }\n\n /**\n * Perform this request as-is.\n */\n public async passthrough(): Promise<void> {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to passthrough the \"%s %s\" request: the request has already been handled',\n this.request.method,\n this.request.url\n )\n\n this.readyState = RequestController.PASSTHROUGH\n await this.source.passthrough()\n this.#handled.resolve()\n }\n\n /**\n * Respond to this request with the given `Response` instance.\n *\n * @example\n * controller.respondWith(new Response())\n * controller.respondWith(Response.json({ id }))\n * controller.respondWith(Response.error())\n */\n public respondWith(response: Response): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to respond to the \"%s %s\" request with \"%d %s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n response.status,\n response.statusText || 'OK',\n this.readyState\n )\n\n this.readyState = RequestController.RESPONSE\n this.#handled.resolve()\n\n /**\n * @note Although `source.respondWith()` is potentially asynchronous,\n * do NOT await it for backward-compatibility. Awaiting it will short-circuit\n * the request listener invocation as soon as a listener responds to a request.\n * Ideally, that's what we want, but that's not what we promise the user.\n */\n this.source.respondWith(response)\n }\n\n /**\n * Error this request with the given reason.\n *\n * @example\n * controller.errorWith()\n * controller.errorWith(new Error('Oops!'))\n * controller.errorWith({ message: 'Oops!'})\n */\n public errorWith(reason?: unknown): void {\n invariant.as(\n InterceptorError,\n this.readyState === RequestController.PENDING,\n 'Failed to error the \"%s %s\" request with \"%s\": the request has already been handled (%d)',\n this.request.method,\n this.request.url,\n reason?.toString(),\n this.readyState\n )\n\n this.readyState = RequestController.ERROR\n this.source.errorWith(reason)\n this.#handled.resolve()\n }\n}\n","/**\n * Returns a boolean indicating whether the given URL string\n * can be parsed into a `URL` instance.\n * A substitute for `URL.canParse()` for Node.js 18.\n */\nexport function canParseUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch (_error) {\n return false\n }\n}\n","/**\n * Returns the value behind the symbol with the given name.\n */\nexport function getValueBySymbol<T>(\n symbolName: string,\n source: object\n): T | undefined {\n const ownSymbols = Object.getOwnPropertySymbols(source)\n\n const symbol = ownSymbols.find((symbol) => {\n return symbol.description === symbolName\n })\n\n if (symbol) {\n return Reflect.get(source, symbol)\n }\n\n return\n}\n","import { canParseUrl } from './canParseUrl'\nimport { getValueBySymbol } from './getValueBySymbol'\n\ninterface UndiciRequestState extends RequestInit {}\n\ninterface FetchRequestInit extends Omit<RequestInit, 'mode'> {\n mode?: RequestMode | 'websocket' | 'webtransport'\n duplex?: 'half' | 'full'\n}\n\nexport class FetchRequest extends Request {\n static #resolveProperty<T extends keyof FetchRequestInit & keyof Request>(\n input: RequestInfo | URL,\n init: FetchRequestInit = {},\n key: T\n ): FetchRequestInit[T] {\n return init[key] ?? (input instanceof Request ? input[key] : undefined)\n }\n\n /**\n * Check if the given request method is configurable.\n * @see https://fetch.spec.whatwg.org/#methods\n */\n static isConfigurableMethod(method: string): boolean {\n return method !== 'CONNECT' && method !== 'TRACE' && method !== 'TRACK'\n }\n\n static isMethodWithBody(method: string): boolean {\n return (\n method !== 'HEAD' &&\n method !== 'GET' &&\n FetchRequest.isConfigurableMethod(method)\n )\n }\n\n /**\n * Check if the given request `mode` is configurable.\n * @see https://fetch.spec.whatwg.org/#concept-request-mode\n */\n static isConfigurableMode(mode: string): boolean {\n return (\n mode !== 'navigate' && mode !== 'websocket' && mode !== 'webtransport'\n )\n }\n\n constructor(input: URL | RequestInfo, init?: FetchRequestInit) {\n const method = FetchRequest.#resolveProperty(input, init, 'method') || 'GET'\n const safeMethod = FetchRequest.isConfigurableMethod(method)\n ? method\n : 'GET'\n\n const hasExplicitBody = init != null && 'body' in init\n\n /**\n * Only include `body` in the super init when it needs to be overridden.\n * When `input` is a Request and no explicit body is in `init`, let the\n * Request constructor handle body transfer naturally so it properly\n * marks the original request's body as consumed (bodyUsed = true).\n */\n const bodyInit: { body?: BodyInit | null } = !FetchRequest.isMethodWithBody(\n method\n )\n ? { body: undefined }\n : hasExplicitBody\n ? { body: init.body }\n : {}\n\n const mode =\n (FetchRequest.#resolveProperty(input, init, 'mode') as RequestMode) ??\n undefined\n const safeMode = FetchRequest.isConfigurableMode(mode) ? mode : undefined\n\n super(input, {\n ...(init || {}),\n method: safeMethod,\n mode: safeMode,\n // @ts-expect-error Untyped Node.js property.\n duplex:\n init?.duplex ??\n (FetchRequest.isMethodWithBody(method) ? 'half' : undefined),\n ...bodyInit,\n })\n\n if (method !== safeMethod) {\n this.#setInternalProperty('method', method)\n }\n\n if (method === 'CONNECT') {\n const url = new URL(input instanceof Request ? input.url : input)\n\n let authority: string\n\n /**\n * @note Node.js has a bug parsing raw CONNECT requests URLs like\n * \"http://127.0.0.1:1337/localhost:80\". It would treat \"localhost:\" as a protocol.\n */\n if (url.protocol === 'localhost:') {\n authority = url.href\n } else {\n authority = url.pathname.replace(/^\\/+/, '')\n }\n\n /**\n * @note Define \"url\" as a getter because Undici uses their own\n * logic to resolve the \"request.url\" property. Simply reassigning\n * its value doesn't do anything. This is a destructive action\n * but it's safe because \"CONNECT\" requests are forbidden per fetch.\n */\n Object.defineProperty(this, 'url', {\n get: () => authority,\n enumerable: true,\n configurable: true,\n })\n }\n\n if (mode != null && mode !== safeMode) {\n this.#setInternalProperty('mode', mode)\n }\n }\n\n #setInternalProperty<T extends keyof Request>(\n key: T,\n value: Request[T]\n ): void {\n const internalState = getValueBySymbol<UndiciRequestState>('state', this)\n\n if (internalState) {\n Reflect.set(internalState, key, value)\n } else {\n Object.defineProperty(this, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n}\n\nexport interface FetchResponseInit extends ResponseInit {\n url?: string\n}\n\ninterface UndiciResponseState {\n aborted: boolean\n rangeRequested: boolean\n timingAllowPassed: boolean\n requestIncludesCredentials: boolean\n type: ResponseType\n status: number\n statusText: string\n timingInfo: unknown\n cacheState: unknown\n headersList: Record<symbol, Map<string, unknown>>\n urlList: Array<URL>\n body?: {\n stream: ReadableStream\n source: unknown\n length: number\n }\n}\n\nexport class FetchResponse extends Response {\n /**\n * Response status codes for responses that cannot have body.\n * @see https://fetch.spec.whatwg.org/#statuses\n */\n static readonly STATUS_CODES_WITHOUT_BODY = [101, 103, 204, 205, 304]\n\n static readonly STATUS_CODES_WITH_REDIRECT = [301, 302, 303, 307, 308]\n\n static isConfigurableStatusCode(status: number): boolean {\n return status >= 200 && status <= 599\n }\n\n static isRedirectResponse(status: number): boolean {\n return FetchResponse.STATUS_CODES_WITH_REDIRECT.includes(status)\n }\n\n /**\n * Returns a boolean indicating whether the given response status\n * code represents a response that can have a body.\n */\n static isResponseWithBody(status: number): boolean {\n return !FetchResponse.STATUS_CODES_WITHOUT_BODY.includes(status)\n }\n\n static setUrl(url: string | undefined, response: Response): void {\n if (!url || url === 'about:' || !canParseUrl(url)) {\n return\n }\n\n const state = getValueBySymbol<UndiciResponseState>('state', response)\n\n if (state) {\n // In Undici, push the URL to the internal list of URLs.\n // This will respect the `response.url` getter logic correctly.\n state.urlList.push(new URL(url))\n } else {\n // In other libraries, redefine the `url` property directly.\n Object.defineProperty(response, 'url', {\n value: url,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n /**\n * Parses the given raw HTTP headers into a Fetch API `Headers` instance.\n */\n static parseRawHeaders(rawHeaders: Array<string>): Headers {\n const headers = new Headers()\n for (let line = 0; line < rawHeaders.length; line += 2) {\n headers.append(rawHeaders[line], rawHeaders[line + 1])\n }\n return headers\n }\n\n /**\n * Safely clones the given `Response`.\n * Coerces response clone exceptions into 500 mocked responses.\n * Handy in the environments that introduce arbitrary response\n * cloning restrictions, like \"101 Switching Protocols\" cloning\n * in \"miniflare\".\n */\n static clone(response: Response): Response {\n try {\n const clone = response.clone()\n return clone\n } catch (error) {\n return Response.json(\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : {},\n {\n status: 500,\n statusText: 'Unclonable Response',\n }\n )\n }\n }\n\n constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {\n const status = init.status ?? 200\n const safeStatus = FetchResponse.isConfigurableStatusCode(status)\n ? status\n : 200\n const finalBody = FetchResponse.isResponseWithBody(status) ? body : null\n\n super(finalBody, {\n status: safeStatus,\n statusText: init.statusText,\n headers: init.headers,\n })\n\n if (status !== safeStatus) {\n /**\n * @note Undici keeps an internal \"Symbol(state)\" that holds\n * the actual value of response status. Update that in Node.js.\n */\n const state = getValueBySymbol<UndiciResponseState>('state', this)\n\n if (state) {\n state.status = status\n } else {\n Object.defineProperty(this, 'status', {\n value: status,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n }\n\n FetchResponse.setUrl(init.url, this)\n }\n}\n","const kRawRequest = Symbol('kRawRequest')\n\n/**\n * Returns a raw request instance associated with this request.\n *\n * @example\n * interceptor.on('request', ({ request }) => {\n * const rawRequest = getRawRequest(request)\n *\n * if (rawRequest instanceof http.ClientRequest) {\n * console.log(rawRequest.rawHeaders)\n * }\n * })\n */\nexport function getRawRequest(request: Request): unknown | undefined {\n return Reflect.get(request, kRawRequest)\n}\n\nexport function setRawRequest(request: Request, rawRequest: unknown): void {\n Reflect.set(request, kRawRequest, rawRequest)\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hasConfigurableGlobal-B39W0thh.mjs","names":["#globals","errors: Array<Error>","currentOwner: Owner | null","descriptor: PropertyDescriptor | undefined"],"sources":["../../src/utils/emitAsync.ts","../../src/utils/globalsRegistry.ts","../../src/utils/hasConfigurableGlobal.ts"],"sourcesContent":["import { Emitter, EventMap } from 'strict-event-emitter'\n\n/**\n * Emits an event on the given emitter but executes\n * the listeners sequentially. This accounts for asynchronous\n * listeners (e.g. those having \"sleep\" and handling the request).\n */\nexport async function emitAsync<\n Events extends EventMap,\n EventName extends keyof Events\n>(\n emitter: Emitter<Events>,\n eventName: EventName,\n ...data: Events[EventName]\n): Promise<void> {\n const listeners = emitter.listeners(eventName)\n\n if (listeners.length === 0) {\n return\n }\n\n for (const listener of listeners) {\n await listener.apply(emitter, data)\n }\n}\n","import { invariant } from 'outvariant'\n\nclass GlobalsRegistry {\n #globals = new Map<keyof typeof globalThis, () => void>()\n\n public replaceGlobal<K extends keyof typeof globalThis>(\n key: K,\n nextValue: (typeof globalThis)[K]\n ): () => void {\n invariant(\n !this.#globals.has(key),\n `Failed to replace a global value at \"${key}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(globalThis, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${key}\": not a global value.`\n )\n return () => {}\n }\n\n Object.defineProperty(globalThis, key, {\n value: nextValue,\n enumerable: true,\n configurable: true,\n })\n\n const restoreGlobal = () => {\n if (!this.#globals.has(key)) {\n return\n }\n\n if (match.owner === globalThis) {\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the owner isn't `globalThis`, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(globalThis, key)\n }\n\n this.#globals.delete(key)\n }\n\n this.#globals.set(key, restoreGlobal)\n\n return restoreGlobal\n }\n\n public restoreAllGlobals(): void {\n const errors: Array<Error> = []\n\n for (const [, restoreGlobal] of this.#globals) {\n try {\n restoreGlobal()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const globalsRegistry = new GlobalsRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: keyof Owner\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n","import { getDeepPropertyDescriptor } from './globalsRegistry'\n\n/**\n * Returns a boolean indicating whether the given global property\n * is defined and is configurable.\n */\nexport function hasConfigurableGlobal(\n propertyName: keyof typeof globalThis\n): boolean {\n const match = getDeepPropertyDescriptor(globalThis, propertyName)\n\n // The property is not set at all.\n if (typeof match === 'undefined') {\n return false\n }\n\n const { descriptor } = match\n\n // The property is set to a getter that returns undefined.\n if (\n typeof descriptor.get === 'function' &&\n typeof descriptor.get() === 'undefined'\n ) {\n return false\n }\n\n // The property is set to a value equal to undefined.\n if (typeof descriptor.get === 'undefined' && descriptor.value == null) {\n return false\n }\n\n if (typeof descriptor.set === 'undefined' && !descriptor.configurable) {\n console.error(\n `[MSW] Failed to apply interceptor: the global \\`${propertyName}\\` property is non-configurable. This is likely an issue with your environment. If you are using a framework, please open an issue about this in their repository.`\n )\n return false\n }\n\n return true\n}\n"],"mappings":";;;;;;;;AAOA,eAAsB,UAIpB,SACA,WACA,GAAG,MACY;CACf,MAAM,YAAY,QAAQ,UAAU,UAAU;AAE9C,KAAI,UAAU,WAAW,EACvB;AAGF,MAAK,MAAM,YAAY,UACrB,OAAM,SAAS,MAAM,SAAS,KAAK;;;;;ACpBvC,IAAM,kBAAN,MAAsB;CACpB,2BAAW,IAAI,KAA0C;CAEzD,AAAO,cACL,KACA,WACY;AACZ,YACE,CAAC,MAAKA,QAAS,IAAI,IAAI,EACvB,wCAAwC,IAAI,sBAC7C;EAED,MAAM,QAAQ,0BAA0B,YAAY,IAAI;AAExD,MAAI,OAAO,UAAU,aAAa;AAChC,WAAQ,KACN,wCAAwC,IAAI,wBAC7C;AACD,gBAAa;;AAGf,SAAO,eAAe,YAAY,KAAK;GACrC,OAAO;GACP,YAAY;GACZ,cAAc;GACf,CAAC;EAEF,MAAM,sBAAsB;AAC1B,OAAI,CAAC,MAAKA,QAAS,IAAI,IAAI,CACzB;AAGF,OAAI,MAAM,UAAU,WAClB,QAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;;;;;;;AAOzD,WAAQ,eAAe,YAAY,IAAI;AAGzC,SAAKA,QAAS,OAAO,IAAI;;AAG3B,QAAKA,QAAS,IAAI,KAAK,cAAc;AAErC,SAAO;;CAGT,AAAO,oBAA0B;EAC/B,MAAMC,SAAuB,EAAE;AAE/B,OAAK,MAAM,GAAG,kBAAkB,MAAKD,QACnC,KAAI;AACF,kBAAe;WACR,OAAO;AACd,OAAI,iBAAiB,MACnB,QAAO,KAAK,MAAM;OAElB,OAAM;;AAKZ,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,QAAQ,OAAO;;;AAK9C,MAAa,kBAAkB,IAAI,iBAAiB;;;;;;;AAapD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAIE,eAA6B;CACjC,IAAIC;AAEJ,QAAO,cAAc;AACnB,eAAa,OAAO,yBAAyB,cAAc,IAAI;AAE/D,MAAI,WACF,QAAO;GACL,OAAO;GACP;GACD;AAGH,iBAAe,OAAO,eAAe,aAAa;;;;;;;;;;AClGtD,SAAgB,sBACd,cACS;CACT,MAAM,QAAQ,0BAA0B,YAAY,aAAa;AAGjE,KAAI,OAAO,UAAU,YACnB,QAAO;CAGT,MAAM,EAAE,eAAe;AAGvB,KACE,OAAO,WAAW,QAAQ,cAC1B,OAAO,WAAW,KAAK,KAAK,YAE5B,QAAO;AAIT,KAAI,OAAO,WAAW,QAAQ,eAAe,WAAW,SAAS,KAC/D,QAAO;AAGT,KAAI,OAAO,WAAW,QAAQ,eAAe,CAAC,WAAW,cAAc;AACrE,UAAQ,MACN,mDAAmD,aAAa,oKACjE;AACD,SAAO;;AAGT,QAAO"}
|