@mswjs/interceptors 0.42.2 → 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-IskkJJOL.js → has-configurable-global-CT6-QYdg.js} +2 -2
- package/lib/node/{has-configurable-global-IskkJJOL.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 +21 -0
- package/lib/node/interceptors/net/index.js +1 -1
- package/lib/node/{net-9sRKnjIG.js → net-DkiHxhQF.js} +66 -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-BUVce4Q1.js → source-BFZ6wg4P.js} +115 -39
- 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 +34 -5
- package/src/interceptors/http/source.ts +82 -3
- package/src/interceptors/net/index.ts +39 -12
- package/src/interceptors/net/socket-controller.ts +61 -9
- package/src/utils/internal-connection.ts +22 -0
- package/lib/node/buffer-utils-BvPY1Tc-.js.map +0 -1
- package/lib/node/net-9sRKnjIG.js.map +0 -1
- package/lib/node/source-BUVce4Q1.js.map +0 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { t as Interceptor } from "./interceptor-dtJZ_9QT.js";
|
|
2
|
+
import { TypedEvent } from "rettime";
|
|
3
|
+
//#region src/interceptors/WebSocket/utils/events.d.ts
|
|
4
|
+
declare const kCancelable: unique symbol;
|
|
5
|
+
declare const kDefaultPrevented: unique symbol;
|
|
6
|
+
/**
|
|
7
|
+
* A `MessageEvent` superset that supports event cancellation
|
|
8
|
+
* in Node.js. It's rather non-intrusive so it can be safely
|
|
9
|
+
* used in the browser as well.
|
|
10
|
+
*
|
|
11
|
+
* @see https://github.com/nodejs/node/issues/51767
|
|
12
|
+
*/
|
|
13
|
+
declare class CancelableMessageEvent<T = any> extends MessageEvent<T> {
|
|
14
|
+
[kCancelable]: boolean;
|
|
15
|
+
[kDefaultPrevented]: boolean;
|
|
16
|
+
constructor(type: string, init: MessageEventInit<T>);
|
|
17
|
+
get cancelable(): boolean;
|
|
18
|
+
set cancelable(nextCancelable: boolean);
|
|
19
|
+
get defaultPrevented(): boolean;
|
|
20
|
+
set defaultPrevented(nextDefaultPrevented: boolean);
|
|
21
|
+
preventDefault(): void;
|
|
22
|
+
}
|
|
23
|
+
interface CloseEventInit extends EventInit {
|
|
24
|
+
code?: number;
|
|
25
|
+
reason?: string;
|
|
26
|
+
wasClean?: boolean;
|
|
27
|
+
}
|
|
28
|
+
declare class CloseEvent extends Event {
|
|
29
|
+
code: number;
|
|
30
|
+
reason: string;
|
|
31
|
+
wasClean: boolean;
|
|
32
|
+
constructor(type: string, init?: CloseEventInit);
|
|
33
|
+
}
|
|
34
|
+
declare class CancelableCloseEvent extends CloseEvent {
|
|
35
|
+
[kCancelable]: boolean;
|
|
36
|
+
[kDefaultPrevented]: boolean;
|
|
37
|
+
constructor(type: string, init?: CloseEventInit);
|
|
38
|
+
get cancelable(): boolean;
|
|
39
|
+
set cancelable(nextCancelable: boolean);
|
|
40
|
+
get defaultPrevented(): boolean;
|
|
41
|
+
set defaultPrevented(nextDefaultPrevented: boolean);
|
|
42
|
+
preventDefault(): void;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/interceptors/WebSocket/web-socket-transport.d.ts
|
|
46
|
+
type WebSocketData = string | ArrayBuffer | Blob | ArrayBufferView<ArrayBuffer>;
|
|
47
|
+
type WebSocketTransportEventMap = {
|
|
48
|
+
incoming: MessageEvent<WebSocketData>;
|
|
49
|
+
outgoing: MessageEvent<WebSocketData>;
|
|
50
|
+
close: CloseEvent;
|
|
51
|
+
};
|
|
52
|
+
type StrictEventListenerOrEventListenerObject<EventType extends Event> = ((this: WebSocket, event: EventType) => void) | {
|
|
53
|
+
handleEvent(this: WebSocket, event: EventType): void;
|
|
54
|
+
};
|
|
55
|
+
interface WebSocketTransport {
|
|
56
|
+
addEventListener<EventType extends keyof WebSocketTransportEventMap>(event: EventType, listener: StrictEventListenerOrEventListenerObject<WebSocketTransportEventMap[EventType]> | null, options?: boolean | AddEventListenerOptions): void;
|
|
57
|
+
dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(event: WebSocketTransportEventMap[EventType]): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Send the data from the server to this client.
|
|
60
|
+
*/
|
|
61
|
+
send(data: WebSocketData): void;
|
|
62
|
+
/**
|
|
63
|
+
* Close the client connection.
|
|
64
|
+
*/
|
|
65
|
+
close(code?: number, reason?: string): void;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/interceptors/WebSocket/web-socket-override.d.ts
|
|
69
|
+
type WebSocketEventListener<EventType extends WebSocketEventMap[keyof WebSocketEventMap] = Event> = (this: WebSocket, event: EventType) => void;
|
|
70
|
+
declare const kPassthroughPromise: unique symbol;
|
|
71
|
+
declare const kOnSend: unique symbol;
|
|
72
|
+
declare const kClose: unique symbol;
|
|
73
|
+
declare class WebSocketOverride extends EventTarget implements WebSocket {
|
|
74
|
+
static readonly CONNECTING = 0;
|
|
75
|
+
static readonly OPEN = 1;
|
|
76
|
+
static readonly CLOSING = 2;
|
|
77
|
+
static readonly CLOSED = 3;
|
|
78
|
+
readonly CONNECTING = 0;
|
|
79
|
+
readonly OPEN = 1;
|
|
80
|
+
readonly CLOSING = 2;
|
|
81
|
+
readonly CLOSED = 3;
|
|
82
|
+
url: string;
|
|
83
|
+
protocol: string;
|
|
84
|
+
extensions: string;
|
|
85
|
+
binaryType: BinaryType;
|
|
86
|
+
readyState: WebSocket['readyState'];
|
|
87
|
+
bufferedAmount: number;
|
|
88
|
+
private _onopen;
|
|
89
|
+
private _onmessage;
|
|
90
|
+
private _onerror;
|
|
91
|
+
private _onclose;
|
|
92
|
+
private [kPassthroughPromise];
|
|
93
|
+
private [kOnSend]?;
|
|
94
|
+
constructor(url: string | URL, protocols?: string | Array<string>);
|
|
95
|
+
set onopen(listener: WebSocketEventListener | null);
|
|
96
|
+
get onopen(): WebSocketEventListener | null;
|
|
97
|
+
set onmessage(listener: WebSocketEventListener<MessageEvent<WebSocketData>> | null);
|
|
98
|
+
get onmessage(): WebSocketEventListener<MessageEvent<WebSocketData>> | null;
|
|
99
|
+
set onerror(listener: WebSocketEventListener | null);
|
|
100
|
+
get onerror(): WebSocketEventListener | null;
|
|
101
|
+
set onclose(listener: WebSocketEventListener<CloseEvent> | null);
|
|
102
|
+
get onclose(): WebSocketEventListener<CloseEvent> | null;
|
|
103
|
+
/**
|
|
104
|
+
* @see https://websockets.spec.whatwg.org/#ref-for-dom-websocket-send%E2%91%A0
|
|
105
|
+
*/
|
|
106
|
+
send(data: WebSocketData): void;
|
|
107
|
+
close(code?: number, reason?: string): void;
|
|
108
|
+
private [kClose];
|
|
109
|
+
addEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, event: WebSocketEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void;
|
|
110
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
111
|
+
removeEventListener<K extends keyof WebSocketEventMap>(type: K, callback: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/interceptors/WebSocket/web-socket-client-connection.d.ts
|
|
115
|
+
declare const kEmitter$1: unique symbol;
|
|
116
|
+
interface WebSocketClientEventMap {
|
|
117
|
+
message: MessageEvent<WebSocketData>;
|
|
118
|
+
close: CloseEvent;
|
|
119
|
+
}
|
|
120
|
+
declare abstract class WebSocketClientConnectionProtocol {
|
|
121
|
+
abstract id: string;
|
|
122
|
+
abstract url: URL;
|
|
123
|
+
abstract send(data: WebSocketData): void;
|
|
124
|
+
abstract close(code?: number, reason?: string): void;
|
|
125
|
+
abstract addEventListener<EventType extends keyof WebSocketClientEventMap>(type: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
|
|
126
|
+
abstract removeEventListener<EventType extends keyof WebSocketClientEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The WebSocket client instance represents an incoming
|
|
130
|
+
* client connection. The user can control the connection,
|
|
131
|
+
* send and receive events.
|
|
132
|
+
*/
|
|
133
|
+
declare class WebSocketClientConnection implements WebSocketClientConnectionProtocol {
|
|
134
|
+
readonly socket: WebSocket;
|
|
135
|
+
private readonly transport;
|
|
136
|
+
readonly id: string;
|
|
137
|
+
readonly url: URL;
|
|
138
|
+
private [kEmitter$1];
|
|
139
|
+
constructor(socket: WebSocket, transport: WebSocketTransport);
|
|
140
|
+
/**
|
|
141
|
+
* Listen for the outgoing events from the connected WebSocket client.
|
|
142
|
+
*/
|
|
143
|
+
addEventListener<EventType extends keyof WebSocketClientEventMap>(type: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
|
|
144
|
+
/**
|
|
145
|
+
* Removes the listener for the given event.
|
|
146
|
+
*/
|
|
147
|
+
removeEventListener<EventType extends keyof WebSocketClientEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketClientEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
|
|
148
|
+
/**
|
|
149
|
+
* Send data to the connected client.
|
|
150
|
+
*/
|
|
151
|
+
send(data: WebSocketData): void;
|
|
152
|
+
/**
|
|
153
|
+
* Close the WebSocket connection.
|
|
154
|
+
* @param {number} code A status code (see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1).
|
|
155
|
+
* @param {string} reason A custom connection close reason.
|
|
156
|
+
*/
|
|
157
|
+
close(code?: number, reason?: string): void;
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/interceptors/WebSocket/web-socket-class-transport.d.ts
|
|
161
|
+
/**
|
|
162
|
+
* Abstraction over the given mock `WebSocket` instance that allows
|
|
163
|
+
* for controlling that instance (e.g. sending and receiving messages).
|
|
164
|
+
*/
|
|
165
|
+
declare class WebSocketClassTransport extends EventTarget implements WebSocketTransport {
|
|
166
|
+
protected readonly socket: WebSocketOverride;
|
|
167
|
+
constructor(socket: WebSocketOverride);
|
|
168
|
+
addEventListener<EventType extends keyof WebSocketTransportEventMap>(type: EventType, callback: StrictEventListenerOrEventListenerObject<WebSocketTransportEventMap[EventType]> | null, options?: boolean | AddEventListenerOptions): void;
|
|
169
|
+
dispatchEvent<EventType extends keyof WebSocketTransportEventMap>(event: WebSocketTransportEventMap[EventType]): boolean;
|
|
170
|
+
send(data: WebSocketData): void;
|
|
171
|
+
close(code: number, reason?: string): void;
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/interceptors/WebSocket/web-socket-server-connection.d.ts
|
|
175
|
+
declare const kEmitter: unique symbol;
|
|
176
|
+
declare const kSend: unique symbol;
|
|
177
|
+
interface WebSocketServerEventMap {
|
|
178
|
+
open: Event;
|
|
179
|
+
message: MessageEvent<WebSocketData>;
|
|
180
|
+
error: Event;
|
|
181
|
+
close: CloseEvent;
|
|
182
|
+
}
|
|
183
|
+
declare abstract class WebSocketServerConnectionProtocol {
|
|
184
|
+
abstract connect(): void;
|
|
185
|
+
abstract send(data: WebSocketData): void;
|
|
186
|
+
abstract close(): void;
|
|
187
|
+
abstract addEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
|
|
188
|
+
abstract removeEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The WebSocket server instance represents the actual production
|
|
192
|
+
* WebSocket server connection. It's idle by default but you can
|
|
193
|
+
* establish it by calling `server.connect()`.
|
|
194
|
+
*/
|
|
195
|
+
declare class WebSocketServerConnection implements WebSocketServerConnectionProtocol {
|
|
196
|
+
private readonly client;
|
|
197
|
+
private readonly transport;
|
|
198
|
+
private readonly createConnection;
|
|
199
|
+
/**
|
|
200
|
+
* A WebSocket instance connected to the original server.
|
|
201
|
+
*/
|
|
202
|
+
private realWebSocket?;
|
|
203
|
+
private mockCloseController;
|
|
204
|
+
private realCloseController;
|
|
205
|
+
private [kEmitter];
|
|
206
|
+
constructor(client: WebSocketOverride, transport: WebSocketClassTransport, createConnection: () => WebSocket);
|
|
207
|
+
/**
|
|
208
|
+
* The `WebSocket` instance connected to the original server.
|
|
209
|
+
* Accessing this before calling `server.connect()` will throw.
|
|
210
|
+
*/
|
|
211
|
+
get socket(): WebSocket;
|
|
212
|
+
/**
|
|
213
|
+
* Open connection to the original WebSocket server.
|
|
214
|
+
*/
|
|
215
|
+
connect(): void;
|
|
216
|
+
/**
|
|
217
|
+
* Listen for the incoming events from the original WebSocket server.
|
|
218
|
+
*/
|
|
219
|
+
addEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: AddEventListenerOptions | boolean): void;
|
|
220
|
+
/**
|
|
221
|
+
* Remove the listener for the given event.
|
|
222
|
+
*/
|
|
223
|
+
removeEventListener<EventType extends keyof WebSocketServerEventMap>(event: EventType, listener: WebSocketEventListener<WebSocketServerEventMap[EventType]>, options?: EventListenerOptions | boolean): void;
|
|
224
|
+
/**
|
|
225
|
+
* Send data to the original WebSocket server.
|
|
226
|
+
* @example
|
|
227
|
+
* server.send('hello')
|
|
228
|
+
* server.send(new Blob(['hello']))
|
|
229
|
+
* server.send(new TextEncoder().encode('hello'))
|
|
230
|
+
*/
|
|
231
|
+
send(data: WebSocketData): void;
|
|
232
|
+
private [kSend];
|
|
233
|
+
/**
|
|
234
|
+
* Close the actual server connection.
|
|
235
|
+
*/
|
|
236
|
+
close(): void;
|
|
237
|
+
private handleIncomingMessage;
|
|
238
|
+
private handleMockClose;
|
|
239
|
+
private handleRealClose;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/interceptors/WebSocket/index.d.ts
|
|
243
|
+
/**
|
|
244
|
+
* Intercept the outgoing WebSocket connections created using
|
|
245
|
+
* the global `WebSocket` class.
|
|
246
|
+
*/
|
|
247
|
+
declare class WebSocketInterceptor extends Interceptor<WebSocketEventMap$1> {
|
|
248
|
+
static symbol: symbol;
|
|
249
|
+
protected predicate(): boolean;
|
|
250
|
+
protected setup(): void;
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region src/events/websocket.d.ts
|
|
254
|
+
/**
|
|
255
|
+
* The connection information.
|
|
256
|
+
*/
|
|
257
|
+
interface WebSocketConnectionInfo {
|
|
258
|
+
/**
|
|
259
|
+
* The protocols supported by the WebSocket client.
|
|
260
|
+
*/
|
|
261
|
+
protocols: string | Array<string> | undefined;
|
|
262
|
+
}
|
|
263
|
+
interface WebSocketConnectionEventData {
|
|
264
|
+
/**
|
|
265
|
+
* The incoming WebSocket client connection.
|
|
266
|
+
*/
|
|
267
|
+
client: WebSocketClientConnection;
|
|
268
|
+
/**
|
|
269
|
+
* The original WebSocket server connection.
|
|
270
|
+
*/
|
|
271
|
+
server: WebSocketServerConnection;
|
|
272
|
+
info: WebSocketConnectionInfo;
|
|
273
|
+
}
|
|
274
|
+
declare class WebSocketConnectionEvent<DataType extends WebSocketConnectionEventData = WebSocketConnectionEventData> extends TypedEvent<DataType, void, 'connection'> {
|
|
275
|
+
client: WebSocketClientConnection;
|
|
276
|
+
server: WebSocketServerConnection;
|
|
277
|
+
info: WebSocketConnectionInfo;
|
|
278
|
+
constructor(data: DataType);
|
|
279
|
+
}
|
|
280
|
+
type WebSocketEventMap$1 = {
|
|
281
|
+
connection: WebSocketConnectionEvent;
|
|
282
|
+
};
|
|
283
|
+
//#endregion
|
|
284
|
+
export { WebSocketServerConnectionProtocol as a, WebSocketClientConnectionProtocol as c, WebSocketTransport as d, CancelableCloseEvent as f, WebSocketServerConnection as i, WebSocketClientEventMap as l, CloseEvent as m, WebSocketEventMap$1 as n, WebSocketServerEventMap as o, CancelableMessageEvent as p, WebSocketInterceptor as r, WebSocketClientConnection as s, WebSocketConnectionEvent as t, WebSocketData as u };
|
|
285
|
+
//# sourceMappingURL=websocket-CC0nB0md.d.ts.map
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@mswjs/interceptors",
|
|
4
4
|
"description": "Low-level network interception library for Node.js.",
|
|
5
|
-
"version": "0.42.
|
|
5
|
+
"version": "0.42.4",
|
|
6
6
|
"main": "./lib/node/index.js",
|
|
7
7
|
"types": "./lib/node/index.d.ts",
|
|
8
8
|
"exports": {
|
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
},
|
|
35
35
|
"./fetch/node": "./lib/node/interceptors/fetch/node.js",
|
|
36
36
|
"./fetch/web": "./lib/browser/interceptors/fetch/web.js",
|
|
37
|
-
"./WebSocket":
|
|
37
|
+
"./WebSocket": {
|
|
38
|
+
"browser": "./lib/browser/interceptors/WebSocket/index.js",
|
|
39
|
+
"default": "./lib/node/interceptors/WebSocket/index.js"
|
|
40
|
+
},
|
|
38
41
|
"./RemoteHttpInterceptor": {
|
|
39
42
|
"browser": null,
|
|
40
43
|
"default": "./lib/node/remote-http-interceptor.js"
|
|
@@ -23,6 +23,7 @@ import { bindEvent } from './utils/bind-event'
|
|
|
23
23
|
import { hasConfigurableGlobal } from '../../utils/has-configurable-global'
|
|
24
24
|
import { patchesRegistry } from '../../utils/patches-registry'
|
|
25
25
|
import { createLogger } from '../../utils/logger'
|
|
26
|
+
import { runAsInternalConnection } from '../../utils/internal-connection'
|
|
26
27
|
|
|
27
28
|
export {
|
|
28
29
|
type WebSocketData,
|
|
@@ -73,7 +74,9 @@ export class WebSocketInterceptor extends Interceptor<WebSocketEventMap> {
|
|
|
73
74
|
})
|
|
74
75
|
|
|
75
76
|
const createConnection = (): WebSocket => {
|
|
76
|
-
return
|
|
77
|
+
return runAsInternalConnection(() => {
|
|
78
|
+
return Reflect.construct(target, args, newTarget)
|
|
79
|
+
})
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
// All WebSocket instances are mocked and don't forward
|
|
@@ -29,6 +29,7 @@ const KIND_RESPONSE = constants.TYPE.RESPONSE
|
|
|
29
29
|
type ParserKind = typeof KIND_REQUEST | typeof KIND_RESPONSE
|
|
30
30
|
|
|
31
31
|
export interface ParserCallbacks<K extends ParserKind> {
|
|
32
|
+
onError?: (error: Error) => void
|
|
32
33
|
onMessageBegin?: () => number | void
|
|
33
34
|
onHeadersComplete?: (
|
|
34
35
|
info: K extends typeof KIND_REQUEST
|
|
@@ -305,6 +306,13 @@ export class HttpParser<K extends ParserKind> {
|
|
|
305
306
|
const buffer = new Uint8Array(llhttp_memory.buffer)
|
|
306
307
|
const length = buffer.indexOf(0, errorPointer) - errorPointer
|
|
307
308
|
|
|
308
|
-
|
|
309
|
+
const error = new Error(readStringFrom(errorPointer, length))
|
|
310
|
+
|
|
311
|
+
if (this[kCallbacks].onError) {
|
|
312
|
+
this[kCallbacks].onError(error)
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
throw error
|
|
309
317
|
}
|
|
310
318
|
}
|
|
@@ -4,19 +4,24 @@ 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
|
|
10
11
|
}
|
|
11
12
|
onRequest: (request: Request, abortController: AbortController) => void
|
|
13
|
+
onMessageComplete?: () => void
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export class HttpRequestParser extends HttpParser<1> {
|
|
15
17
|
#requestBodyStream?: Readable
|
|
18
|
+
#upgrade = false
|
|
16
19
|
|
|
17
20
|
constructor(options: HttpRequestParserOptions) {
|
|
18
21
|
super(1, {
|
|
19
|
-
|
|
22
|
+
onError: options.onError,
|
|
23
|
+
onHeadersComplete: ({ rawHeaders, method, url: path, upgrade }) => {
|
|
24
|
+
this.#upgrade = upgrade
|
|
20
25
|
/**
|
|
21
26
|
* @note When the socket is reused, "connectionOptions" will point
|
|
22
27
|
* to the "net.connect()" call options that established the connection,
|
|
@@ -79,27 +84,44 @@ export class HttpRequestParser extends HttpParser<1> {
|
|
|
79
84
|
},
|
|
80
85
|
onMessageComplete: () => {
|
|
81
86
|
this.#requestBodyStream?.push(null)
|
|
87
|
+
this.#requestBodyStream = undefined
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @note An upgraded exchange (e.g. "CONNECT", WebSocket) has
|
|
91
|
+
* no message boundary: it takes the connection over, and the
|
|
92
|
+
* bytes that follow belong to it, not to a next exchange.
|
|
93
|
+
*/
|
|
94
|
+
if (!this.#upgrade) {
|
|
95
|
+
options.onMessageComplete?.()
|
|
96
|
+
}
|
|
82
97
|
},
|
|
83
98
|
})
|
|
84
99
|
}
|
|
85
100
|
|
|
86
|
-
public free(): void {
|
|
101
|
+
public free(error?: Error): void {
|
|
87
102
|
this.destroy()
|
|
88
|
-
this.#requestBodyStream?.destroy()
|
|
103
|
+
this.#requestBodyStream?.destroy(error)
|
|
89
104
|
this.#requestBodyStream = undefined
|
|
90
105
|
}
|
|
91
106
|
}
|
|
92
107
|
|
|
93
108
|
export class HttpResponseParser extends HttpParser<2> {
|
|
94
109
|
#responseBodyStream?: Readable | null
|
|
110
|
+
#status = 0
|
|
95
111
|
|
|
96
|
-
constructor(options: {
|
|
112
|
+
constructor(options: {
|
|
113
|
+
onResponse: (response: Response) => void
|
|
114
|
+
onError: (error: Error) => void
|
|
115
|
+
onMessageComplete?: (status: number) => void
|
|
116
|
+
}) {
|
|
97
117
|
super(2, {
|
|
118
|
+
onError: options.onError,
|
|
98
119
|
onHeadersComplete: ({
|
|
99
120
|
rawHeaders,
|
|
100
121
|
statusCode: status,
|
|
101
122
|
statusMessage: statusText,
|
|
102
123
|
}) => {
|
|
124
|
+
this.#status = status
|
|
103
125
|
const headers = FetchResponse.parseRawHeaders([...rawHeaders])
|
|
104
126
|
|
|
105
127
|
const response = new FetchResponse(
|
|
@@ -127,12 +149,19 @@ export class HttpResponseParser extends HttpParser<2> {
|
|
|
127
149
|
},
|
|
128
150
|
onMessageComplete: () => {
|
|
129
151
|
this.#responseBodyStream?.push(null)
|
|
152
|
+
this.#responseBodyStream = null
|
|
153
|
+
options.onMessageComplete?.(this.#status)
|
|
130
154
|
},
|
|
131
155
|
})
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
public free(): void {
|
|
158
|
+
public free(error?: Error): void {
|
|
135
159
|
this.destroy()
|
|
160
|
+
|
|
161
|
+
if (error) {
|
|
162
|
+
this.#responseBodyStream?.destroy(error)
|
|
163
|
+
}
|
|
164
|
+
|
|
136
165
|
this.#responseBodyStream = null
|
|
137
166
|
}
|
|
138
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,10 +183,22 @@ 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,
|
|
170
190
|
},
|
|
191
|
+
/**
|
|
192
|
+
* @note The message boundary ends the current exchange.
|
|
193
|
+
* Schedule the controller reset so the next write on this
|
|
194
|
+
* (kept-alive) socket opens a new exchange and buffers for
|
|
195
|
+
* its own verdict instead of following the settled one
|
|
196
|
+
* (e.g. leaking a mocked request to the server of a
|
|
197
|
+
* previously passed-through exchange).
|
|
198
|
+
*/
|
|
199
|
+
onMessageComplete: () => {
|
|
200
|
+
socketController.scheduleReset()
|
|
201
|
+
},
|
|
171
202
|
onRequest: async (parsedRequest, requestAbortController) => {
|
|
172
203
|
const request =
|
|
173
204
|
requestContextValue?.transformRequest?.(parsedRequest) ??
|
|
@@ -276,9 +307,15 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
276
307
|
},
|
|
277
308
|
passthrough: () => {
|
|
278
309
|
const realSocket = socketController.passthrough(
|
|
279
|
-
|
|
310
|
+
isHttpConnection === false
|
|
311
|
+
? undefined
|
|
312
|
+
: this.#modifyHttpHeaders(context.request)
|
|
280
313
|
)
|
|
281
314
|
|
|
315
|
+
if (isHttpConnection === false) {
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
|
|
282
319
|
if (this.emitter.listenerCount('response') > 0) {
|
|
283
320
|
httpLogger.verbose(
|
|
284
321
|
'found "response" listener, corking socket reads'
|
|
@@ -293,8 +330,20 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
293
330
|
*/
|
|
294
331
|
socketController.corkReads()
|
|
295
332
|
|
|
333
|
+
let responseParserDisposed = false
|
|
334
|
+
let responseComplete = false
|
|
335
|
+
let hasFinalResponse = false
|
|
296
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
|
+
},
|
|
297
344
|
onResponse: async (response) => {
|
|
345
|
+
hasFinalResponse =
|
|
346
|
+
response.status >= 200 || response.status === 101
|
|
298
347
|
httpLogger.verbose(
|
|
299
348
|
'HTTP response parser parsed: %d %s',
|
|
300
349
|
response.status,
|
|
@@ -334,6 +383,7 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
334
383
|
* final response on the "response" event listeners.
|
|
335
384
|
*/
|
|
336
385
|
if (
|
|
386
|
+
!responseParserDisposed &&
|
|
337
387
|
response.status < 200 &&
|
|
338
388
|
response.status !== 101
|
|
339
389
|
) {
|
|
@@ -343,9 +393,35 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
343
393
|
},
|
|
344
394
|
})
|
|
345
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
|
+
|
|
346
422
|
realSocket
|
|
347
|
-
.on('data',
|
|
348
|
-
.
|
|
423
|
+
.on('data', onResponseData)
|
|
424
|
+
.once('close', onResponseClose)
|
|
349
425
|
}
|
|
350
426
|
},
|
|
351
427
|
},
|
|
@@ -392,9 +468,12 @@ export class NodeHttpRequestSource extends Interceptor<HttpRequestEventMap> {
|
|
|
392
468
|
}
|
|
393
469
|
}
|
|
394
470
|
|
|
471
|
+
pendingRequestController = requestController
|
|
472
|
+
|
|
395
473
|
try {
|
|
396
474
|
await handleRequest(context)
|
|
397
475
|
} finally {
|
|
476
|
+
pendingRequestController = undefined
|
|
398
477
|
abortPendingRequest = undefined
|
|
399
478
|
}
|
|
400
479
|
},
|