@mswjs/interceptors 0.42.3 → 0.42.5

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 (54) hide show
  1. package/lib/browser/interceptors/WebSocket/index.js +14 -1
  2. package/lib/browser/interceptors/WebSocket/index.js.map +1 -1
  3. package/lib/browser/interceptors/fetch/web.js +1 -1
  4. package/lib/browser/presets/browser.js +1 -1
  5. package/lib/browser/{web-CdcYFjgm.js → web-DUlFZEtz.js} +58 -3
  6. package/lib/browser/web-DUlFZEtz.js.map +1 -0
  7. package/lib/node/{batch-interceptor-ByEZVih3.js → batch-interceptor-DMOfd-9x.js} +2 -2
  8. package/lib/node/{batch-interceptor-ByEZVih3.js.map → batch-interceptor-DMOfd-9x.js.map} +1 -1
  9. package/lib/node/buffer-utils-B4FUq-l2.js +17 -0
  10. package/lib/node/buffer-utils-B4FUq-l2.js.map +1 -0
  11. package/lib/node/create-request-id-DHo-fRTV.js +14 -0
  12. package/lib/node/create-request-id-DHo-fRTV.js.map +1 -0
  13. package/lib/node/{fetch-utils-Dw1PsmtX.js → fetch-utils-D-_xeRlK.js} +3 -14
  14. package/lib/node/{fetch-utils-Dw1PsmtX.js.map → fetch-utils-D-_xeRlK.js.map} +1 -1
  15. package/lib/node/{has-configurable-global-BU1sT1u4.js → has-configurable-global-CT6-QYdg.js} +2 -2
  16. package/lib/node/{has-configurable-global-BU1sT1u4.js.map → has-configurable-global-CT6-QYdg.js.map} +1 -1
  17. package/lib/node/index.d.ts +2 -243
  18. package/lib/node/index.js +6 -25
  19. package/lib/node/index.js.map +1 -1
  20. package/lib/node/{buffer-utils-BvPY1Tc-.js → interceptor-C8qRPjxG.js} +2 -16
  21. package/lib/node/interceptor-C8qRPjxG.js.map +1 -0
  22. package/lib/node/interceptors/ClientRequest/index.js +3 -3
  23. package/lib/node/interceptors/WebSocket/index.d.ts +2 -0
  24. package/lib/node/interceptors/WebSocket/index.js +635 -0
  25. package/lib/node/interceptors/WebSocket/index.js.map +1 -0
  26. package/lib/node/interceptors/XMLHttpRequest/node.js +5 -5
  27. package/lib/node/interceptors/fetch/node.js +5 -5
  28. package/lib/node/interceptors/http/index.js +2 -2
  29. package/lib/node/interceptors/net/index.d.ts +16 -0
  30. package/lib/node/interceptors/net/index.js +1 -1
  31. package/lib/node/{net-Ca8p1rIe.js → net-Bh16MP4u.js} +82 -92
  32. package/lib/node/net-Bh16MP4u.js.map +1 -0
  33. package/lib/node/patches-registry-DxR5TEc-.js +76 -0
  34. package/lib/node/patches-registry-DxR5TEc-.js.map +1 -0
  35. package/lib/node/remote-http-interceptor.js +4 -4
  36. package/lib/node/resolve-web-socket-url-CSvNPLGi.js +25 -0
  37. package/lib/node/resolve-web-socket-url-CSvNPLGi.js.map +1 -0
  38. package/lib/node/{source-CIgPng7r.js → source-DHVO1vzq.js} +327 -213
  39. package/lib/node/source-DHVO1vzq.js.map +1 -0
  40. package/lib/node/websocket-CC0nB0md.d.ts +285 -0
  41. package/package.json +5 -2
  42. package/src/interceptors/WebSocket/index.ts +4 -1
  43. package/src/interceptors/fetch/web.ts +11 -2
  44. package/src/interceptors/http/http-parser/index.ts +9 -1
  45. package/src/interceptors/http/http-parser.ts +21 -4
  46. package/src/interceptors/http/source.ts +368 -283
  47. package/src/interceptors/net/index.ts +39 -12
  48. package/src/interceptors/net/socket-controller.ts +126 -66
  49. package/src/utils/clone-response.ts +72 -0
  50. package/src/utils/internal-connection.ts +22 -0
  51. package/lib/browser/web-CdcYFjgm.js.map +0 -1
  52. package/lib/node/buffer-utils-BvPY1Tc-.js.map +0 -1
  53. package/lib/node/net-Ca8p1rIe.js.map +0 -1
  54. package/lib/node/source-CIgPng7r.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import net from 'node:net'
2
2
  import tls from 'node:tls'
3
3
  import http from 'node:http'
4
+ import { AsyncLocalStorage } from 'node:async_hooks'
4
5
  import { TypedEvent } from 'rettime'
