@mswjs/interceptors 0.41.5 → 0.41.7

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.
Files changed (99) hide show
  1. package/lib/browser/{XMLHttpRequest-DSAhl6_a.cjs → XMLHttpRequest-CHqOQWxg.cjs} +10 -8
  2. package/lib/browser/{XMLHttpRequest-DSAhl6_a.cjs.map → XMLHttpRequest-CHqOQWxg.cjs.map} +1 -1
  3. package/lib/browser/{XMLHttpRequest-DYyDAIJh.mjs → XMLHttpRequest-DP9ps_69.mjs} +10 -8
  4. package/lib/{node/XMLHttpRequest-DmsdUGmj.mjs.map → browser/XMLHttpRequest-DP9ps_69.mjs.map} +1 -1
  5. package/lib/browser/{fetch-BMC2en2x.cjs → fetch-CtkfZi4Z.cjs} +13 -10
  6. package/lib/browser/{fetch-BMC2en2x.cjs.map → fetch-CtkfZi4Z.cjs.map} +1 -1
  7. package/lib/browser/{fetch-D5LGhx5i.mjs → fetch-Ddj5TV04.mjs} +13 -10
  8. package/lib/browser/{fetch-D5LGhx5i.mjs.map → fetch-Ddj5TV04.mjs.map} +1 -1
  9. package/lib/browser/{getRawRequest-BEw5hEOW.mjs → getRawRequest-B1BqgWG6.mjs} +60 -15
  10. package/lib/browser/getRawRequest-B1BqgWG6.mjs.map +1 -0
  11. package/lib/browser/{getRawRequest-B9kRTvjs.cjs → getRawRequest-DdfaiPVH.cjs} +60 -15
  12. package/lib/browser/getRawRequest-DdfaiPVH.cjs.map +1 -0
  13. package/lib/browser/{handleRequest-DuUoGpfA.mjs → handleRequest-BHrC8Flw.mjs} +3 -3
  14. package/lib/browser/{handleRequest-DuUoGpfA.mjs.map → handleRequest-BHrC8Flw.mjs.map} +1 -1
  15. package/lib/browser/{handleRequest-BCtqvFSS.cjs → handleRequest-tUNU616J.cjs} +3 -3
  16. package/lib/browser/{handleRequest-BCtqvFSS.cjs.map → handleRequest-tUNU616J.cjs.map} +1 -1
  17. package/lib/browser/{hasConfigurableGlobal-B5o6PHq3.cjs → hasConfigurableGlobal-BS75Oulv.cjs} +30 -26
  18. package/lib/browser/hasConfigurableGlobal-BS75Oulv.cjs.map +1 -0
  19. package/lib/browser/{hasConfigurableGlobal-B39W0thh.mjs → hasConfigurableGlobal-FTYwno1G.mjs} +27 -23
  20. package/lib/browser/hasConfigurableGlobal-FTYwno1G.mjs.map +1 -0
  21. package/lib/browser/index.cjs +1 -1
  22. package/lib/browser/index.d.cts +10 -0
  23. package/lib/browser/index.d.mts +10 -0
  24. package/lib/browser/index.mjs +1 -1
  25. package/lib/browser/interceptors/WebSocket/index.cjs +2 -2
  26. package/lib/browser/interceptors/WebSocket/index.cjs.map +1 -1
  27. package/lib/browser/interceptors/WebSocket/index.mjs +2 -2
  28. package/lib/browser/interceptors/WebSocket/index.mjs.map +1 -1
  29. package/lib/browser/interceptors/XMLHttpRequest/index.cjs +4 -4
  30. package/lib/browser/interceptors/XMLHttpRequest/index.mjs +4 -4
  31. package/lib/browser/interceptors/fetch/index.cjs +4 -4
  32. package/lib/browser/interceptors/fetch/index.mjs +4 -4
  33. package/lib/browser/presets/browser.cjs +5 -5
  34. package/lib/browser/presets/browser.mjs +5 -5
  35. package/lib/node/{BatchInterceptor-B2YAn5m9.mjs → BatchInterceptor-DdMNfSUY.mjs} +2 -2
  36. package/lib/node/{BatchInterceptor-B2YAn5m9.mjs.map → BatchInterceptor-DdMNfSUY.mjs.map} +1 -1
  37. package/lib/node/{BatchInterceptor-AZr7q8p_.cjs → BatchInterceptor-Z1lwK23r.cjs} +2 -2
  38. package/lib/node/{BatchInterceptor-AZr7q8p_.cjs.map → BatchInterceptor-Z1lwK23r.cjs.map} +1 -1
  39. package/lib/node/{ClientRequest-BgwQG-9G.cjs → ClientRequest-DFs7FPNi.cjs} +8 -7
  40. package/lib/node/ClientRequest-DFs7FPNi.cjs.map +1 -0
  41. package/lib/node/{ClientRequest-5WjHvs9Y.mjs → ClientRequest-dRpI9v1o.mjs} +8 -7
  42. package/lib/node/ClientRequest-dRpI9v1o.mjs.map +1 -0
  43. package/lib/node/RemoteHttpInterceptor.cjs +8 -8
  44. package/lib/node/RemoteHttpInterceptor.cjs.map +1 -1
  45. package/lib/node/RemoteHttpInterceptor.mjs +8 -8
  46. package/lib/node/RemoteHttpInterceptor.mjs.map +1 -1
  47. package/lib/node/{XMLHttpRequest-DmsdUGmj.mjs → XMLHttpRequest-BgkpwNOu.mjs} +10 -8
  48. package/lib/{browser/XMLHttpRequest-DYyDAIJh.mjs.map → node/XMLHttpRequest-BgkpwNOu.mjs.map} +1 -1
  49. package/lib/node/{XMLHttpRequest-B7sloBex.cjs → XMLHttpRequest-CUbLHiq6.cjs} +10 -8
  50. package/lib/node/{XMLHttpRequest-B7sloBex.cjs.map → XMLHttpRequest-CUbLHiq6.cjs.map} +1 -1
  51. package/lib/node/{fetch-DDAGTtZi.mjs → fetch-BciBtVqm.mjs} +13 -10
  52. package/lib/node/{fetch-DDAGTtZi.mjs.map → fetch-BciBtVqm.mjs.map} +1 -1
  53. package/lib/node/{fetch-uc7pNtPg.cjs → fetch-znfXVEPe.cjs} +13 -10
  54. package/lib/node/{fetch-uc7pNtPg.cjs.map → fetch-znfXVEPe.cjs.map} +1 -1
  55. package/lib/node/{fetchUtils-BJxD7wvl.mjs → fetchUtils-BKJ1XmiO.mjs} +60 -15
  56. package/lib/node/{fetchUtils-BJxD7wvl.mjs.map → fetchUtils-BKJ1XmiO.mjs.map} +1 -1
  57. package/lib/node/{fetchUtils-Dw6kQghF.cjs → fetchUtils-umV5xXBy.cjs} +60 -15
  58. package/lib/node/{fetchUtils-Dw6kQghF.cjs.map → fetchUtils-umV5xXBy.cjs.map} +1 -1
  59. package/lib/node/{handleRequest-DGfUg9bm.mjs → handleRequest-DCLzePtS.mjs} +2 -2
  60. package/lib/node/{handleRequest-DGfUg9bm.mjs.map → handleRequest-DCLzePtS.mjs.map} +1 -1
  61. package/lib/node/{handleRequest-Dzg1d3e5.cjs → handleRequest-DVOthWJo.cjs} +2 -2
  62. package/lib/node/{handleRequest-Dzg1d3e5.cjs.map → handleRequest-DVOthWJo.cjs.map} +1 -1
  63. package/lib/node/{hasConfigurableGlobal-Befl_QJD.cjs → hasConfigurableGlobal-BtHi5OlL.cjs} +30 -26
  64. package/lib/node/hasConfigurableGlobal-BtHi5OlL.cjs.map +1 -0
  65. package/lib/node/{hasConfigurableGlobal-jN_-v3gp.mjs → hasConfigurableGlobal-Cwrs35Xo.mjs} +27 -23
  66. package/lib/node/hasConfigurableGlobal-Cwrs35Xo.mjs.map +1 -0
  67. package/lib/node/index.cjs +2 -2
  68. package/lib/node/index.d.cts +10 -0
  69. package/lib/node/index.d.mts +10 -0
  70. package/lib/node/index.mjs +2 -2
  71. package/lib/node/interceptors/ClientRequest/index.cjs +3 -3
  72. package/lib/node/interceptors/ClientRequest/index.mjs +3 -3
  73. package/lib/node/interceptors/XMLHttpRequest/index.cjs +4 -4
  74. package/lib/node/interceptors/XMLHttpRequest/index.mjs +4 -4
  75. package/lib/node/interceptors/fetch/index.cjs +4 -4
  76. package/lib/node/interceptors/fetch/index.mjs +4 -4
  77. package/lib/node/presets/node.cjs +6 -6
  78. package/lib/node/presets/node.mjs +6 -6
  79. package/package.json +1 -1
  80. package/src/RemoteHttpInterceptor.ts +1 -1
  81. package/src/interceptors/ClientRequest/utils/recordRawHeaders.test.ts +33 -0
  82. package/src/interceptors/ClientRequest/utils/recordRawHeaders.ts +19 -4
  83. package/src/interceptors/WebSocket/index.ts +2 -2
  84. package/src/interceptors/XMLHttpRequest/index.ts +4 -5
  85. package/src/interceptors/fetch/index.ts +15 -10
  86. package/src/utils/fetchUtils.test.ts +47 -1
  87. package/src/utils/fetchUtils.ts +89 -16
  88. package/src/utils/hasConfigurableGlobal.ts +1 -1
  89. package/src/utils/{globalUtils.test.ts → patchesRegistry.test.ts} +59 -19
  90. package/src/utils/patchesRegistry.ts +122 -0
  91. package/lib/browser/getRawRequest-B9kRTvjs.cjs.map +0 -1
  92. package/lib/browser/getRawRequest-BEw5hEOW.mjs.map +0 -1
  93. package/lib/browser/hasConfigurableGlobal-B39W0thh.mjs.map +0 -1
  94. package/lib/browser/hasConfigurableGlobal-B5o6PHq3.cjs.map +0 -1
  95. package/lib/node/ClientRequest-5WjHvs9Y.mjs.map +0 -1
  96. package/lib/node/ClientRequest-BgwQG-9G.cjs.map +0 -1
  97. package/lib/node/hasConfigurableGlobal-Befl_QJD.cjs.map +0 -1
  98. package/lib/node/hasConfigurableGlobal-jN_-v3gp.mjs.map +0 -1
  99. 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 { globalsRegistry } from '../../utils/globalsRegistry'
