@mswjs/interceptors 0.42.3 → 0.42.4
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/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-Tm5LbwBe.js} +3 -14
- package/lib/node/{fetch-utils-Dw1PsmtX.js.map → fetch-utils-Tm5LbwBe.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 +9 -0
- package/lib/node/interceptors/net/index.js +1 -1
- package/lib/node/{net-Ca8p1rIe.js → net-DkiHxhQF.js} +43 -79
- package/lib/node/net-DkiHxhQF.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-BFZ6wg4P.js} +95 -38
- package/lib/node/source-BFZ6wg4P.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/http/http-parser/index.ts +9 -1
- package/src/interceptors/http/http-parser.ts +21 -4
- package/src/interceptors/http/source.ts +71 -3
- package/src/interceptors/net/index.ts +39 -12
- package/src/interceptors/net/socket-controller.ts +34 -9
- package/src/utils/internal-connection.ts +22 -0
- 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
|
@@ -4,6 +4,7 @@ import { FetchRequest, FetchResponse } from '../../utils/fetch-utils'
|
|
|
4
4
|
import { HttpParser } from './http-parser/index'
|
|
5
5
|
|
|
6
6
|
interface HttpRequestParserOptions {
|
|
7
|
+
onError: (error: Error) => void
|
|
7
8
|
connectionOptions: {
|
|
8
9
|
method?: string
|
|
9
10
|
url: URL
|
|
@@ -18,6 +19,7 @@ export class HttpRequestParser extends HttpParser<1> {
|
|
|
18
19
|
|
|
19
20
|
constructor(options: HttpRequestParserOptions) {
|
|
20
21
|
super(1, {
|
|
22
|
+
onError: options.onError,
|
|
21
23
|
onHeadersComplete: ({ rawHeaders, method, url: path, upgrade }) => {
|
|
22
24
|
this.#upgrade = upgrade
|
|
23
25
|
/**
|
|
@@ -82,6 +84,7 @@ export class HttpRequestParser extends HttpParser<1> {
|
|
|
82
84
|
},
|
|
83
85
|
onMessageComplete: () => {
|
|
84
86
|
this.#requestBodyStream?.push(null)
|
|
87
|
+
this.#requestBodyStream = undefined
|
|
85
88
|
|
|
86
89
|
/**
|
|
87
90
|
* @note An upgraded exchange (e.g. "CONNECT", WebSocket) has
|
|
@@ -95,23 +98,30 @@ export class HttpRequestParser extends HttpParser<1> {
|
|
|
95
98
|
})
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
public free(): void {
|
|
101
|
+
public free(error?: Error): void {
|
|
99
102
|
this.destroy()
|
|
100
|
-
this.#requestBodyStream?.destroy()
|
|
103
|
+
this.#requestBodyStream?.destroy(error)
|
|
101
104
|
this.#requestBodyStream = undefined
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
export class HttpResponseParser extends HttpParser<2> {
|
|
106
109
|
#responseBodyStream?: Readable | null
|
|
110
|
+
#status = 0
|
|
107
111
|
|
|
108
|
-
constructor(options: {
|
|
112
|
+
constructor(options: {
|
|
113
|
+
onResponse: (response: Response) => void
|
|
114
|
+
onError: (error: Error) => void
|
|
115
|
+
onMessageComplete?: (status: number) => void
|
|
116
|
+
}) {
|
|
109
117
|
super(2, {
|
|
118
|
+
onError: options.onError,
|
|
110
119
|
onHeadersComplete: ({
|
|
111
120
|
rawHeaders,
|
|
112
121
|
statusCode: status,
|
|
113
122
|
statusMessage: statusText,
|
|
114
123
|
}) => {
|
|
124
|
+
this.#status = status
|
|
115
125
|
const headers = FetchResponse.parseRawHeaders([...rawHeaders])
|
|
116
126
|
|
|
117
127
|
const response = new FetchResponse(
|
|
@@ -139,12 +149,19 @@ export class HttpResponseParser extends HttpParser<2> {
|
|
|
139
149
|
},
|
|
140
150
|
onMessageComplete: () => {
|
|
141
151
|
this.#responseBodyStream?.push(null)
|
|
152
|
+
this.#responseBodyStream = null
|
|
153
|
+
options.onMessageComplete?.(this.#status)
|
|
142
154
|
},
|
|
143
155
|
})
|
|
144
156
|
}
|
|
145
157
|
|
|
146
|
-
public free(): void {
|
|
158
|
+
public free(error?: Error): void {
|
|
147
159
|
this.destroy()
|
|
160
|
+
|
|
161
|
+
if (error) {
|
|
162
|
+
this.#responseBodyStream?.destroy(error)
|
|
163
|
+
}
|
|
164
|
+
|
|
148
165
|
this.#responseBodyStream = null
|
|
149
166
|
}
|
|
150
167
|
}
|
|
@@ -67,6 +67,24 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
67
67
|
let requestParser: HttpRequestParser | undefined
|
|
68
68
|
let tunnelUrl: URL | undefined
|
|
69
69
|
let abortPendingRequest: (() => void) | undefined
|
|
70
|
+
let pendingRequestController: RequestController | undefined
|
|
71
|
+
|
|
72
|
+
// A malformed request loses the boundary for subsequent requests.
|
|
73
|
+
// Preserve the original bytes for the client and server to handle.
|
|
74
|
+
const stopParsingRequests = (error: Error) => {
|
|
75
|
+
httpLogger.verbose('stopping HTTP request parsing: %o', error)
|
|
76
|
+
isHttpConnection = false
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
pendingRequestController?.readyState === RequestController.PENDING
|
|
80
|
+
) {
|
|
81
|
+
void pendingRequestController.passthrough()
|
|
82
|
+
} else {
|
|
83
|
+
socketController.decline()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
requestParser?.free(error)
|
|
87
|
+
}
|
|
70
88
|
|
|
71
89
|
/**
|
|
72
90
|
* @note Capture the request context of the connection itself.
|
|
@@ -99,6 +117,7 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
99
117
|
*/
|
|
100
118
|
socket.on('data', (chunk) => {
|
|
101
119
|
if (isHttpConnection === false) {
|
|
120
|
+
socketController.decline()
|
|
102
121
|
return
|
|
103
122
|
}
|
|
104
123
|
|
|
@@ -164,6 +183,7 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
164
183
|
const initiator = requestContextValue?.initiator || socket
|
|
165
184
|
|
|
166
185
|
requestParser = new HttpRequestParser({
|
|
186
|
+
onError: stopParsingRequests,
|
|
167
187
|
connectionOptions: {
|
|
168
188
|
method: httpMethod,
|
|
169
189
|
url: baseUrl,
|
|
@@ -287,9 +307,15 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
287
307
|
},
|
|
288
308
|
passthrough: () => {
|
|
289
309
|
const realSocket = socketController.passthrough(
|
|
290
|
-
|
|
310
|
+
isHttpConnection === false
|
|
311
|
+
? undefined
|
|
312
|
+
: this.#modifyHttpHeaders(context.request)
|
|
291
313
|
)
|
|
292
314
|
|
|
315
|
+
if (isHttpConnection === false) {
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
|
|
293
319
|
if (this.emitter.listenerCount('response') > 0) {
|
|
294
320
|
httpLogger.verbose(
|
|
295
321
|
'found "response" listener, corking socket reads'
|
|
@@ -304,8 +330,20 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
304
330
|
*/
|
|
305
331
|
socketController.corkReads()
|
|
306
332
|
|
|
333
|
+
let responseParserDisposed = false
|
|
334
|
+
let responseComplete = false
|
|
335
|
+
let hasFinalResponse = false
|
|
307
336
|
const responseParser = new HttpResponseParser({
|
|
337
|
+
onError: (error) => {
|
|
338
|
+
disposeResponseParser(error)
|
|
339
|
+
socketController.uncorkReads()
|
|
340
|
+
},
|
|
341
|
+
onMessageComplete: (status) => {
|
|
342
|
+
responseComplete = status >= 200 || status === 101
|
|
343
|
+
},
|
|
308
344
|
onResponse: async (response) => {
|
|
345
|
+
hasFinalResponse =
|
|
346
|
+
response.status >= 200 || response.status === 101
|
|
309
347
|
httpLogger.verbose(
|
|
310
348
|
'HTTP response parser parsed: %d %s',
|
|
311
349
|
response.status,
|
|
@@ -345,6 +383,7 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
345
383
|
* final response on the "response" event listeners.
|
|
346
384
|
*/
|
|
347
385
|
if (
|
|
386
|
+
!responseParserDisposed &&
|
|
348
387
|
response.status < 200 &&
|
|
349
388
|
response.status !== 101
|
|
350
389
|
) {
|
|
@@ -354,9 +393,35 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
354
393
|
},
|
|
355
394
|
})
|
|
356
395
|
|
|
396
|
+
const onResponseData = (chunk: Buffer) => {
|
|
397
|
+
responseParser.execute(chunk)
|
|
398
|
+
|
|
399
|
+
// Free only after llhttp returns from its callbacks.
|
|
400
|
+
if (responseComplete) {
|
|
401
|
+
disposeResponseParser()
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const onResponseClose = () => {
|
|
406
|
+
disposeResponseParser()
|
|
407
|
+
|
|
408
|
+
// Without a response, no response listener will release
|
|
409
|
+
// the buffered EOF/close that rejects the client request.
|
|
410
|
+
if (!hasFinalResponse) {
|
|
411
|
+
socketController.uncorkReads()
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const disposeResponseParser = (error?: Error) => {
|
|
416
|
+
responseParserDisposed = true
|
|
417
|
+
realSocket.removeListener('data', onResponseData)
|
|
418
|
+
realSocket.removeListener('close', onResponseClose)
|
|
419
|
+
responseParser.free(error)
|
|
420
|
+
}
|
|
421
|
+
|
|
357
422
|
realSocket
|
|
358
|
-
.on('data',
|
|
359
|
-
.
|
|
423
|
+
.on('data', onResponseData)
|
|
424
|
+
.once('close', onResponseClose)
|
|
360
425
|
}
|
|
361
426
|
},
|
|
362
427
|
},
|
|
@@ -403,9 +468,12 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
403
468
|
}
|
|
404
469
|
}
|
|
405
470
|
|
|
471
|
+
pendingRequestController = requestController
|
|
472
|
+
|
|
406
473
|
try {
|
|
407
474
|
await handleRequest(context)
|
|
408
475
|
} finally {
|
|
476
|
+
pendingRequestController = undefined
|
|
409
477
|
abortPendingRequest = undefined
|
|
410
478
|
}
|
|
411
479
|
},
|
|
@@ -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)
|
|
@@ -538,7 +541,11 @@ export class TcpSocketController extends SocketController {
|
|
|
538
541
|
typeof args[0] === 'object' &&
|
|
539
542
|
(args[0].localAddress != null || args[0].localPort != null)
|
|
540
543
|
) {
|
|
541
|
-
args[0] = {
|
|
544
|
+
args[0] = {
|
|
545
|
+
...args[0],
|
|
546
|
+
localAddress: undefined,
|
|
547
|
+
localPort: undefined,
|
|
548
|
+
}
|
|
542
549
|
}
|
|
543
550
|
|
|
544
551
|
return Reflect.apply(target, thisArg, args)
|
|
@@ -868,9 +875,7 @@ export class TcpSocketController extends SocketController {
|
|
|
868
875
|
}
|
|
869
876
|
}
|
|
870
877
|
|
|
871
|
-
#removeBufferedWrite(
|
|
872
|
-
args: Parameters<net.Socket['_writeGeneric']>
|
|
873
|
-
): boolean {
|
|
878
|
+
#removeBufferedWrite(args: Parameters<net.Socket['_writeGeneric']>): boolean {
|
|
874
879
|
const index = this.#bufferedWrites.indexOf(args)
|
|
875
880
|
|
|
876
881
|
if (index === -1) {
|
|
@@ -1107,6 +1112,10 @@ export class TcpSocketController extends SocketController {
|
|
|
1107
1112
|
* the consumer reads the buffered data.
|
|
1108
1113
|
*/
|
|
1109
1114
|
#emitClientClose(hadError: boolean): void {
|
|
1115
|
+
if (this.#clientCloseEmitted) {
|
|
1116
|
+
return
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1110
1119
|
if (
|
|
1111
1120
|
this.#clientEndPushed &&
|
|
1112
1121
|
!this.socket.readableEnded &&
|
|
@@ -1120,9 +1129,7 @@ export class TcpSocketController extends SocketController {
|
|
|
1120
1129
|
closeDelivered = true
|
|
1121
1130
|
|
|
1122
1131
|
process.nextTick(() => {
|
|
1123
|
-
|
|
1124
|
-
this.socket.emit('close', hadErrorOverride ?? hadError)
|
|
1125
|
-
}
|
|
1132
|
+
this.#emitClientClose(hadErrorOverride ?? hadError)
|
|
1126
1133
|
})
|
|
1127
1134
|
}
|
|
1128
1135
|
|
|
@@ -1144,7 +1151,13 @@ export class TcpSocketController extends SocketController {
|
|
|
1144
1151
|
return
|
|
1145
1152
|
}
|
|
1146
1153
|
|
|
1147
|
-
|
|
1154
|
+
// Agent pools inspect the socket state inside their close listeners.
|
|
1155
|
+
// Match Node.js: a closed socket must already be destroyed and unwritable.
|
|
1156
|
+
this.socket.destroy()
|
|
1157
|
+
|
|
1158
|
+
if (this.#realHandleSwapped) {
|
|
1159
|
+
this.socket.emit('close', hadError)
|
|
1160
|
+
}
|
|
1148
1161
|
}
|
|
1149
1162
|
|
|
1150
1163
|
#onMockSocketDrain = () => {
|
|
@@ -1462,6 +1475,18 @@ export class TlsSocketController extends TcpSocketController {
|
|
|
1462
1475
|
}
|
|
1463
1476
|
|
|
1464
1477
|
protected emulateConnect(): void {
|
|
1478
|
+
/**
|
|
1479
|
+
* @note The client chooses its wire protocol when we emulate the
|
|
1480
|
+
* handshake. A later passthrough handshake must preserve that choice
|
|
1481
|
+
* instead of negotiating a different protocol for buffered writes.
|
|
1482
|
+
*/
|
|
1483
|
+
if (this.#tlsConnectionOptions) {
|
|
1484
|
+
const alpnProtocol = this.socket._handle.getALPNNegotiatedProtocol()
|
|
1485
|
+
this.#tlsConnectionOptions.ALPNProtocols = alpnProtocol
|
|
1486
|
+
? [alpnProtocol]
|
|
1487
|
+
: []
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1465
1490
|
super.emulateConnect()
|
|
1466
1491
|
|
|
1467
1492
|
// For TLS sockets, also invoke the "secureConnect" callbacks since some consumers,
|
|
@@ -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":"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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"}
|