5
6
  import {
6
7
  type NetworkConnectionOptions,
@@ -16,6 +17,33 @@ import { getTlsConnectOptions } from './utils/get-tls-connect-options'
16
17
  import { createLogger } from '../../utils/logger'
17
18
  import { patchesRegistry } from '../../utils/patches-registry'
18
19
  import { Interceptor } from '#/src/interceptor'
20
+ import '../../utils/internal-connection'
21
+
22
+ /**
23
+ * @note Initialize once across entry points and package copies. The runner
24
+ * is inert without the socket patch, so it needs no disposal lifecycle.
25
+ */
26
+ globalThis.__MSW_INTERNAL_CONNECTION_CONTEXT ??= (() => {
27
+ const context = new AsyncLocalStorage<{ consumed: boolean }>()
28
+
29
+ return {
30
+ run<T>(callback: () => T): T {
31
+ return context.run({ consumed: false }, callback)
32
+ },
33
+ consume(): boolean {
34
+ const connection = context.getStore()
35
+
36
+ if (!connection || connection.consumed) {
37
+ return false
38
+ }
39
+
40
+ // Socket events inherit this context. Consume it before connecting
41
+ // so requests from user event listeners remain intercepted.
42
+ connection.consumed = true
43
+ return true
44
+ },
45
+ }
46
+ })()
19
47
 
20
48
  declare module 'node:http' {
21
49
  interface Agent {
@@ -23,10 +51,7 @@ declare module 'node:http' {
23
51
  * @note An undocumented method backing every agent-driven request
24
52
  * (see "#stopReusingUnpatchedSockets").
25
53
  */
26
- addRequest?: (
27
- request: http.ClientRequest,
28
- ...args: Array<unknown>
29
- ) => void
54
+ addRequest?: (request: http.ClientRequest, ...args: Array<unknown>) => void
30
55
  }
31
56
  }
32
57
 
@@ -143,6 +168,13 @@ export class SocketInterceptor extends Interceptor<SocketEventMap> {
143
168
  return realSocketConnect.apply(socket, args)
144
169
  }
145
170
 
171
+ if (globalThis.__MSW_INTERNAL_CONNECTION_CONTEXT?.consume()) {
172
+ // Internal connections must bypass every socket consumer,
173
+ // including HTTP upgrade interception. Mark reconnects too.
174
+ socket[kPatched] = true
175
+ return realSocketConnect.apply(socket, args)
176
+ }
177
+
146
178
  logger.verbose('socket.connect() %o', args)
147
179
 
148
180
  /**
@@ -188,7 +220,7 @@ export class SocketInterceptor extends Interceptor<SocketEventMap> {
188
220
  () => {
189
221
  /**
190
222
  * @note Create the passthrough connection via the original
191
- * "tls.connect()" with the original connection options
223
+ * "tls.connect()" with the effective connection options
192
224
  * (the real DNS lookup and the caller's certificate
193
225
  * validation included). The latch exempts the transport
194
226
  * connect of that connection from interception.
@@ -196,10 +228,7 @@ export class SocketInterceptor extends Interceptor<SocketEventMap> {
196
228
  isCreatingPassthroughConnection = true
197
229
 
198
230
  try {
199
- return tls.connect(
200
- (realTlsConnectionOptions ??
201
- tlsConnectionOptions) as tls.ConnectionOptions
202
- )
231
+ return tls.connect(tlsConnectionOptions)
203
232
  } finally {
204
233
  isCreatingPassthroughConnection = false
205
234
  }
@@ -253,9 +282,7 @@ export class SocketInterceptor extends Interceptor<SocketEventMap> {
253
282
  * listeners to claim the connection (or once every listener
254
283
  * declines it), the controller passes it through as-is.
255
284
  */
256
- controller.awaitVerdicts(
257
- interceptor.listenerCount('connection')
258
- )
285
+ controller.awaitVerdicts(interceptor.listenerCount('connection'))
259
286
 
260
287
  interceptor.emitter.emit(
261
288
  new SocketConnectionEvent({
@@ -187,7 +187,10 @@ function toServerSocket<T extends net.Socket>(socket: T): T {
187
187
 
188
188
  // Deliver the final chunk passed to "end(chunk)", if any.
189
189
  if (finalEnd.chunk != null) {
190
- socket.push(toBuffer(finalEnd.chunk, finalEnd.encoding), finalEnd.encoding)
190
+ socket.push(
191
+ toBuffer(finalEnd.chunk, finalEnd.encoding),
192
+ finalEnd.encoding
193
+ )
191
194
  }
192
195
 
193
196
  socket.push(null)
@@ -469,6 +472,8 @@ export class TcpSocketController extends SocketController {
469
472
 
470
473
  protected pendingConnection: PromiseWithResolvers<[TcpWrap, TcpHandle]>
471
474
 
475
+ private removePassthroughSocketListeners?: () => void
476
+
472
477
  #connectionOptions?: NetworkConnectionOptions
473
478
  #retargetedConnectionOptions?: NetworkConnectionOptions &
474
479
  net.SocketConnectOpts
@@ -538,7 +543,11 @@ export class TcpSocketController extends SocketController {
538
543
  typeof args[0] === 'object' &&
539
544
  (args[0].localAddress != null || args[0].localPort != null)
540
545
  ) {
541
- args[0] = { ...args[0], localAddress: undefined, localPort: undefined }
546
+ args[0] = {
547
+ ...args[0],
548
+ localAddress: undefined,
549
+ localPort: undefined,
550
+ }
542
551
  }
543
552
 
544
553
  return Reflect.apply(target, thisArg, args)
@@ -611,21 +620,8 @@ export class TcpSocketController extends SocketController {
611
620
  * must not close the client socket).
612
621
  */
613
622
  if (this.#passthroughSocket) {
614
- this.#passthroughSocket
615
- .removeListener('connect', this.#onRealSocketConnect)
616
- .removeListener(
617
- 'connectionAttemptFailed',
618
- this.#onRealSocketConnectionAttemptFailed
619
- )
620
- .removeListener(
621
- 'connectionAttemptTimeout',
622
- this.#onRealSocketConnectionAttemptTimeout
623
- )
624
- .removeListener('data', this.#onRealSocketData)
625
- .removeListener('error', this.#onRealSocketError)
626
- .removeListener('end', this.#onRealSocketEnd)
627
- .removeListener('close', this.#onRealSocketClose)
628
- .destroy()
623
+ this.removePassthroughSocketListeners?.()
624
+ this.#passthroughSocket.destroy()
629
625
 
630
626
  this.#passthroughSocket = null
631
627
 
@@ -868,9 +864,7 @@ export class TcpSocketController extends SocketController {
868
864
  }
869
865
  }
870
866
 
871
- #removeBufferedWrite(
872
- args: Parameters<net.Socket['_writeGeneric']>
873
- ): boolean {
867
+ #removeBufferedWrite(args: Parameters<net.Socket['_writeGeneric']>): boolean {
874
868
  const index = this.#bufferedWrites.indexOf(args)
875
869
 
876
870
  if (index === -1) {
@@ -1107,6 +1101,10 @@ export class TcpSocketController extends SocketController {
1107
1101
  * the consumer reads the buffered data.
1108
1102
  */
1109
1103
  #emitClientClose(hadError: boolean): void {
1104
+ if (this.#clientCloseEmitted) {
1105
+ return
1106
+ }
1107
+
1110
1108
  if (
1111
1109
  this.#clientEndPushed &&
1112
1110
  !this.socket.readableEnded &&
@@ -1120,9 +1118,7 @@ export class TcpSocketController extends SocketController {
1120
1118
  closeDelivered = true
1121
1119
 
1122
1120
  process.nextTick(() => {
1123
- if (!this.#clientCloseEmitted) {
1124
- this.socket.emit('close', hadErrorOverride ?? hadError)
1125
- }
1121
+ this.#emitClientClose(hadErrorOverride ?? hadError)
1126
1122
  })
1127
1123
  }
1128
1124
 
@@ -1144,7 +1140,13 @@ export class TcpSocketController extends SocketController {
1144
1140
  return
1145
1141
  }
1146
1142
 
1147
- this.socket.emit('close', hadError)
1143
+ // Agent pools inspect the socket state inside their close listeners.
1144
+ // Match Node.js: a closed socket must already be destroyed and unwritable.
1145
+ this.socket.destroy()
1146
+
1147
+ if (this.#realHandleSwapped) {
1148
+ this.socket.emit('close', hadError)
1149
+ }
1148
1150
  }
1149
1151
 
1150
1152
  #onMockSocketDrain = () => {
@@ -1312,6 +1314,11 @@ export class TcpSocketController extends SocketController {
1312
1314
  // but this skips the detection cost on its every "destroyed" read).
1313
1315
  realSocket[kPatched] = true
1314
1316
 
1317
+ // The client owns half-close semantics. It may not have consumed
1318
+ // the forwarded EOF yet, so keep the real connection writable until
1319
+ // the client ends its writable side.
1320
+ realSocket.allowHalfOpen = true
1321
+
1315
1322
  if (this.socket.timeout != null) {
1316
1323
  realSocket.setTimeout(this.socket.timeout)
1317
1324
  }
@@ -1325,9 +1332,8 @@ export class TcpSocketController extends SocketController {
1325
1332
  ? this.#passthroughSocket
1326
1333
  : createRealSocket()
1327
1334
 
1328
- if (realSocket !== this.#passthroughSocket) {
1329
- this.#passthroughSocket = realSocket
1330
- }
1335
+ const isNewConnection = realSocket !== this.#passthroughSocket
1336
+ this.#passthroughSocket = realSocket
1331
1337
 
1332
1338
  if (this.#bufferedWrites.length === 0) {
1333
1339
  logger.verbose(
@@ -1365,32 +1371,15 @@ export class TcpSocketController extends SocketController {
1365
1371
  this.socket.removeListener('drain', this.#onMockSocketDrain)
1366
1372
  this.socket.on('drain', this.#onMockSocketDrain)
1367
1373
 
1368
- realSocket
1369
- .removeListener('connect', this.#onRealSocketConnect)
1370
- .removeListener(
1371
- 'connectionAttemptFailed',
1372
- this.#onRealSocketConnectionAttemptFailed
1373
- )
1374
- .removeListener(
1375
- 'connectionAttemptTimeout',
1376
- this.#onRealSocketConnectionAttemptTimeout
1377
- )
1378
- .removeListener('data', this.#onRealSocketData)
1379
- .removeListener('error', this.#onRealSocketError)
1380
- .removeListener('end', this.#onRealSocketEnd)
1381
- .removeListener('close', this.#onRealSocketClose)
1374
+ // Let Node register its pending-write "connect" listener first so
1375
+ // buffered writes flush before our listener swaps the socket handle.
1376
+ if (isNewConnection) {
1377
+ this.removePassthroughSocketListeners =
1378
+ this.addPassthroughSocketListeners(realSocket)
1382
1379
 
1383
- realSocket
1384
- .once('connect', this.#onRealSocketConnect)
1385
- .on('connectionAttemptFailed', this.#onRealSocketConnectionAttemptFailed)
1386
- .on(
1387
- 'connectionAttemptTimeout',
1388
- this.#onRealSocketConnectionAttemptTimeout
1389
- )
1390
- .on('data', this.#onRealSocketData)
1391
- .on('error', this.#onRealSocketError)
1392
- .on('end', this.#onRealSocketEnd)
1393
- .on('close', this.#onRealSocketClose)
1380
+ // The real socket may still emit errors after the client closes.
1381
+ realSocket.once('close', this.removePassthroughSocketListeners)
1382
+ }
1394
1383
 
1395
1384
  /**
1396
1385
  * @note Forward the client's half-close, unless the real handle
@@ -1409,6 +1398,41 @@ export class TcpSocketController extends SocketController {
1409
1398
  return realSocket
1410
1399
  }
1411
1400
 
1401
+ /**
1402
+ * Forward events for the lifetime of the connection, including while
1403
+ * it is idle in an agent pool. Reusing it must not add more listeners.
1404
+ */
1405
+ protected addPassthroughSocketListeners(realSocket: net.Socket): () => void {
1406
+ realSocket
1407
+ .once('connect', this.#onRealSocketConnect)
1408
+ .on('connectionAttemptFailed', this.#onRealSocketConnectionAttemptFailed)
1409
+ .on(
1410
+ 'connectionAttemptTimeout',
1411
+ this.#onRealSocketConnectionAttemptTimeout
1412
+ )
1413
+ .on('data', this.#onRealSocketData)
1414
+ .on('error', this.#onRealSocketError)
1415
+ .on('end', this.#onRealSocketEnd)
1416
+ .on('close', this.#onRealSocketClose)
1417
+
1418
+ return () => {
1419
+ realSocket
1420
+ .removeListener('connect', this.#onRealSocketConnect)
1421
+ .removeListener(
1422
+ 'connectionAttemptFailed',
1423
+ this.#onRealSocketConnectionAttemptFailed
1424
+ )
1425
+ .removeListener(
1426
+ 'connectionAttemptTimeout',
1427
+ this.#onRealSocketConnectionAttemptTimeout
1428
+ )
1429
+ .removeListener('data', this.#onRealSocketData)
1430
+ .removeListener('error', this.#onRealSocketError)
1431
+ .removeListener('end', this.#onRealSocketEnd)
1432
+ .removeListener('close', this.#onRealSocketClose)
1433
+ }
1434
+ }
1435
+
1412
1436
  /**
1413
1437
  * Create the passthrough connection to the target this controller
1414
1438
  * was retargeted to (see `reset()`). The original `createConnection`
@@ -1462,6 +1486,18 @@ export class TlsSocketController extends TcpSocketController {
1462
1486
  }
1463
1487
 
1464
1488
  protected emulateConnect(): void {
1489
+ /**
1490
+ * @note The client chooses its wire protocol when we emulate the
1491
+ * handshake. A later passthrough handshake must preserve that choice
1492
+ * instead of negotiating a different protocol for buffered writes.
1493
+ */
1494
+ if (this.#tlsConnectionOptions) {
1495
+ const alpnProtocol = this.socket._handle.getALPNNegotiatedProtocol()
1496
+ this.#tlsConnectionOptions.ALPNProtocols = alpnProtocol
1497
+ ? [alpnProtocol]
1498
+ : []
1499
+ }
1500
+
1465
1501
  super.emulateConnect()
1466
1502
 
1467
1503
  // For TLS sockets, also invoke the "secureConnect" callbacks since some consumers,
@@ -1638,20 +1674,44 @@ export class TlsSocketController extends TcpSocketController {
1638
1674
  }
1639
1675
  }
1640
1676
 
1641
- realSocket
1642
- .on('secure', () => {
1643
- this.socket.emit('secure')
1644
- })
1645
- .on('session', (...args) => {
1646
- this.socket.emit('session', ...args)
1647
- })
1648
- .on('keylog', (...args) => {
1649
- this.socket.emit('keylog', ...args)
1650
- })
1651
- .on('OCSPResponse', (...args) => {
1652
- this.socket.emit('OCSPResponse', ...args)
1653
- })
1654
-
1655
1677
  return realSocket
1656
1678
  }
1679
+
1680
+ #onRealSocketSecure = () => {
1681
+ this.socket.emit('secure')
1682
+ }
1683
+
1684
+ #onRealSocketSession = (session: Buffer) => {
1685
+ this.socket.emit('session', session)
1686
+ }
1687
+
1688
+ #onRealSocketKeylog = (line: Buffer) => {
1689
+ this.socket.emit('keylog', line)
1690
+ }
1691
+
1692
+ #onRealSocketOCSPResponse = (response: Buffer | null) => {
1693
+ this.socket.emit('OCSPResponse', response)
1694
+ }
1695
+
1696
+ protected addPassthroughSocketListeners(realSocket: net.Socket): () => void {
1697
+ const removeTcpSocketListeners = super.addPassthroughSocketListeners(
1698
+ realSocket
1699
+ )
1700
+
1701
+ realSocket
1702
+ .on('secure', this.#onRealSocketSecure)
1703
+ .on('session', this.#onRealSocketSession)
1704
+ .on('keylog', this.#onRealSocketKeylog)
1705
+ .on('OCSPResponse', this.#onRealSocketOCSPResponse)
1706
+
1707
+ return () => {
1708
+ removeTcpSocketListeners()
1709
+
1710
+ realSocket
1711
+ .removeListener('secure', this.#onRealSocketSecure)
1712
+ .removeListener('session', this.#onRealSocketSession)
1713
+ .removeListener('keylog', this.#onRealSocketKeylog)
1714
+ .removeListener('OCSPResponse', this.#onRealSocketOCSPResponse)
1715
+ }
1716
+ }
1657
1717
  }
@@ -0,0 +1,72 @@
1
+ import { FetchResponse } from './fetch-utils'
2
+ import { copyRawHeaders } from '../interceptors/ClientRequest/utils/record-raw-headers'
3
+
4
+ /** Clone for observers without letting their unread body block caller cancellation. */
5
+ export function cloneResponse(response: Response): [Response, Response] {
6
+ const clone = FetchResponse.clone(response)
7
+
8
+ if (!response.body || !clone.body) {
9
+ return [response, clone]
10
+ }
11
+
12
+ const observer = wrapResponse(clone)
13
+ const caller = wrapResponse(response, observer.cancel)
14
+
15
+ return [caller.response, observer.response]
16
+ }
17
+
18
+ function wrapResponse(
19
+ response: Response,
20
+ onCancel?: (reason: unknown) => Promise<void>
21
+ ) {
22
+ const body = response.body!
23
+ const reader = body.getReader()
24
+ const cancel = (reason: unknown) => {
25
+ return body.locked ? reader.cancel(reason) : body.cancel(reason)
26
+ }
27
+ const stream = new ReadableStream<Uint8Array>(
28
+ {
29
+ async pull(controller) {
30
+ try {
31
+ const { done, value } = await reader.read()
32
+
33
+ if (done) {
34
+ controller.close()
35
+ reader.releaseLock()
36
+ return
37
+ }
38
+
39
+ controller.enqueue(value)
40
+ } catch (error) {
41
+ controller.error(error)
42
+ reader.releaseLock()
43
+ }
44
+ },
45
+ async cancel(reason) {
46
+ try {
47
+ const cancellation = cancel(reason)
48
+
49
+ if (onCancel) {
50
+ // Caller cancellation owns both branches.
51
+ await Promise.all([cancellation, onCancel(reason)])
52
+ } else {
53
+ // An observer must not wait for the caller to consume its branch:
54
+ // Response delivery is still waiting for this listener to finish.
55
+ void cancellation.catch(() => {})
56
+ }
57
+ } finally {
58
+ reader.releaseLock()
59
+ }
60
+ },
61
+ },
62
+ { highWaterMark: 0 }
63
+ )
64
+ const wrappedResponse = new FetchResponse(stream, response)
65
+ copyRawHeaders(response.headers, wrappedResponse.headers)
66
+ Object.defineProperties(wrappedResponse, {
67
+ type: { value: response.type },
68
+ redirected: { value: response.redirected },
69
+ })
70
+
71
+ return { response: wrappedResponse, cancel }
72
+ }
@@ -0,0 +1,22 @@
1
+ interface InternalConnectionContext {
2
+ run<T>(callback: () => T): T
3
+ consume(): boolean
4
+ }
5
+
6
+ declare global {
7
+ var __MSW_INTERNAL_CONNECTION_CONTEXT: InternalConnectionContext | undefined
8
+ }
9
+
10
+ /**
11
+ * Share the Node-provided async context with the browser-built WebSocket
12
+ * entry point without importing Node modules into the browser bundle.
13
+ */
14
+ export function runAsInternalConnection<T>(callback: () => T): T {
15
+ const context = globalThis.__MSW_INTERNAL_CONNECTION_CONTEXT
16
+
17
+ if (context) {
18
+ return context.run(callback)
19
+ }
20
+
21
+ return callback()
22
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"web-CdcYFjgm.js","names":[],"sources":["../../src/interceptors/fetch/utils/create-network-error.ts","../../src/interceptors/fetch/utils/follow-redirect.ts","../../src/interceptors/fetch/utils/brotli-decompress.browser.ts","../../src/interceptors/fetch/utils/decompression.ts","../../src/interceptors/fetch/web.ts"],"sourcesContent":["export function createNetworkError(cause?: unknown) {\n return Object.assign(new TypeError('Failed to fetch'), {\n cause,\n })\n}\n","import { createNetworkError } from './create-network-error'\n\nconst REQUEST_BODY_HEADERS = [\n 'content-encoding',\n 'content-language',\n 'content-location',\n 'content-type',\n 'content-length',\n]\n\nconst kRedirectCount = Symbol('kRedirectCount')\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1210\n */\nexport async function followFetchRedirect(\n request: Request,\n response: Response\n): Promise<Response> {\n if (response.status !== 303 && request.body != null) {\n return Promise.reject(createNetworkError())\n }\n\n const requestUrl = new URL(request.url)\n\n let locationUrl: URL\n try {\n // If the location is a relative URL, use the request URL as the base URL.\n locationUrl = new URL(response.headers.get('location')!, request.url) \n } catch (error) {\n return Promise.reject(createNetworkError(error))\n }\n\n if (\n !(locationUrl.protocol === 'http:' || locationUrl.protocol === 'https:')\n ) {\n return Promise.reject(\n createNetworkError('URL scheme must be a HTTP(S) scheme')\n )\n }\n\n if (Reflect.get(request, kRedirectCount) > 20) {\n return Promise.reject(createNetworkError('redirect count exceeded'))\n }\n\n Object.defineProperty(request, kRedirectCount, {\n value: (Reflect.get(request, kRedirectCount) || 0) + 1,\n })\n\n if (\n request.mode === 'cors' &&\n (locationUrl.username || locationUrl.password) &&\n !sameOrigin(requestUrl, locationUrl)\n ) {\n return Promise.reject(\n createNetworkError('cross origin not allowed for request mode \"cors\"')\n )\n }\n\n const requestInit: RequestInit = {}\n\n if (\n ([301, 302].includes(response.status) && request.method === 'POST') ||\n (response.status === 303 && !['HEAD', 'GET'].includes(request.method))\n ) {\n requestInit.method = 'GET'\n requestInit.body = null\n\n REQUEST_BODY_HEADERS.forEach((headerName) => {\n request.headers.delete(headerName)\n })\n }\n\n if (!sameOrigin(requestUrl, locationUrl)) {\n request.headers.delete('authorization')\n request.headers.delete('proxy-authorization')\n request.headers.delete('cookie')\n request.headers.delete('host')\n }\n\n /**\n * @note Undici \"safely\" extracts the request body.\n * I suspect we cannot dispatch this request again\n * since its body has been read and the stream is locked.\n */\n\n requestInit.headers = request.headers\n const finalResponse = await fetch(new Request(locationUrl, requestInit))\n Object.defineProperty(finalResponse, 'redirected', {\n value: true,\n configurable: true,\n })\n\n return finalResponse\n}\n\n/**\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/util.js#L761\n */\nfunction sameOrigin(left: URL, right: URL): boolean {\n if (left.origin === right.origin && left.origin === 'null') {\n return true\n }\n\n if (\n left.protocol === right.protocol &&\n left.hostname === right.hostname &&\n left.port === right.port\n ) {\n return true\n }\n\n return false\n}\n","export class BrotliDecompressionStream extends TransformStream {\n constructor() {\n console.warn(\n '[Interceptors]: Brotli decompression of response streams is not supported in the browser'\n )\n\n super({\n transform(chunk, controller) {\n // Keep the stream as passthrough, it does nothing.\n controller.enqueue(chunk)\n },\n })\n }\n}\n","// Import from an internal alias that resolves to different modules\n// depending on the environment. This way, we can keep the fetch interceptor\n// intact while using different strategies for Brotli decompression.\nimport { BrotliDecompressionStream } from 'internal:brotli-decompress'\n\nclass PipelineStream extends TransformStream {\n constructor(\n transformStreams: Array<TransformStream>,\n ...strategies: Array<QueuingStrategy>\n ) {\n super({}, ...strategies)\n\n const readable = [super.readable as any, ...transformStreams].reduce(\n (readable, transform) => readable.pipeThrough(transform)\n )\n\n Object.defineProperty(this, 'readable', {\n get() {\n return readable\n },\n })\n }\n}\n\nexport function parseContentEncoding(contentEncoding: string): Array<string> {\n return contentEncoding\n .toLowerCase()\n .split(',')\n .map((coding) => coding.trim())\n}\n\nfunction createDecompressionStream(\n contentEncoding: string\n): TransformStream | null {\n if (contentEncoding === '') {\n return null\n }\n\n const codings = parseContentEncoding(contentEncoding)\n\n if (codings.length === 0) {\n return null\n }\n\n const transformers = codings.reduceRight<Array<TransformStream>>(\n (transformers, coding) => {\n if (coding === 'gzip' || coding === 'x-gzip') {\n return transformers.concat(new DecompressionStream('gzip'))\n } else if (coding === 'deflate') {\n return transformers.concat(new DecompressionStream('deflate'))\n } else if (coding === 'br') {\n return transformers.concat(new BrotliDecompressionStream())\n } else {\n transformers.length = 0\n }\n\n return transformers\n },\n []\n )\n\n return new PipelineStream(transformers)\n}\n\nexport function decompressResponse(\n response: Response\n): ReadableStream<any> | null {\n if (response.body === null) {\n return null\n }\n\n const decompressionStream = createDecompressionStream(\n response.headers.get('content-encoding') || ''\n )\n\n if (!decompressionStream) {\n return null\n }\n\n // Use `pipeTo` and return the decompression stream's readable\n // instead of `pipeThrough` because that will lock the original\n // response stream, making it unusable as the input to Response.\n response.body.pipeTo(decompressionStream.writable)\n return decompressionStream.readable\n}\n","import { until } from '@open-draft/until'\nimport { HttpResponseEvent, type HttpRequestEventMap } from '../../events/http'\nimport { RequestController } from '../../request-controller'\nimport { handleRequest } from '../../utils/handle-request'\nimport { createRequestId } from '../../create-request-id'\nimport { createNetworkError } from './utils/create-network-error'\nimport { followFetchRedirect } from './utils/follow-redirect'\nimport { decompressResponse } from './utils/decompression'\nimport { hasConfigurableGlobal } from '../../utils/has-configurable-global'\nimport { FetchResponse } from '../../utils/fetch-utils'\nimport { isResponseError } from '../../utils/response-utils'\nimport { patchesRegistry } from '../../utils/patches-registry'\nimport { copyRawHeaders } from '../ClientRequest/utils/record-raw-headers'\nimport { Interceptor } from '../../interceptor'\nimport { createLogger } from '../../utils/logger'\n\nconst logger = createLogger('fetch')\n\n/**\n * Interceptor for `fetch` requests in the browser.\n */\nexport class FetchInterceptor extends Interceptor<HttpRequestEventMap> {\n static symbol = Symbol.for('fetch-interceptor')\n\n protected predicate() {\n return hasConfigurableGlobal('fetch')\n }\n\n protected async setup() {\n logger.verbose('patching global fetch...')\n\n this.subscriptions.push(\n patchesRegistry.applyPatch(globalThis, 'fetch', (realFetch) => {\n return async (input, init) => {\n const requestId = createRequestId()\n\n /**\n * @note Resolve potentially relative request URL\n * against the present `location`. This is mainly\n * for native `fetch` in JSDOM.\n * @see https://github.com/mswjs/msw/issues/1625\n */\n const resolvedInput =\n typeof input === 'string' &&\n typeof location !== 'undefined' &&\n !URL.canParse(input)\n ? new URL(input, location.href)\n : input\n\n const request = new Request(resolvedInput, init)\n\n const responsePromise = Promise.withResolvers<Response>()\n\n const controller = new RequestController(\n request,\n {\n passthrough: async () => {\n logger.verbose('performing request as-is')\n\n /**\n * @note Clone the request instance right before performing it.\n * This preserves any modifications made to the intercepted request\n * in the \"request\" listener. This also allows the user to read the\n * request body in the \"response\" listener (otherwise \"unusable\").\n */\n const requestCloneForResponseEvent = request.clone()\n\n // Perform the intercepted request as-is.\n const [responseError, originalResponse] = await until(() =>\n realFetch(request)\n )\n\n if (responseError) {\n return responsePromise.reject(responseError)\n }\n\n logger.verbose('original fetch performed %o', originalResponse)\n\n if (this.emitter.listenerCount('response') > 0) {\n logger.verbose('emitting the \"response\" event')\n\n const responseClone = FetchResponse.clone(originalResponse)\n await this.emitter.emitAsPromise(\n new HttpResponseEvent({\n initiator: requestCloneForResponseEvent,\n request: requestCloneForResponseEvent,\n requestId,\n response: responseClone,\n responseType: 'original',\n })\n )\n }\n\n // Resolve the response promise with the original response\n // since the `fetch()` return this internal promise.\n responsePromise.resolve(originalResponse)\n },\n respondWith: async (rawResponse) => {\n // Handle mocked `Response.error()` (i.e. request errors).\n if (isResponseError(rawResponse)) {\n logger.verbose('request errored %o', {\n response: rawResponse,\n })\n responsePromise.reject(createNetworkError(rawResponse))\n return\n }\n\n // Decompress the mocked response body, if applicable.\n const decompressedStream = decompressResponse(rawResponse)\n const response = new FetchResponse(\n decompressedStream || rawResponse.body,\n {\n url: request.url,\n status: rawResponse.status,\n statusText: rawResponse.statusText,\n headers: rawResponse.headers,\n }\n )\n\n copyRawHeaders(rawResponse.headers, response.headers)\n\n /**\n * Undici's handling of following redirect responses.\n * Treat the \"manual\" redirect mode as a regular mocked response.\n * This way, the client can manually follow the redirect it receives.\n * @see https://github.com/nodejs/undici/blob/a6dac3149c505b58d2e6d068b97f4dc993da55f0/lib/web/fetch/index.js#L1173\n */\n if (FetchResponse.isRedirectResponse(response.status)) {\n // Reject the request promise if its `redirect` is set to `error`\n // and it receives a mocked redirect response.\n if (request.redirect === 'error') {\n responsePromise.reject(\n createNetworkError('unexpected redirect')\n )\n return\n }\n\n if (request.redirect === 'follow') {\n followFetchRedirect(request, response).then(\n (response) => {\n responsePromise.resolve(response)\n },\n (reason) => {\n responsePromise.reject(reason)\n }\n )\n return\n }\n }\n\n if (this.emitter.listenerCount('response') > 0) {\n logger.verbose('emitting the \"response\" event')\n\n // Await the response listeners to finish before resolving\n // the response promise. This ensures all your logic finishes\n // before the interceptor resolves the pending response.\n await this.emitter.emitAsPromise(\n new HttpResponseEvent({\n initiator: request,\n // Clone the mocked response for the \"response\" event listener.\n // This way, the listener can read the response and not lock its body\n // for the actual fetch consumer.\n response: FetchResponse.clone(response),\n responseType: 'mock',\n request,\n requestId,\n })\n )\n }\n\n responsePromise.resolve(response)\n },\n errorWith: (reason) => {\n logger.verbose('request aborted %o', { reason })\n responsePromise.reject(reason)\n },\n },\n {\n logger,\n requestId,\n }\n )\n\n logger.verbose('awaiting request resolution')\n\n logger.verbose(\n 'emitting the \"request\" event for %s listener(s)...',\n this.emitter.listenerCount('request')\n )\n\n /**\n * @note Give the consumer a chance to abort the request before\n * it is dispatched. Fetch queues the request processing as a\n * task, so a signal aborted synchronously after `fetch()` must\n * prevent the request from ever reaching the \"request\" listeners.\n * Without this, the first listener is invoked synchronously\n * within the `fetch()` call itself.\n */\n await Promise.resolve()\n\n await handleRequest({\n initiator: request,\n request,\n requestId,\n emitter: this.emitter,\n controller,\n logger,\n })\n\n return responsePromise.promise\n }\n })\n )\n\n logger.verbose('global fetch patched: %s', globalThis.fetch.name)\n }\n}\n"],"mappings":";;;;;;AAAA,SAAgB,mBAAmB,OAAiB;CAClD,OAAO,OAAO,uBAAO,IAAI,UAAU,iBAAiB,GAAG,EACrD,MACF,CAAC;AACH;;;ACFA,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,iBAAiB,OAAO,gBAAgB;;;;AAK9C,eAAsB,oBACpB,SACA,UACmB;CACnB,IAAI,SAAS,WAAW,OAAO,QAAQ,QAAQ,MAC7C,OAAO,QAAQ,OAAO,mBAAmB,CAAC;CAG5C,MAAM,aAAa,IAAI,IAAI,QAAQ,GAAG;CAEtC,IAAI;CACJ,IAAI;EAEF,cAAc,IAAI,IAAI,SAAS,QAAQ,IAAI,UAAU,GAAI,QAAQ,GAAG;CACtE,SAAS,OAAO;EACd,OAAO,QAAQ,OAAO,mBAAmB,KAAK,CAAC;CACjD;CAEA,IACE,EAAE,YAAY,aAAa,WAAW,YAAY,aAAa,WAE/D,OAAO,QAAQ,OACb,mBAAmB,qCAAqC,CAC1D;CAGF,IAAI,QAAQ,IAAI,SAAS,cAAc,IAAI,IACzC,OAAO,QAAQ,OAAO,mBAAmB,yBAAyB,CAAC;CAGrE,OAAO,eAAe,SAAS,gBAAgB,EAC7C,QAAQ,QAAQ,IAAI,SAAS,cAAc,KAAK,KAAK,EACvD,CAAC;CAED,IACE,QAAQ,SAAS,WAChB,YAAY,YAAY,YAAY,aACrC,CAAC,WAAW,YAAY,WAAW,GAEnC,OAAO,QAAQ,OACb,mBAAmB,oDAAkD,CACvE;CAGF,MAAM,cAA2B,CAAC;CAElC,IACG,CAAC,KAAK,GAAG,CAAC,CAAC,SAAS,SAAS,MAAM,KAAK,QAAQ,WAAW,UAC3D,SAAS,WAAW,OAAO,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,SAAS,QAAQ,MAAM,GACpE;EACA,YAAY,SAAS;EACrB,YAAY,OAAO;EAEnB,qBAAqB,SAAS,eAAe;GAC3C,QAAQ,QAAQ,OAAO,UAAU;EACnC,CAAC;CACH;CAEA,IAAI,CAAC,WAAW,YAAY,WAAW,GAAG;EACxC,QAAQ,QAAQ,OAAO,eAAe;EACtC,QAAQ,QAAQ,OAAO,qBAAqB;EAC5C,QAAQ,QAAQ,OAAO,QAAQ;EAC/B,QAAQ,QAAQ,OAAO,MAAM;CAC/B;;;;;;CAQA,YAAY,UAAU,QAAQ;CAC9B,MAAM,gBAAgB,MAAM,MAAM,IAAI,QAAQ,aAAa,WAAW,CAAC;CACvE,OAAO,eAAe,eAAe,cAAc;EACjD,OAAO;EACP,cAAc;CAChB,CAAC;CAED,OAAO;AACT;;;;AAKA,SAAS,WAAW,MAAW,OAAqB;CAClD,IAAI,KAAK,WAAW,MAAM,UAAU,KAAK,WAAW,QAClD,OAAO;CAGT,IACE,KAAK,aAAa,MAAM,YACxB,KAAK,aAAa,MAAM,YACxB,KAAK,SAAS,MAAM,MAEpB,OAAO;CAGT,OAAO;AACT;;;ACjHA,IAAa,4BAAb,cAA+C,gBAAgB;CAC7D,cAAc;EACZ,QAAQ,KACN,0FACF;EAEA,MAAM,EACJ,UAAU,OAAO,YAAY;GAE3B,WAAW,QAAQ,KAAK;EAC1B,EACF,CAAC;CACH;AACF;;;ACRA,IAAM,iBAAN,cAA6B,gBAAgB;CAC3C,YACE,kBACA,GAAG,YACH;EACA,MAAM,CAAC,GAAG,GAAG,UAAU;EAEvB,MAAM,WAAW,CAAC,MAAM,UAAiB,GAAG,gBAAgB,CAAC,CAAC,QAC3D,UAAU,cAAc,SAAS,YAAY,SAAS,CACzD;EAEA,OAAO,eAAe,MAAM,YAAY,EACtC,MAAM;GACJ,OAAO;EACT,EACF,CAAC;CACH;AACF;AAEA,SAAgB,qBAAqB,iBAAwC;CAC3E,OAAO,gBACJ,YAAY,CAAC,CACb,MAAM,GAAG,CAAC,CACV,KAAK,WAAW,OAAO,KAAK,CAAC;AAClC;AAEA,SAAS,0BACP,iBACwB;CACxB,IAAI,oBAAoB,IACtB,OAAO;CAGT,MAAM,UAAU,qBAAqB,eAAe;CAEpD,IAAI,QAAQ,WAAW,GACrB,OAAO;CAoBT,OAAO,IAAI,eAjBU,QAAQ,aAC1B,cAAc,WAAW;EACxB,IAAI,WAAW,UAAU,WAAW,UAClC,OAAO,aAAa,OAAO,IAAI,oBAAoB,MAAM,CAAC;OACrD,IAAI,WAAW,WACpB,OAAO,aAAa,OAAO,IAAI,oBAAoB,SAAS,CAAC;OACxD,IAAI,WAAW,MACpB,OAAO,aAAa,OAAO,IAAI,0BAA0B,CAAC;OAE1D,aAAa,SAAS;EAGxB,OAAO;CACT,GACA,CAAC,CAGuB,CAAY;AACxC;AAEA,SAAgB,mBACd,UAC4B;CAC5B,IAAI,SAAS,SAAS,MACpB,OAAO;CAGT,MAAM,sBAAsB,0BAC1B,SAAS,QAAQ,IAAI,kBAAkB,KAAK,EAC9C;CAEA,IAAI,CAAC,qBACH,OAAO;CAMT,SAAS,KAAK,OAAO,oBAAoB,QAAQ;CACjD,OAAO,oBAAoB;AAC7B;;;ACpEA,MAAM,SAAS,aAAa,OAAO;;;;AAKnC,IAAa,mBAAb,cAAsC,YAAiC;;EACrD,KAAA,SAAA,OAAO,IAAI,mBAAmB;;CAE9C,YAAsB;EACpB,OAAO,sBAAsB,OAAO;CACtC;CAEA,MAAgB,QAAQ;EACtB,OAAO,QAAQ,0BAA0B;EAEzC,KAAK,cAAc,KACjB,gBAAgB,WAAW,YAAY,UAAU,cAAc;GAC7D,OAAO,OAAO,OAAO,SAAS;IAC5B,MAAM,YAAY,gBAAgB;;;;;;;IAQlC,MAAM,gBACJ,OAAO,UAAU,YACjB,OAAO,aAAa,eACpB,CAAC,IAAI,SAAS,KAAK,IACf,IAAI,IAAI,OAAO,SAAS,IAAI,IAC5B;IAEN,MAAM,UAAU,IAAI,QAAQ,eAAe,IAAI;IAE/C,MAAM,kBAAkB,QAAQ,cAAwB;IAExD,MAAM,aAAa,IAAI,kBACrB,SACA;KACE,aAAa,YAAY;MACvB,OAAO,QAAQ,0BAA0B;;;;;;;MAQzC,MAAM,+BAA+B,QAAQ,MAAM;MAGnD,MAAM,CAAC,eAAe,oBAAoB,MAAM,YAC9C,UAAU,OAAO,CACnB;MAEA,IAAI,eACF,OAAO,gBAAgB,OAAO,aAAa;MAG7C,OAAO,QAAQ,+BAA+B,gBAAgB;MAE9D,IAAI,KAAK,QAAQ,cAAc,UAAU,IAAI,GAAG;OAC9C,OAAO,QAAQ,iCAA+B;OAE9C,MAAM,gBAAgB,cAAc,MAAM,gBAAgB;OAC1D,MAAM,KAAK,QAAQ,cACjB,IAAI,kBAAkB;QACpB,WAAW;QACX,SAAS;QACT;QACA,UAAU;QACV,cAAc;OAChB,CAAC,CACH;MACF;MAIA,gBAAgB,QAAQ,gBAAgB;KAC1C;KACA,aAAa,OAAO,gBAAgB;MAElC,IAAI,gBAAgB,WAAW,GAAG;OAChC,OAAO,QAAQ,sBAAsB,EACnC,UAAU,YACZ,CAAC;OACD,gBAAgB,OAAO,mBAAmB,WAAW,CAAC;OACtD;MACF;MAIA,MAAM,WAAW,IAAI,cADM,mBAAmB,WAE3B,KAAK,YAAY,MAClC;OACE,KAAK,QAAQ;OACb,QAAQ,YAAY;OACpB,YAAY,YAAY;OACxB,SAAS,YAAY;MACvB,CACF;MAEA,eAAe,YAAY,SAAS,SAAS,OAAO;;;;;;;MAQpD,IAAI,cAAc,mBAAmB,SAAS,MAAM,GAAG;OAGrD,IAAI,QAAQ,aAAa,SAAS;QAChC,gBAAgB,OACd,mBAAmB,qBAAqB,CAC1C;QACA;OACF;OAEA,IAAI,QAAQ,aAAa,UAAU;QACjC,oBAAoB,SAAS,QAAQ,CAAC,CAAC,MACpC,aAAa;SACZ,gBAAgB,QAAQ,QAAQ;QAClC,IACC,WAAW;SACV,gBAAgB,OAAO,MAAM;QAC/B,CACF;QACA;OACF;MACF;MAEA,IAAI,KAAK,QAAQ,cAAc,UAAU,IAAI,GAAG;OAC9C,OAAO,QAAQ,iCAA+B;OAK9C,MAAM,KAAK,QAAQ,cACjB,IAAI,kBAAkB;QACpB,WAAW;QAIX,UAAU,cAAc,MAAM,QAAQ;QACtC,cAAc;QACd;QACA;OACF,CAAC,CACH;MACF;MAEA,gBAAgB,QAAQ,QAAQ;KAClC;KACA,YAAY,WAAW;MACrB,OAAO,QAAQ,sBAAsB,EAAE,OAAO,CAAC;MAC/C,gBAAgB,OAAO,MAAM;KAC/B;IACF,GACA;KACE;KACA;IACF,CACF;IAEA,OAAO,QAAQ,6BAA6B;IAE5C,OAAO,QACL,wDACA,KAAK,QAAQ,cAAc,SAAS,CACtC;;;;;;;;;IAUA,MAAM,QAAQ,QAAQ;IAEtB,MAAM,cAAc;KAClB,WAAW;KACX;KACA;KACA,SAAS,KAAK;KACd;KACA;IACF,CAAC;IAED,OAAO,gBAAgB;GACzB;EACF,CAAC,CACH;EAEA,OAAO,QAAQ,4BAA4B,WAAW,MAAM,IAAI;CAClE;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"buffer-utils-BvPY1Tc-.js","names":["#owners","#getLoggerNamespace"],"sources":["../../src/disposable.ts","../../src/utils/logger.ts","../../src/interceptor.ts","../../src/utils/buffer-utils.ts"],"sourcesContent":["export type DisposableSubscription = () => void\n\nexport class Disposable {\n protected subscriptions: Array<DisposableSubscription> = []\n\n public dispose() {\n let subscription: DisposableSubscription | undefined\n\n while ((subscription = this.subscriptions.pop())) {\n subscription()\n }\n }\n}\n","import debug from 'debug'\n\nexport type LogLevel = 'default' | 'verbose'\n\nexport interface Logger {\n info(message: string, ...positionals: Array<unknown>): void\n verbose(message: string, ...positionals: Array<unknown>): void\n isEnabled(level: LogLevel): boolean\n}\n\nconst LOG_TIMESTAMP_REGEXP = /\\d{2}:\\d{2}:\\d{2}\\.\\d{3}/\n\nfunction normalizeNamespace(namespace: string): string {\n return namespace\n .split(':')\n .map((segment) => {\n return segment\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-|-$/g, '')\n .toLowerCase()\n })\n .filter(Boolean)\n .join(':')\n}\n\nfunction getTimestamp(): string {\n return new Date().toISOString().slice(11, 23)\n}\n\nasync function readBody(message: Request | Response): Promise<string | null> {\n if (message.body == null) {\n return null\n }\n\n try {\n return await message.clone().text()\n } catch {\n return null\n }\n}\n\nfunction formatHeaders(headers: Headers): Array<string> {\n return Array.from(headers.entries()).map(([name, value]) => {\n return `${name}: ${value}`\n })\n}\n\nasync function formatHttpMessage(\n startLine: string,\n message: Request | Response\n): Promise<string> {\n const lines = [startLine, ...formatHeaders(message.headers)]\n const body = await readBody(message)\n\n lines.push('', body ?? '')\n\n return lines.join('\\n')\n}\n\nexport async function formatRequest(request: Request): Promise<string> {\n return formatHttpMessage(`${request.method} ${request.url}`, request)\n}\n\nexport async function formatResponse(response: Response): Promise<string> {\n const statusText = response.statusText ? ` ${response.statusText}` : ''\n return formatHttpMessage(\n `HTTP ${response.status}${statusText}`,\n response\n )\n}\n\nfunction formatLogArguments(arguments_: Array<unknown>): void {\n const message = arguments_[0]\n\n if (typeof message === 'string') {\n const messageWithoutDebugTimestamp = message.replace(\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z /,\n ''\n )\n const timestampMatch = messageWithoutDebugTimestamp.match(\n LOG_TIMESTAMP_REGEXP\n )\n\n if (!timestampMatch || timestampMatch.index === undefined) {\n arguments_[0] = messageWithoutDebugTimestamp\n return\n }\n\n const messagePrefix = messageWithoutDebugTimestamp\n .slice(0, timestampMatch.index)\n .trim()\n const messageBody = messageWithoutDebugTimestamp\n .slice(timestampMatch.index + timestampMatch[0].length)\n .trimStart()\n\n arguments_[0] = `${timestampMatch[0]} ${messagePrefix} ${messageBody}`\n }\n}\n\nfunction useConciseTimestamp(logger: debug.Debugger): void {\n logger.log = (...arguments_) => {\n formatLogArguments(arguments_)\n debug.log(...arguments_)\n }\n}\n\nfunction isVerboseLoggingEnabled(): boolean {\n if (typeof process !== 'undefined' && process.env.DEBUG_LEVEL === 'verbose') {\n return true\n }\n\n /**\n * @note Consult the localStorage only in browser-like environments.\n * In Node.js 26+, reading \"globalThis.localStorage\" without the\n * \"--localstorage-file\" flag set emits an experimental warning\n * (a try/catch cannot suppress it). Node.js consumers control the\n * log level via the \"DEBUG_LEVEL\" environment variable above.\n */\n if (typeof document === 'undefined') {\n return false\n }\n\n try {\n return globalThis.localStorage?.getItem('debugLevel') === 'verbose'\n } catch {\n return false\n }\n}\n\nexport function createLogger(namespace: string): Logger {\n const normalizedNamespace = normalizeNamespace(namespace)\n const logger = debug(`interceptors:${normalizedNamespace}`)\n Reflect.set(logger, 'useColors', true)\n useConciseTimestamp(logger)\n\n return {\n info(message, ...positionals) {\n logger(`${getTimestamp()} ${message}`, ...positionals)\n },\n verbose(message, ...positionals) {\n if (!isVerboseLoggingEnabled()) {\n return\n }\n\n logger(`${getTimestamp()} ${message}`, ...positionals)\n },\n isEnabled(level) {\n return (\n logger.enabled && (level === 'default' || isVerboseLoggingEnabled())\n )\n },\n }\n}\n","import { Emitter, type DefaultEventMap } from 'rettime'\nimport { Disposable } from './disposable'\nimport { createLogger, type Logger } from './utils/logger'\n\nexport enum InterceptorReadyState {\n INACTIVE = 'INACTIVE',\n ACTIVE = 'ACTIVE',\n DISPOSED = 'DISPOSED',\n}\n\ndeclare global {\n var __MSW_INTERCEPTORS_REGISTRY: Map<symbol, Interceptor<any>> | undefined\n}\n\nconst interceptorsRegistry = (globalThis.__MSW_INTERCEPTORS_REGISTRY ??=\n new Map<symbol, Interceptor<any>>())\n\nexport abstract class Interceptor<\n Events extends DefaultEventMap,\n> extends Disposable {\n declare ['constructor']: typeof Interceptor\n\n protected emitter: Emitter<Events>\n protected readonly logger: Logger\n\n public readyState: InterceptorReadyState\n\n static readonly symbol: symbol\n\n #owners: Set<object>\n\n static singleton<T extends Interceptor<any>>(\n InterceptorClass: (new () => T) & { symbol: symbol }\n ): T {\n const symbol = InterceptorClass.symbol\n const existing = interceptorsRegistry.get(symbol)\n\n if (existing instanceof InterceptorClass) {\n return existing\n }\n\n const newInstance = new InterceptorClass()\n interceptorsRegistry.set(symbol, newInstance)\n return newInstance\n }\n\n constructor() {\n super()\n\n this.#owners = new Set()\n this.readyState = InterceptorReadyState.INACTIVE\n this.emitter = new Emitter()\n this.logger = createLogger(this.#getLoggerNamespace())\n }\n\n protected abstract predicate(): boolean\n protected abstract setup(): void\n\n public apply(owner: object = this): void {\n if (this.#owners.has(owner)) {\n return\n }\n\n if (\n this.readyState !== InterceptorReadyState.ACTIVE &&\n !this.predicate()\n ) {\n return\n }\n\n this.#owners.add(owner)\n\n if (this.readyState === InterceptorReadyState.ACTIVE) {\n return\n }\n\n try {\n this.setup()\n this.readyState = InterceptorReadyState.ACTIVE\n this.logger.info('apply')\n } catch (error) {\n this.dispose(owner)\n throw error\n }\n }\n\n public dispose(owner: object = this): void {\n if (!this.#owners.delete(owner)) {\n return\n }\n\n if (this.#owners.size > 0) {\n return\n }\n\n super.dispose()\n this.emitter.removeAllListeners()\n this.readyState = InterceptorReadyState.DISPOSED\n this.logger.info('disable')\n }\n\n public on: Emitter<Events>['on'] = (type, listener, options) => {\n return this.emitter.on(type, listener, options)\n }\n\n public once: Emitter<Events>['once'] = (type, listener, options) => {\n return this.emitter.once(type, listener, options)\n }\n\n public listeners: Emitter<Events>['listeners'] = (type) => {\n return this.emitter.listeners(type)\n }\n\n public listenerCount: Emitter<Events>['listenerCount'] = (type) => {\n return this.emitter.listenerCount(type)\n }\n\n public removeListener: Emitter<Events>['removeListener'] = (\n type,\n listener\n ) => {\n return this.emitter.removeListener(type, listener)\n }\n\n public removeAllListeners: Emitter<Events>['removeAllListeners'] = (type) => {\n this.logger.info('removeAllListeners %o', { eventType: type ?? '*' })\n return this.emitter.removeAllListeners(type)\n }\n\n #getLoggerNamespace(): string {\n const symbolDescription = this.constructor.symbol?.description\n\n if (symbolDescription) {\n return symbolDescription.replace(/-interceptor$/, '')\n }\n\n return this.constructor.name.replace(/Interceptor$/, '')\n }\n}\n","const encoder = new TextEncoder()\n\nexport function encodeBuffer(text: string) {\n return encoder.encode(text)\n}\n\nexport function decodeBuffer(buffer: AllowSharedBufferSource, encoding?: string): string {\n const decoder = new TextDecoder(encoding)\n return decoder.decode(buffer)\n}\n\n/**\n * Create an `ArrayBuffer` from the given `Uint8Array`.\n * Takes the byte offset into account to produce the right buffer\n * in the case when the buffer is bigger than the data view.\n */\nexport function toArrayBuffer(array: Uint8Array<ArrayBuffer>): ArrayBuffer {\n return array.buffer.slice(\n array.byteOffset,\n array.byteOffset + array.byteLength\n )\n}\n\nexport function toBuffer(\n data: string | Buffer | Uint8Array<ArrayBufferLike>,\n encoding?: BufferEncoding\n): Buffer {\n if (Buffer.isBuffer(data)) {\n return data\n }\n\n if (data instanceof Uint8Array) {\n return Buffer.from(data.buffer)\n }\n\n return Buffer.from(data, encoding)\n}\n"],"mappings":"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"}