17
+ import { patchesRegistry } from '../../utils/patchesRegistry'
18
18
 
19
19
  export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
20
20
  static symbol = Symbol('fetch')
@@ -85,7 +85,7 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
85
85
  if (this.emitter.listenerCount('response') > 0) {
86
86
  this.logger.info('emitting the "response" event...')
87
87
 
88
- const responseClone = originalResponse.clone()
88
+ const responseClone = FetchResponse.clone(originalResponse)
89
89
  await emitAsync(this.emitter, 'response', {
90
90
  response: responseClone,
91
91
  isMockedResponse: false,
@@ -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 === null
117
- ? rawResponse
118
- : new FetchResponse(decompressedStream, rawResponse)
119
-
120
- FetchResponse.setUrl(request.url, response)
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.
@@ -156,7 +159,7 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
156
159
  // Clone the mocked response for the "response" event listener.
157
160
  // This way, the listener can read the response and not lock its body
158
161
  // for the actual fetch consumer.
159
- response: response.clone(),
162
+ response: FetchResponse.clone(response),
160
163
  isMockedResponse: true,
161
164
  request,
162
165
  requestId,
@@ -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(globalsRegistry.replaceGlobal('fetch', fetchProxy))
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
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { FetchRequest } from './fetchUtils'
2
+ import { FetchRequest, FetchResponse } from './fetchUtils'
3
3
 
4
4
  describe('FetchRequest', () => {
5
5
  const URL = 'https://example.com/'
@@ -83,3 +83,49 @@ describe('FetchRequest', () => {
83
83
  .toHaveProperty('body', expect.any(ReadableStream))
84
84
  })
85
85
  })
86
+
87
+ describe('FetchResponse', () => {
88
+ it('clones a regular response', () => {
89
+ const response = new Response('hello world')
90
+ expect(FetchResponse.clone(response)).toMatchObject(response)
91
+ })
92
+
93
+ it('returns a mocked 500 response if cloning throws', async () => {
94
+ const response = new Response('hello world')
95
+ const error = new Error('Cannot clone!')
96
+ response.clone = function () {
97
+ throw error
98
+ }
99
+
100
+ const clone = FetchResponse.clone(response)
101
+ expect.soft(clone.status).toBe(500)
102
+ expect.soft(clone.statusText).toBe('Unclonable Response')
103
+ await expect.soft(clone.json()).resolves.toEqual({
104
+ name: error.name,
105
+ message: error.message,
106
+ stack: error.stack,
107
+ })
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
+ })
131
+ })
@@ -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,12 +247,42 @@ 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
 
258
+ /**
259
+ * Safely clones the given `Response`.
260
+ * Coerces response clone exceptions into 500 mocked responses.
261
+ * Handy in the environments that introduce arbitrary response
262
+ * cloning restrictions, like "101 Switching Protocols" cloning
263
+ * in "miniflare".
264
+ */
265
+ static clone(response: Response): Response {
266
+ try {
267
+ const clone = response.clone()
268
+ return clone
269
+ } catch (error) {
270
+ return Response.json(
271
+ error instanceof Error
272
+ ? {
273
+ name: error.name,
274
+ message: error.message,
275
+ stack: error.stack,
276
+ }
277
+ : {},
278
+ {
279
+ status: 500,
280
+ statusText: 'Unclonable Response',
281
+ }
282
+ )
283
+ }
284
+ }
285
+
221
286
  constructor(body?: BodyInit | null, init: FetchResponseInit = {}) {
222
287
  const status = init.status ?? 200
223
288
  const safeStatus = FetchResponse.isConfigurableStatusCode(status)
@@ -231,25 +296,33 @@ export class FetchResponse extends Response {
231
296
  headers: init.headers,
232
297
  })
233
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
+ */
234
304
  if (status !== safeStatus) {
235
- /**
236
- * @note Undici keeps an internal "Symbol(state)" that holds
237
- * the actual value of response status. Update that in Node.js.
238
- */
239
- const state = getValueBySymbol<UndiciResponseState>('state', this)
240
-
241
- if (state) {
242
- state.status = status
243
- } else {
244
- Object.defineProperty(this, 'status', {
245
- value: status,
246
- enumerable: true,
247
- configurable: true,
248
- writable: false,
249
- })
250
- }
305
+ FetchResponse.setStatus(status, this)
251
306
  }
252
307
 
253
308
  FetchResponse.setUrl(init.url, this)
254
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
+ }
255
328
  }
@@ -1,4 +1,4 @@
1
- import { getDeepPropertyDescriptor } from './globalsRegistry'
1
+ import { getDeepPropertyDescriptor } from './patchesRegistry'
2
2
 
3
3
  /**
4
4
  * Returns a boolean indicating whether the given global property
@@ -1,5 +1,5 @@
1
1
  import { it, beforeEach, afterEach, expect, vi } from 'vitest'
2
- import { globalsRegistry } from './globalsRegistry'
2
+ import { patchesRegistry } from './patchesRegistry'
3
3
 
4
4
  declare global {
5
5
  var foo: { original: boolean }
@@ -13,11 +13,19 @@ beforeEach(() => {
13
13
 
14
14
  afterEach(() => {
15
15
  Object.setPrototypeOf(global, realGlobalPrototype)
16
- globalsRegistry.restoreAllGlobals()
16
+ patchesRegistry.restoreAllPatches()
17
17
  })
18
18
 
19
19
  it('replaces the global', () => {
20
- globalsRegistry.replaceGlobal('foo', { original: false })
20
+ patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
21
+ expect(global.foo).toEqual({ original: false })
22
+ })
23
+
24
+ it('exposes the real value to the replacement callback', () => {
25
+ patchesRegistry.applyPatch(global, 'foo', (realValue) => {
26
+ expect(realValue).toEqual({ original: true })
27
+ return { original: false }
28
+ })
21
29
  expect(global.foo).toEqual({ original: false })
22
30
  })
23
31
 
@@ -30,7 +38,7 @@ it('replaces the global set on the prototype', () => {
30
38
  expect(global.foo).toEqual({ prototype: true })
31
39
  expect(FakeGlobalScope.prototype.foo).toEqual({ prototype: true })
32
40
 
33
- globalsRegistry.replaceGlobal('foo', { original: false })
41
+ patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
34
42
 
35
43
  expect(global.foo).toEqual({ original: false })
36
44
  expect(FakeGlobalScope.prototype.foo, 'Preserves prototype value').toEqual({
@@ -39,25 +47,57 @@ it('replaces the global set on the prototype', () => {
39
47
  })
40
48
 
41
49
  it('replaces the global after it was restored', () => {
42
- const restoreGlobal = globalsRegistry.replaceGlobal('foo', {
50
+ const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
43
51
  original: false,
44
- })
52
+ }))
45
53
  expect(global.foo).toEqual({ original: false })
46
54
 
47
55
  restoreGlobal()
48
56
  expect(global.foo).toEqual({ original: true })
49
57
 
50
- globalsRegistry.replaceGlobal('foo', { original: false })
58
+ patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
51
59
  expect(global.foo).toEqual({ original: false })
52
60
  })
53
61
 
62
+ it('replaces a property on a custom owner', () => {
63
+ const owner = { bar: { original: true } }
64
+ const restoreGlobal = patchesRegistry.applyPatch(owner, 'bar', () => ({
65
+ original: false,
66
+ }))
67
+
68
+ expect(owner.bar).toEqual({ original: false })
69
+ expect(global.foo).toEqual({ original: true })
70
+
71
+ restoreGlobal()
72
+ expect(owner.bar).toEqual({ original: true })
73
+ })
74
+
75
+ it('tracks replacements per owner independently', () => {
76
+ const ownerA = { shared: 'a-original' }
77
+ const ownerB = { shared: 'b-original' }
78
+
79
+ const restoreA = patchesRegistry.applyPatch(ownerA, 'shared', () => 'a-next')
80
+ const restoreB = patchesRegistry.applyPatch(ownerB, 'shared', () => 'b-next')
81
+
82
+ expect(ownerA.shared).toBe('a-next')
83
+ expect(ownerB.shared).toBe('b-next')
84
+
85
+ restoreA()
86
+ expect(ownerA.shared).toBe('a-original')
87
+ expect(ownerB.shared).toBe('b-next')
88
+
89
+ restoreB()
90
+ expect(ownerB.shared).toBe('b-original')
91
+ })
92
+
54
93
  it('warns on replacing a non-existing global', () => {
55
94
  vi.spyOn(console, 'warn').mockImplementation(() => {})
56
95
 
57
- globalsRegistry.replaceGlobal(
96
+ patchesRegistry.applyPatch(
97
+ global,
58
98
  // @ts-expect-error Intentionally invalid value.
59
99
  'NON-EXISTING',
60
- { original: false }
100
+ () => ({ original: false })
61
101
  )
62
102
  expect(console.warn).toHaveBeenCalledExactlyOnceWith(
63
103
  'Failed to replace a global value at "NON-EXISTING": not a global value.'
@@ -65,18 +105,18 @@ it('warns on replacing a non-existing global', () => {
65
105
  })
66
106
 
67
107
  it('throws if replacing an already replaced global', () => {
68
- globalsRegistry.replaceGlobal('foo', { original: false })
108
+ patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
69
109
  expect(global.foo).toEqual({ original: false })
70
110
 
71
111
  expect(() =>
72
- globalsRegistry.replaceGlobal('foo', { original: false })
112
+ patchesRegistry.applyPatch(global, 'foo', () => ({ original: false }))
73
113
  ).toThrow('Failed to replace a global value at "foo": already replaced.')
74
114
  })
75
115
 
76
116
  it('does nothing if restoring an already restored global', () => {
77
- const restoreGlobal = globalsRegistry.replaceGlobal('foo', {
117
+ const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
78
118
  original: false,
79
- })
119
+ }))
80
120
 
81
121
  expect(global.foo).toEqual({ original: false })
82
122
 
@@ -88,9 +128,9 @@ it('does nothing if restoring an already restored global', () => {
88
128
  })
89
129
 
90
130
  it('restores the global', () => {
91
- const restoreGlobal = globalsRegistry.replaceGlobal('foo', {
131
+ const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
92
132
  original: false,
93
- })
133
+ }))
94
134
  expect(global.foo).toEqual({ original: false })
95
135
 
96
136
  restoreGlobal()
@@ -105,9 +145,9 @@ it('restores the global set on the prototype', () => {
105
145
 
106
146
  expect(global.foo).toEqual({ prototype: true })
107
147
 
108
- const restoreGlobal = globalsRegistry.replaceGlobal('foo', {
148
+ const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
109
149
  original: false,
110
- })
150
+ }))
111
151
 
112
152
  expect(global.foo).toEqual({ original: false })
113
153
 
@@ -125,9 +165,9 @@ it('restores global to the original property descriptor', () => {
125
165
  Object.defineProperty(global, 'foo', descriptor)
126
166
  expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual(descriptor)
127
167
 
128
- const restoreGlobal = globalsRegistry.replaceGlobal('foo', {
168
+ const restoreGlobal = patchesRegistry.applyPatch(global, 'foo', () => ({
129
169
  original: false,
130
- })
170
+ }))
131
171
 
132
172
  expect(global.foo).toEqual({ original: false })
133
173
  expect(Object.getOwnPropertyDescriptor(global, 'foo')).toEqual({
@@ -0,0 +1,122 @@
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
+ Object.defineProperty(owner, key, {
28
+ value: getNextValue(owner[key]),
29
+ enumerable: true,
30
+ configurable: true,
31
+ })
32
+
33
+ const restorePatch = () => {
34
+ const currentReplacements = this.#replacements.get(owner)
35
+
36
+ if (!currentReplacements?.has(key)) {
37
+ return
38
+ }
39
+
40
+ if (match.owner === owner) {
41
+ Object.defineProperty(match.owner, key, match.descriptor)
42
+ } else {
43
+ /**
44
+ * @todo Delete the proxy property set by the registry.
45
+ * If the match's owner isn't the original owner, the property is likely nested in the prototype.
46
+ * The registry does not meddle with those, they are left intact.
47
+ */
48
+ Reflect.deleteProperty(owner, key)
49
+ }
50
+
51
+ currentReplacements.delete(key)
52
+
53
+ if (currentReplacements.size === 0) {
54
+ this.#replacements.delete(owner)
55
+ }
56
+ }
57
+
58
+ if (ownerReplacements) {
59
+ ownerReplacements.set(key, restorePatch)
60
+ } else {
61
+ this.#replacements.set(owner, new Map([[key, restorePatch]]))
62
+ }
63
+
64
+ return restorePatch
65
+ }
66
+
67
+ public restoreAllPatches(): void {
68
+ const errors: Array<Error> = []
69
+
70
+ for (const [, ownerReplacements] of this.#replacements) {
71
+ for (const [, restorePatch] of ownerReplacements) {
72
+ try {
73
+ restorePatch()
74
+ } catch (error) {
75
+ if (error instanceof Error) {
76
+ errors.push(error)
77
+ } else {
78
+ throw error
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ if (errors.length > 0) {
85
+ throw new AggregateError(errors, 'FOO!')
86
+ }
87
+ }
88
+ }
89
+
90
+ export const patchesRegistry = new PatchesRegistry()
91
+
92
+ interface DeepDescriptorMatch {
93
+ owner: object
94
+ descriptor: PropertyDescriptor
95
+ }
96
+
97
+ /**
98
+ * Returns a property descriptor for the given property on the owner.
99
+ * Walks down the prototype chain if the property does not exist on the owner.
100
+ * Handy for getting a global property descriptor where `globalThis` is
101
+ * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).
102
+ */
103
+ export function getDeepPropertyDescriptor<Owner extends object>(
104
+ owner: Owner,
105
+ key: keyof Owner
106
+ ): DeepDescriptorMatch | undefined {
107
+ let currentOwner: Owner | null = owner
108
+ let descriptor: PropertyDescriptor | undefined
109
+
110
+ while (currentOwner) {
111
+ descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)
112
+
113
+ if (descriptor) {
114
+ return {
115
+ owner: currentOwner,
116
+ descriptor,
117
+ }
118
+ }
119
+
120
+ currentOwner = Object.getPrototypeOf(currentOwner)
121
+ }
122
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"getRawRequest-B9kRTvjs.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 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-BEw5hEOW.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 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"}