@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.
- package/lib/browser/interceptors/WebSocket/index.js +14 -1
- package/lib/browser/interceptors/WebSocket/index.js.map +1 -1
- package/lib/browser/interceptors/fetch/web.js +1 -1
- package/lib/browser/presets/browser.js +1 -1
- package/lib/browser/{web-CdcYFjgm.js → web-DUlFZEtz.js} +58 -3
- package/lib/browser/web-DUlFZEtz.js.map +1 -0
- package/lib/node/{batch-interceptor-ByEZVih3.js → batch-interceptor-DMOfd-9x.js} +2 -2
- package/lib/node/{batch-interceptor-ByEZVih3.js.map → batch-interceptor-DMOfd-9x.js.map} +1 -1
- package/lib/node/buffer-utils-B4FUq-l2.js +17 -0
- package/lib/node/buffer-utils-B4FUq-l2.js.map +1 -0
- package/lib/node/create-request-id-DHo-fRTV.js +14 -0
- package/lib/node/create-request-id-DHo-fRTV.js.map +1 -0
- package/lib/node/{fetch-utils-Dw1PsmtX.js → fetch-utils-D-_xeRlK.js} +3 -14
- package/lib/node/{fetch-utils-Dw1PsmtX.js.map → fetch-utils-D-_xeRlK.js.map} +1 -1
- package/lib/node/{has-configurable-global-BU1sT1u4.js → has-configurable-global-CT6-QYdg.js} +2 -2
- package/lib/node/{has-configurable-global-BU1sT1u4.js.map → has-configurable-global-CT6-QYdg.js.map} +1 -1
- package/lib/node/index.d.ts +2 -243
- package/lib/node/index.js +6 -25
- package/lib/node/index.js.map +1 -1
- package/lib/node/{buffer-utils-BvPY1Tc-.js → interceptor-C8qRPjxG.js} +2 -16
- package/lib/node/interceptor-C8qRPjxG.js.map +1 -0
- package/lib/node/interceptors/ClientRequest/index.js +3 -3
- package/lib/node/interceptors/WebSocket/index.d.ts +2 -0
- package/lib/node/interceptors/WebSocket/index.js +635 -0
- package/lib/node/interceptors/WebSocket/index.js.map +1 -0
- package/lib/node/interceptors/XMLHttpRequest/node.js +5 -5
- package/lib/node/interceptors/fetch/node.js +5 -5
- package/lib/node/interceptors/http/index.js +2 -2
- package/lib/node/interceptors/net/index.d.ts +16 -0
- package/lib/node/interceptors/net/index.js +1 -1
- package/lib/node/{net-Ca8p1rIe.js → net-Bh16MP4u.js} +82 -92
- package/lib/node/net-Bh16MP4u.js.map +1 -0
- package/lib/node/patches-registry-DxR5TEc-.js +76 -0
- package/lib/node/patches-registry-DxR5TEc-.js.map +1 -0
- package/lib/node/remote-http-interceptor.js +4 -4
- package/lib/node/resolve-web-socket-url-CSvNPLGi.js +25 -0
- package/lib/node/resolve-web-socket-url-CSvNPLGi.js.map +1 -0
- package/lib/node/{source-CIgPng7r.js → source-DHVO1vzq.js} +327 -213
- package/lib/node/source-DHVO1vzq.js.map +1 -0
- package/lib/node/websocket-CC0nB0md.d.ts +285 -0
- package/package.json +5 -2
- package/src/interceptors/WebSocket/index.ts +4 -1
- package/src/interceptors/fetch/web.ts +11 -2
- package/src/interceptors/http/http-parser/index.ts +9 -1
- package/src/interceptors/http/http-parser.ts +21 -4
- package/src/interceptors/http/source.ts +368 -283
- package/src/interceptors/net/index.ts +39 -12
- package/src/interceptors/net/socket-controller.ts +126 -66
- package/src/utils/clone-response.ts +72 -0
- package/src/utils/internal-connection.ts +22 -0
- package/lib/browser/web-CdcYFjgm.js.map +0 -1
- package/lib/node/buffer-utils-BvPY1Tc-.js.map +0 -1
- package/lib/node/net-Ca8p1rIe.js.map +0 -1
- 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
|
|
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(
|
|
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] = {
|
|
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
|
|
615
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1329
|
-
|
|
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
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
this
|
|
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
|
-
|
|
1384
|
-
.once('
|
|
1385
|
-
|
|
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":"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"}
|
|
@@ -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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"}
|