@mswjs/interceptors 0.31.1 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +56 -39
  2. package/lib/node/RemoteHttpInterceptor.d.ts +1 -2
  3. package/lib/node/RemoteHttpInterceptor.js +11 -11
  4. package/lib/node/RemoteHttpInterceptor.mjs +5 -5
  5. package/lib/node/{chunk-LTEXDYJ6.js → chunk-2COJKQQB.js} +3 -3
  6. package/lib/node/chunk-3OJLYEWA.mjs +963 -0
  7. package/lib/node/chunk-3OJLYEWA.mjs.map +1 -0
  8. package/lib/node/chunk-5JMJ55U7.js +963 -0
  9. package/lib/node/chunk-5JMJ55U7.js.map +1 -0
  10. package/lib/node/{chunk-E4AC7YAC.js → chunk-BFLYGQ6D.js} +4 -2
  11. package/lib/node/{chunk-KSHIDGUL.mjs → chunk-DV4PBH4D.mjs} +3 -3
  12. package/lib/node/{chunk-OUWBQF3Z.mjs → chunk-KWV3JXSI.mjs} +14 -14
  13. package/lib/node/chunk-KWV3JXSI.mjs.map +1 -0
  14. package/lib/node/{chunk-6FRASLM3.mjs → chunk-PNWPIDEL.mjs} +2 -2
  15. package/lib/node/{chunk-APT7KA3B.js → chunk-PYD4E2EJ.js} +13 -13
  16. package/lib/node/{chunk-Q7POAM5N.mjs → chunk-TGTPXCLF.mjs} +3 -1
  17. package/lib/node/{chunk-MQJ3JOOK.js → chunk-UXCYRE4F.js} +14 -14
  18. package/lib/node/chunk-UXCYRE4F.js.map +1 -0
  19. package/lib/node/index.js +3 -3
  20. package/lib/node/index.mjs +2 -2
  21. package/lib/node/interceptors/ClientRequest/index.d.ts +83 -14
  22. package/lib/node/interceptors/ClientRequest/index.js +4 -4
  23. package/lib/node/interceptors/ClientRequest/index.mjs +3 -3
  24. package/lib/node/interceptors/XMLHttpRequest/index.js +4 -4
  25. package/lib/node/interceptors/XMLHttpRequest/index.mjs +3 -3
  26. package/lib/node/interceptors/fetch/index.js +10 -10
  27. package/lib/node/interceptors/fetch/index.mjs +2 -2
  28. package/lib/node/presets/node.d.ts +2 -3
  29. package/lib/node/presets/node.js +6 -6
  30. package/lib/node/presets/node.mjs +4 -4
  31. package/package.json +2 -2
  32. package/src/interceptors/ClientRequest/MockHttpSocket.ts +595 -0
  33. package/src/interceptors/ClientRequest/agents.ts +78 -0
  34. package/src/interceptors/ClientRequest/index.test.ts +14 -12
  35. package/src/interceptors/ClientRequest/index.ts +200 -41
  36. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.test.ts +78 -98
  37. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts +40 -22
  38. package/src/interceptors/Socket/MockSocket.test.ts +264 -0
  39. package/src/interceptors/Socket/MockSocket.ts +59 -0
  40. package/src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts +26 -0
  41. package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.test.ts +52 -0
  42. package/src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts +33 -0
  43. package/src/interceptors/Socket/utils/parseRawHeaders.ts +10 -0
  44. package/lib/node/chunk-IS3CIGXU.js +0 -909
  45. package/lib/node/chunk-IS3CIGXU.js.map +0 -1
  46. package/lib/node/chunk-MQJ3JOOK.js.map +0 -1
  47. package/lib/node/chunk-OMOWHUE6.mjs +0 -909
  48. package/lib/node/chunk-OMOWHUE6.mjs.map +0 -1
  49. package/lib/node/chunk-OUWBQF3Z.mjs.map +0 -1
  50. package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +0 -206
  51. package/src/interceptors/ClientRequest/NodeClientRequest.ts +0 -680
  52. package/src/interceptors/ClientRequest/http.get.ts +0 -30
  53. package/src/interceptors/ClientRequest/http.request.ts +0 -27
  54. package/src/interceptors/ClientRequest/utils/cloneIncomingMessage.test.ts +0 -26
  55. package/src/interceptors/ClientRequest/utils/cloneIncomingMessage.ts +0 -74
  56. package/src/interceptors/ClientRequest/utils/createRequest.test.ts +0 -144
  57. package/src/interceptors/ClientRequest/utils/createRequest.ts +0 -51
  58. package/src/interceptors/ClientRequest/utils/createResponse.test.ts +0 -53
  59. package/src/interceptors/ClientRequest/utils/createResponse.ts +0 -55
  60. package/src/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.test.ts +0 -41
  61. package/src/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.ts +0 -53
  62. package/src/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.test.ts +0 -36
  63. package/src/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.ts +0 -39
  64. /package/lib/node/{chunk-LTEXDYJ6.js.map → chunk-2COJKQQB.js.map} +0 -0
  65. /package/lib/node/{chunk-E4AC7YAC.js.map → chunk-BFLYGQ6D.js.map} +0 -0
  66. /package/lib/node/{chunk-KSHIDGUL.mjs.map → chunk-DV4PBH4D.mjs.map} +0 -0
  67. /package/lib/node/{chunk-6FRASLM3.mjs.map → chunk-PNWPIDEL.mjs.map} +0 -0
  68. /package/lib/node/{chunk-APT7KA3B.js.map → chunk-PYD4E2EJ.js.map} +0 -0
  69. /package/lib/node/{chunk-Q7POAM5N.mjs.map → chunk-TGTPXCLF.mjs.map} +0 -0
@@ -0,0 +1,963 @@
1
+ import {
2
+ emitAsync,
3
+ toInteractiveRequest
4
+ } from "./chunk-KWV3JXSI.mjs";
5
+ import {
6
+ INTERNAL_REQUEST_ID_HEADER_NAME,
7
+ Interceptor,
8
+ RESPONSE_STATUS_CODES_WITHOUT_BODY,
9
+ createRequestId,
10
+ createServerErrorResponse,
11
+ isPropertyAccessible
12
+ } from "./chunk-TGTPXCLF.mjs";
13
+
14
+ // src/interceptors/ClientRequest/index.ts
15
+ import http2 from "http";
16
+ import https2 from "https";
17
+ import { until } from "@open-draft/until";
18
+
19
+ // src/interceptors/ClientRequest/MockHttpSocket.ts
20
+ import net2 from "net";
21
+ import {
22
+ HTTPParser
23
+ } from "_http_common";
24
+ import { IncomingMessage, ServerResponse } from "http";
25
+ import { Readable } from "stream";
26
+ import { invariant } from "outvariant";
27
+
28
+ // src/interceptors/Socket/MockSocket.ts
29
+ import net from "net";
30
+
31
+ // src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts
32
+ function normalizeSocketWriteArgs(args) {
33
+ const normalized = [args[0], void 0, void 0];
34
+ if (typeof args[1] === "string") {
35
+ normalized[1] = args[1];
36
+ } else if (typeof args[1] === "function") {
37
+ normalized[2] = args[1];
38
+ }
39
+ if (typeof args[2] === "function") {
40
+ normalized[2] = args[2];
41
+ }
42
+ return normalized;
43
+ }
44
+
45
+ // src/interceptors/Socket/MockSocket.ts
46
+ var MockSocket = class extends net.Socket {
47
+ constructor(options) {
48
+ super();
49
+ this.options = options;
50
+ this.connecting = false;
51
+ this.connect();
52
+ this._final = (callback) => {
53
+ callback(null);
54
+ };
55
+ }
56
+ connect() {
57
+ this.connecting = true;
58
+ return this;
59
+ }
60
+ write(...args) {
61
+ const [chunk, encoding, callback] = normalizeSocketWriteArgs(
62
+ args
63
+ );
64
+ this.options.write(chunk, encoding, callback);
65
+ return true;
66
+ }
67
+ end(...args) {
68
+ const [chunk, encoding, callback] = normalizeSocketWriteArgs(
69
+ args
70
+ );
71
+ this.options.write(chunk, encoding, callback);
72
+ return super.end.apply(this, args);
73
+ }
74
+ push(chunk, encoding) {
75
+ this.options.read(chunk, encoding);
76
+ return super.push(chunk, encoding);
77
+ }
78
+ };
79
+
80
+ // src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts
81
+ function baseUrlFromConnectionOptions(options) {
82
+ if ("href" in options) {
83
+ return new URL(options.href);
84
+ }
85
+ const protocol = options.port === 443 ? "https:" : "http:";
86
+ const host = options.host;
87
+ const url = new URL(`${protocol}//${host}`);
88
+ if (options.port) {
89
+ url.port = options.port.toString();
90
+ }
91
+ if (options.path) {
92
+ url.pathname = options.path;
93
+ }
94
+ if (options.auth) {
95
+ const [username, password] = options.auth.split(":");
96
+ url.username = username;
97
+ url.password = password;
98
+ }
99
+ return url;
100
+ }
101
+
102
+ // src/interceptors/Socket/utils/parseRawHeaders.ts
103
+ function parseRawHeaders(rawHeaders) {
104
+ const headers = new Headers();
105
+ for (let line = 0; line < rawHeaders.length; line += 2) {
106
+ headers.append(rawHeaders[line], rawHeaders[line + 1]);
107
+ }
108
+ return headers;
109
+ }
110
+
111
+ // src/utils/getValueBySymbol.ts
112
+ function getValueBySymbol(symbolName, source) {
113
+ const ownSymbols = Object.getOwnPropertySymbols(source);
114
+ const symbol = ownSymbols.find((symbol2) => {
115
+ return symbol2.description === symbolName;
116
+ });
117
+ if (symbol) {
118
+ return Reflect.get(source, symbol);
119
+ }
120
+ return;
121
+ }
122
+
123
+ // src/utils/isObject.ts
124
+ function isObject(value, loose = false) {
125
+ return loose ? Object.prototype.toString.call(value).startsWith("[object ") : Object.prototype.toString.call(value) === "[object Object]";
126
+ }
127
+
128
+ // src/utils/getRawFetchHeaders.ts
129
+ function getRawFetchHeaders(headers) {
130
+ const headersList = getValueBySymbol("headers list", headers);
131
+ if (!headersList) {
132
+ return;
133
+ }
134
+ const headersMap = getValueBySymbol("headers map", headersList);
135
+ if (!headersMap || !isHeadersMapWithRawHeaderNames(headersMap)) {
136
+ return;
137
+ }
138
+ const rawHeaders = /* @__PURE__ */ new Map();
139
+ headersMap.forEach(({ name, value }) => {
140
+ rawHeaders.set(name, value);
141
+ });
142
+ return rawHeaders;
143
+ }
144
+ function isHeadersMapWithRawHeaderNames(headersMap) {
145
+ return Array.from(
146
+ headersMap.values()
147
+ ).every((value) => {
148
+ return isObject(value) && "name" in value;
149
+ });
150
+ }
151
+
152
+ // src/interceptors/ClientRequest/MockHttpSocket.ts
153
+ var kRequestId = Symbol("kRequestId");
154
+ var MockHttpSocket = class extends MockSocket {
155
+ constructor(options) {
156
+ super({
157
+ write: (chunk, encoding, callback) => {
158
+ this.writeBuffer.push([chunk, encoding, callback]);
159
+ if (chunk) {
160
+ this.requestParser.execute(
161
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding)
162
+ );
163
+ }
164
+ },
165
+ read: (chunk) => {
166
+ if (chunk !== null) {
167
+ this.responseParser.execute(
168
+ Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
169
+ );
170
+ }
171
+ }
172
+ });
173
+ this.writeBuffer = [];
174
+ this.responseType = "bypassed";
175
+ this.onRequestStart = (versionMajor, versionMinor, rawHeaders, _, path, __, ___, ____, shouldKeepAlive) => {
176
+ this.shouldKeepAlive = shouldKeepAlive;
177
+ const url = new URL(path, this.baseUrl);
178
+ const method = this.connectionOptions.method || "GET";
179
+ const headers = parseRawHeaders(rawHeaders);
180
+ const canHaveBody = method !== "GET" && method !== "HEAD";
181
+ if (url.username || url.password) {
182
+ if (!headers.has("authorization")) {
183
+ headers.set("authorization", `Basic ${url.username}:${url.password}`);
184
+ }
185
+ url.username = "";
186
+ url.password = "";
187
+ }
188
+ if (canHaveBody) {
189
+ this.requestStream = new Readable({
190
+ /**
191
+ * @note Provide the `read()` method so a `Readable` could be
192
+ * used as the actual request body (the stream calls "read()").
193
+ * We control the queue in the onRequestBody/End functions.
194
+ */
195
+ read: () => {
196
+ this.flushWriteBuffer();
197
+ }
198
+ });
199
+ }
200
+ const requestId = createRequestId();
201
+ this.request = new Request(url, {
202
+ method,
203
+ headers,
204
+ credentials: "same-origin",
205
+ // @ts-expect-error Undocumented Fetch property.
206
+ duplex: canHaveBody ? "half" : void 0,
207
+ body: canHaveBody ? Readable.toWeb(this.requestStream) : null
208
+ });
209
+ Reflect.set(this.request, kRequestId, requestId);
210
+ if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
211
+ this.passthrough();
212
+ return;
213
+ }
214
+ this.onRequest({
215
+ requestId,
216
+ request: this.request,
217
+ socket: this
218
+ });
219
+ };
220
+ this.onResponseStart = (versionMajor, versionMinor, rawHeaders, method, url, status, statusText) => {
221
+ const headers = parseRawHeaders(rawHeaders);
222
+ const canHaveBody = !RESPONSE_STATUS_CODES_WITHOUT_BODY.has(status);
223
+ if (canHaveBody) {
224
+ this.responseStream = new Readable();
225
+ }
226
+ const response = new Response(
227
+ /**
228
+ * @note The Fetch API response instance exposed to the consumer
229
+ * is created over the response stream of the HTTP parser. It is NOT
230
+ * related to the Socket instance. This way, you can read response body
231
+ * in response listener while the Socket instance delays the emission
232
+ * of "end" and other events until those response listeners are finished.
233
+ */
234
+ canHaveBody ? Readable.toWeb(this.responseStream) : null,
235
+ {
236
+ status,
237
+ statusText,
238
+ headers
239
+ }
240
+ );
241
+ invariant(
242
+ this.request,
243
+ "Failed to handle a response: request does not exist"
244
+ );
245
+ if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
246
+ return;
247
+ }
248
+ this.responseListenersPromise = this.onResponse({
249
+ response,
250
+ isMockedResponse: this.responseType === "mock",
251
+ requestId: Reflect.get(this.request, kRequestId),
252
+ request: this.request,
253
+ socket: this
254
+ });
255
+ };
256
+ this.connectionOptions = options.connectionOptions;
257
+ this.createConnection = options.createConnection;
258
+ this.onRequest = options.onRequest;
259
+ this.onResponse = options.onResponse;
260
+ this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions);
261
+ this.requestParser = new HTTPParser();
262
+ this.requestParser.initialize(HTTPParser.REQUEST, {});
263
+ this.requestParser[HTTPParser.kOnHeadersComplete] = this.onRequestStart.bind(this);
264
+ this.requestParser[HTTPParser.kOnBody] = this.onRequestBody.bind(this);
265
+ this.requestParser[HTTPParser.kOnMessageComplete] = this.onRequestEnd.bind(this);
266
+ this.responseParser = new HTTPParser();
267
+ this.responseParser.initialize(HTTPParser.RESPONSE, {});
268
+ this.responseParser[HTTPParser.kOnHeadersComplete] = this.onResponseStart.bind(this);
269
+ this.responseParser[HTTPParser.kOnBody] = this.onResponseBody.bind(this);
270
+ this.responseParser[HTTPParser.kOnMessageComplete] = this.onResponseEnd.bind(this);
271
+ this.once("finish", () => this.requestParser.free());
272
+ if (this.baseUrl.protocol === "https:") {
273
+ Reflect.set(this, "encrypted", true);
274
+ Reflect.set(this, "authorized", false);
275
+ Reflect.set(this, "getProtocol", () => "TLSv1.3");
276
+ Reflect.set(this, "getSession", () => void 0);
277
+ Reflect.set(this, "isSessionReused", () => false);
278
+ }
279
+ }
280
+ emit(event, ...args) {
281
+ const emitEvent = super.emit.bind(this, event, ...args);
282
+ if (this.responseListenersPromise) {
283
+ this.responseListenersPromise.finally(emitEvent);
284
+ return this.listenerCount(event) > 0;
285
+ }
286
+ return emitEvent();
287
+ }
288
+ destroy(error) {
289
+ this.responseParser.free();
290
+ if (error) {
291
+ this.emit("error", error);
292
+ }
293
+ return super.destroy(error);
294
+ }
295
+ /**
296
+ * Establish this Socket connection as-is and pipe
297
+ * its data/events through this Socket.
298
+ */
299
+ passthrough() {
300
+ if (this.destroyed) {
301
+ return;
302
+ }
303
+ const socket = this.createConnection();
304
+ this.once("error", (error) => {
305
+ socket.destroy(error);
306
+ });
307
+ this.address = socket.address.bind(socket);
308
+ let writeArgs;
309
+ let headersWritten = false;
310
+ while (writeArgs = this.writeBuffer.shift()) {
311
+ if (writeArgs !== void 0) {
312
+ if (!headersWritten) {
313
+ const [chunk, encoding, callback] = writeArgs;
314
+ const chunkString = chunk.toString();
315
+ const chunkBeforeRequestHeaders = chunkString.slice(
316
+ 0,
317
+ chunkString.indexOf("\r\n") + 2
318
+ );
319
+ const chunkAfterRequestHeaders = chunkString.slice(
320
+ chunk.indexOf("\r\n\r\n")
321
+ );
322
+ const requestHeaders = getRawFetchHeaders(this.request.headers) || this.request.headers;
323
+ const requestHeadersString = Array.from(requestHeaders.entries()).filter(([name]) => name !== INTERNAL_REQUEST_ID_HEADER_NAME).map(([name, value]) => `${name}: ${value}`).join("\r\n");
324
+ const headersChunk = `${chunkBeforeRequestHeaders}${requestHeadersString}${chunkAfterRequestHeaders}`;
325
+ socket.write(headersChunk, encoding, callback);
326
+ headersWritten = true;
327
+ continue;
328
+ }
329
+ socket.write(...writeArgs);
330
+ }
331
+ }
332
+ if (Reflect.get(socket, "encrypted")) {
333
+ const tlsProperties = [
334
+ "encrypted",
335
+ "authorized",
336
+ "getProtocol",
337
+ "getSession",
338
+ "isSessionReused"
339
+ ];
340
+ tlsProperties.forEach((propertyName) => {
341
+ Object.defineProperty(this, propertyName, {
342
+ enumerable: true,
343
+ get: () => {
344
+ const value = Reflect.get(socket, propertyName);
345
+ return typeof value === "function" ? value.bind(socket) : value;
346
+ }
347
+ });
348
+ });
349
+ }
350
+ socket.on("lookup", (...args) => this.emit("lookup", ...args)).on("connect", () => {
351
+ this.connecting = socket.connecting;
352
+ this.emit("connect");
353
+ }).on("secureConnect", () => this.emit("secureConnect")).on("secure", () => this.emit("secure")).on("session", (session) => this.emit("session", session)).on("ready", () => this.emit("ready")).on("drain", () => this.emit("drain")).on("data", (chunk) => {
354
+ this.push(chunk);
355
+ }).on("error", (error) => {
356
+ Reflect.set(this, "_hadError", Reflect.get(socket, "_hadError"));
357
+ this.emit("error", error);
358
+ }).on("resume", () => this.emit("resume")).on("timeout", () => this.emit("timeout")).on("prefinish", () => this.emit("prefinish")).on("finish", () => this.emit("finish")).on("close", (hadError) => this.emit("close", hadError)).on("end", () => this.emit("end"));
359
+ }
360
+ /**
361
+ * Convert the given Fetch API `Response` instance to an
362
+ * HTTP message and push it to the socket.
363
+ */
364
+ async respondWith(response) {
365
+ var _a;
366
+ if (this.destroyed) {
367
+ return;
368
+ }
369
+ if (isPropertyAccessible(response, "type") && response.type === "error") {
370
+ this.errorWith(new TypeError("Network error"));
371
+ return;
372
+ }
373
+ this.mockConnect();
374
+ this.responseType = "mock";
375
+ this.flushWriteBuffer();
376
+ const serverResponse = new ServerResponse(new IncomingMessage(this));
377
+ serverResponse.assignSocket(
378
+ new MockSocket({
379
+ write: (chunk, encoding, callback) => {
380
+ this.push(chunk, encoding);
381
+ callback == null ? void 0 : callback();
382
+ },
383
+ read() {
384
+ }
385
+ })
386
+ );
387
+ serverResponse.statusCode = response.status;
388
+ serverResponse.statusMessage = response.statusText;
389
+ serverResponse.removeHeader("connection");
390
+ serverResponse.removeHeader("date");
391
+ this.once("error", () => {
392
+ serverResponse.destroy();
393
+ });
394
+ const headers = getRawFetchHeaders(response.headers) || response.headers;
395
+ for (const [name, value] of headers) {
396
+ serverResponse.setHeader(name, value);
397
+ }
398
+ if (response.body) {
399
+ try {
400
+ const reader = response.body.getReader();
401
+ while (true) {
402
+ const { done, value } = await reader.read();
403
+ if (done) {
404
+ serverResponse.end();
405
+ break;
406
+ }
407
+ serverResponse.write(value);
408
+ }
409
+ } catch (error) {
410
+ this.respondWith(createServerErrorResponse(error));
411
+ return;
412
+ }
413
+ } else {
414
+ serverResponse.end();
415
+ }
416
+ if (!this.shouldKeepAlive) {
417
+ this.emit("readable");
418
+ (_a = this.responseStream) == null ? void 0 : _a.push(null);
419
+ this.push(null);
420
+ }
421
+ }
422
+ /**
423
+ * Close this socket connection with the given error.
424
+ */
425
+ errorWith(error) {
426
+ this.destroy(error);
427
+ }
428
+ mockConnect() {
429
+ this.connecting = false;
430
+ const isIPv6 = net2.isIPv6(this.connectionOptions.hostname) || this.connectionOptions.family === 6;
431
+ const addressInfo = {
432
+ address: isIPv6 ? "::1" : "127.0.0.1",
433
+ family: isIPv6 ? "IPv6" : "IPv4",
434
+ port: this.connectionOptions.port
435
+ };
436
+ this.address = () => addressInfo;
437
+ this.emit(
438
+ "lookup",
439
+ null,
440
+ addressInfo.address,
441
+ addressInfo.family === "IPv6" ? 6 : 4,
442
+ this.connectionOptions.host
443
+ );
444
+ this.emit("connect");
445
+ this.emit("ready");
446
+ if (this.baseUrl.protocol === "https:") {
447
+ this.emit("secure");
448
+ this.emit("secureConnect");
449
+ this.emit(
450
+ "session",
451
+ this.connectionOptions.session || Buffer.from("mock-session-renegotiate")
452
+ );
453
+ this.emit("session", Buffer.from("mock-session-resume"));
454
+ }
455
+ }
456
+ flushWriteBuffer() {
457
+ var _a;
458
+ let args;
459
+ while (args = this.writeBuffer.shift()) {
460
+ (_a = args == null ? void 0 : args[2]) == null ? void 0 : _a.call(args);
461
+ }
462
+ }
463
+ onRequestBody(chunk) {
464
+ invariant(
465
+ this.requestStream,
466
+ "Failed to write to a request stream: stream does not exist"
467
+ );
468
+ this.requestStream.push(chunk);
469
+ }
470
+ onRequestEnd() {
471
+ if (this.requestStream) {
472
+ this.requestStream.push(null);
473
+ }
474
+ }
475
+ onResponseBody(chunk) {
476
+ invariant(
477
+ this.responseStream,
478
+ "Failed to write to a response stream: stream does not exist"
479
+ );
480
+ this.responseStream.push(chunk);
481
+ }
482
+ onResponseEnd() {
483
+ if (this.responseStream) {
484
+ this.responseStream.push(null);
485
+ }
486
+ }
487
+ };
488
+
489
+ // src/interceptors/ClientRequest/agents.ts
490
+ import http from "http";
491
+ import https from "https";
492
+ var MockAgent = class extends http.Agent {
493
+ constructor(options) {
494
+ super();
495
+ this.customAgent = options.customAgent;
496
+ this.onRequest = options.onRequest;
497
+ this.onResponse = options.onResponse;
498
+ }
499
+ createConnection(options, callback) {
500
+ const createConnection = this.customAgent instanceof http.Agent && this.customAgent.createConnection || super.createConnection;
501
+ const socket = new MockHttpSocket({
502
+ connectionOptions: options,
503
+ createConnection: createConnection.bind(this, options, callback),
504
+ onRequest: this.onRequest.bind(this),
505
+ onResponse: this.onResponse.bind(this)
506
+ });
507
+ return socket;
508
+ }
509
+ };
510
+ var MockHttpsAgent = class extends https.Agent {
511
+ constructor(options) {
512
+ super();
513
+ this.customAgent = options.customAgent;
514
+ this.onRequest = options.onRequest;
515
+ this.onResponse = options.onResponse;
516
+ }
517
+ createConnection(options, callback) {
518
+ const createConnection = this.customAgent instanceof https.Agent && this.customAgent.createConnection || super.createConnection;
519
+ const socket = new MockHttpSocket({
520
+ connectionOptions: options,
521
+ createConnection: createConnection.bind(this, options, callback),
522
+ onRequest: this.onRequest.bind(this),
523
+ onResponse: this.onResponse.bind(this)
524
+ });
525
+ return socket;
526
+ }
527
+ };
528
+
529
+ // src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
530
+ import {
531
+ Agent as HttpAgent,
532
+ globalAgent as httpGlobalAgent
533
+ } from "http";
534
+ import {
535
+ Agent as HttpsAgent,
536
+ globalAgent as httpsGlobalAgent
537
+ } from "https";
538
+ import {
539
+ URL as URL2,
540
+ parse as parseUrl
541
+ } from "url";
542
+ import { Logger as Logger3 } from "@open-draft/logger";
543
+
544
+ // src/utils/getRequestOptionsByUrl.ts
545
+ function getRequestOptionsByUrl(url) {
546
+ const options = {
547
+ method: "GET",
548
+ protocol: url.protocol,
549
+ hostname: typeof url.hostname === "string" && url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname,
550
+ host: url.host,
551
+ path: `${url.pathname}${url.search || ""}`
552
+ };
553
+ if (!!url.port) {
554
+ options.port = Number(url.port);
555
+ }
556
+ if (url.username || url.password) {
557
+ options.auth = `${url.username}:${url.password}`;
558
+ }
559
+ return options;
560
+ }
561
+
562
+ // src/utils/getUrlByRequestOptions.ts
563
+ import { Agent } from "http";
564
+ import { Logger } from "@open-draft/logger";
565
+ var logger = new Logger("utils getUrlByRequestOptions");
566
+ var DEFAULT_PATH = "/";
567
+ var DEFAULT_PROTOCOL = "http:";
568
+ var DEFAULT_HOSTNAME = "localhost";
569
+ var SSL_PORT = 443;
570
+ function getAgent(options) {
571
+ return options.agent instanceof Agent ? options.agent : void 0;
572
+ }
573
+ function getProtocolByRequestOptions(options) {
574
+ var _a;
575
+ if (options.protocol) {
576
+ return options.protocol;
577
+ }
578
+ const agent = getAgent(options);
579
+ const agentProtocol = agent == null ? void 0 : agent.protocol;
580
+ if (agentProtocol) {
581
+ return agentProtocol;
582
+ }
583
+ const port = getPortByRequestOptions(options);
584
+ const isSecureRequest = options.cert || port === SSL_PORT;
585
+ return isSecureRequest ? "https:" : ((_a = options.uri) == null ? void 0 : _a.protocol) || DEFAULT_PROTOCOL;
586
+ }
587
+ function getPortByRequestOptions(options) {
588
+ if (options.port) {
589
+ return Number(options.port);
590
+ }
591
+ const agent = getAgent(options);
592
+ if (agent == null ? void 0 : agent.options.port) {
593
+ return Number(agent.options.port);
594
+ }
595
+ if (agent == null ? void 0 : agent.defaultPort) {
596
+ return Number(agent.defaultPort);
597
+ }
598
+ return void 0;
599
+ }
600
+ function getAuthByRequestOptions(options) {
601
+ if (options.auth) {
602
+ const [username, password] = options.auth.split(":");
603
+ return { username, password };
604
+ }
605
+ }
606
+ function isRawIPv6Address(host) {
607
+ return host.includes(":") && !host.startsWith("[") && !host.endsWith("]");
608
+ }
609
+ function getHostname(options) {
610
+ let host = options.hostname || options.host;
611
+ if (host) {
612
+ if (isRawIPv6Address(host)) {
613
+ host = `[${host}]`;
614
+ }
615
+ return new URL(`http://${host}`).hostname;
616
+ }
617
+ return DEFAULT_HOSTNAME;
618
+ }
619
+ function getUrlByRequestOptions(options) {
620
+ logger.info("request options", options);
621
+ if (options.uri) {
622
+ logger.info(
623
+ 'constructing url from explicitly provided "options.uri": %s',
624
+ options.uri
625
+ );
626
+ return new URL(options.uri.href);
627
+ }
628
+ logger.info("figuring out url from request options...");
629
+ const protocol = getProtocolByRequestOptions(options);
630
+ logger.info("protocol", protocol);
631
+ const port = getPortByRequestOptions(options);
632
+ logger.info("port", port);
633
+ const hostname = getHostname(options);
634
+ logger.info("hostname", hostname);
635
+ const path = options.path || DEFAULT_PATH;
636
+ logger.info("path", path);
637
+ const credentials = getAuthByRequestOptions(options);
638
+ logger.info("credentials", credentials);
639
+ const authString = credentials ? `${credentials.username}:${credentials.password}@` : "";
640
+ logger.info("auth string:", authString);
641
+ const portString = typeof port !== "undefined" ? `:${port}` : "";
642
+ const url = new URL(`${protocol}//${hostname}${portString}${path}`);
643
+ url.username = (credentials == null ? void 0 : credentials.username) || "";
644
+ url.password = (credentials == null ? void 0 : credentials.password) || "";
645
+ logger.info("created url:", url);
646
+ return url;
647
+ }
648
+
649
+ // src/utils/cloneObject.ts
650
+ import { Logger as Logger2 } from "@open-draft/logger";
651
+ var logger2 = new Logger2("cloneObject");
652
+ function isPlainObject(obj) {
653
+ var _a;
654
+ logger2.info("is plain object?", obj);
655
+ if (obj == null || !((_a = obj.constructor) == null ? void 0 : _a.name)) {
656
+ logger2.info("given object is undefined, not a plain object...");
657
+ return false;
658
+ }
659
+ logger2.info("checking the object constructor:", obj.constructor.name);
660
+ return obj.constructor.name === "Object";
661
+ }
662
+ function cloneObject(obj) {
663
+ logger2.info("cloning object:", obj);
664
+ const enumerableProperties = Object.entries(obj).reduce(
665
+ (acc, [key, value]) => {
666
+ logger2.info("analyzing key-value pair:", key, value);
667
+ acc[key] = isPlainObject(value) ? cloneObject(value) : value;
668
+ return acc;
669
+ },
670
+ {}
671
+ );
672
+ return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties);
673
+ }
674
+
675
+ // src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
676
+ var logger3 = new Logger3("http normalizeClientRequestArgs");
677
+ function resolveRequestOptions(args, url) {
678
+ if (typeof args[1] === "undefined" || typeof args[1] === "function") {
679
+ logger3.info("request options not provided, deriving from the url", url);
680
+ return getRequestOptionsByUrl(url);
681
+ }
682
+ if (args[1]) {
683
+ logger3.info("has custom RequestOptions!", args[1]);
684
+ const requestOptionsFromUrl = getRequestOptionsByUrl(url);
685
+ logger3.info("derived RequestOptions from the URL:", requestOptionsFromUrl);
686
+ logger3.info("cloning RequestOptions...");
687
+ const clonedRequestOptions = cloneObject(args[1]);
688
+ logger3.info("successfully cloned RequestOptions!", clonedRequestOptions);
689
+ return {
690
+ ...requestOptionsFromUrl,
691
+ ...clonedRequestOptions
692
+ };
693
+ }
694
+ logger3.info("using an empty object as request options");
695
+ return {};
696
+ }
697
+ function overrideUrlByRequestOptions(url, options) {
698
+ url.host = options.host || url.host;
699
+ url.hostname = options.hostname || url.hostname;
700
+ url.port = options.port ? options.port.toString() : url.port;
701
+ if (options.path) {
702
+ const parsedOptionsPath = parseUrl(options.path, false);
703
+ url.pathname = parsedOptionsPath.pathname || "";
704
+ url.search = parsedOptionsPath.search || "";
705
+ }
706
+ return url;
707
+ }
708
+ function resolveCallback(args) {
709
+ return typeof args[1] === "function" ? args[1] : args[2];
710
+ }
711
+ function normalizeClientRequestArgs(defaultProtocol, args) {
712
+ let url;
713
+ let options;
714
+ let callback;
715
+ logger3.info("arguments", args);
716
+ logger3.info("using default protocol:", defaultProtocol);
717
+ if (args.length === 0) {
718
+ const url2 = new URL2("http://localhost");
719
+ const options2 = resolveRequestOptions(args, url2);
720
+ return [url2, options2];
721
+ }
722
+ if (typeof args[0] === "string") {
723
+ logger3.info("first argument is a location string:", args[0]);
724
+ url = new URL2(args[0]);
725
+ logger3.info("created a url:", url);
726
+ const requestOptionsFromUrl = getRequestOptionsByUrl(url);
727
+ logger3.info("request options from url:", requestOptionsFromUrl);
728
+ options = resolveRequestOptions(args, url);
729
+ logger3.info("resolved request options:", options);
730
+ callback = resolveCallback(args);
731
+ } else if (args[0] instanceof URL2) {
732
+ url = args[0];
733
+ logger3.info("first argument is a URL:", url);
734
+ if (typeof args[1] !== "undefined" && isObject(args[1])) {
735
+ url = overrideUrlByRequestOptions(url, args[1]);
736
+ }
737
+ options = resolveRequestOptions(args, url);
738
+ logger3.info("derived request options:", options);
739
+ callback = resolveCallback(args);
740
+ } else if ("hash" in args[0] && !("method" in args[0])) {
741
+ const [legacyUrl] = args;
742
+ logger3.info("first argument is a legacy URL:", legacyUrl);
743
+ if (legacyUrl.hostname === null) {
744
+ logger3.info("given legacy URL is relative (no hostname)");
745
+ return isObject(args[1]) ? normalizeClientRequestArgs(defaultProtocol, [
746
+ { path: legacyUrl.path, ...args[1] },
747
+ args[2]
748
+ ]) : normalizeClientRequestArgs(defaultProtocol, [
749
+ { path: legacyUrl.path },
750
+ args[1]
751
+ ]);
752
+ }
753
+ logger3.info("given legacy url is absolute");
754
+ const resolvedUrl = new URL2(legacyUrl.href);
755
+ return args[1] === void 0 ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl]) : typeof args[1] === "function" ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl, args[1]]) : normalizeClientRequestArgs(defaultProtocol, [
756
+ resolvedUrl,
757
+ args[1],
758
+ args[2]
759
+ ]);
760
+ } else if (isObject(args[0])) {
761
+ options = { ...args[0] };
762
+ logger3.info("first argument is RequestOptions:", options);
763
+ options.protocol = options.protocol || defaultProtocol;
764
+ logger3.info("normalized request options:", options);
765
+ url = getUrlByRequestOptions(options);
766
+ logger3.info("created a URL from RequestOptions:", url.href);
767
+ callback = resolveCallback(args);
768
+ } else {
769
+ throw new Error(
770
+ `Failed to construct ClientRequest with these parameters: ${args}`
771
+ );
772
+ }
773
+ options.protocol = options.protocol || url.protocol;
774
+ options.method = options.method || "GET";
775
+ if (typeof options.agent === "undefined") {
776
+ const agent = options.protocol === "https:" ? new HttpsAgent({
777
+ rejectUnauthorized: options.rejectUnauthorized
778
+ }) : new HttpAgent();
779
+ options.agent = agent;
780
+ logger3.info("resolved fallback agent:", agent);
781
+ }
782
+ if (!options._defaultAgent) {
783
+ logger3.info(
784
+ 'has no default agent, setting the default agent for "%s"',
785
+ options.protocol
786
+ );
787
+ options._defaultAgent = options.protocol === "https:" ? httpsGlobalAgent : httpGlobalAgent;
788
+ }
789
+ logger3.info("successfully resolved url:", url.href);
790
+ logger3.info("successfully resolved options:", options);
791
+ logger3.info("successfully resolved callback:", callback);
792
+ if (!(url instanceof URL2)) {
793
+ url = url.toString();
794
+ }
795
+ return [url, options, callback];
796
+ }
797
+
798
+ // src/utils/isNodeLikeError.ts
799
+ function isNodeLikeError(error) {
800
+ if (error == null) {
801
+ return false;
802
+ }
803
+ if (!(error instanceof Error)) {
804
+ return false;
805
+ }
806
+ return "code" in error && "errno" in error;
807
+ }
808
+
809
+ // src/interceptors/ClientRequest/index.ts
810
+ var _ClientRequestInterceptor = class extends Interceptor {
811
+ constructor() {
812
+ super(_ClientRequestInterceptor.symbol);
813
+ this.onRequest = async ({
814
+ request,
815
+ socket
816
+ }) => {
817
+ const requestId = Reflect.get(request, kRequestId);
818
+ const { interactiveRequest, requestController } = toInteractiveRequest(request);
819
+ this.emitter.once("request", ({ requestId: pendingRequestId }) => {
820
+ if (pendingRequestId !== requestId) {
821
+ return;
822
+ }
823
+ if (requestController.responsePromise.state === "pending") {
824
+ this.logger.info(
825
+ "request has not been handled in listeners, executing fail-safe listener..."
826
+ );
827
+ requestController.responsePromise.resolve(void 0);
828
+ }
829
+ });
830
+ const listenerResult = await until(async () => {
831
+ await emitAsync(this.emitter, "request", {
832
+ requestId,
833
+ request: interactiveRequest
834
+ });
835
+ return await requestController.responsePromise;
836
+ });
837
+ if (listenerResult.error) {
838
+ if (listenerResult.error instanceof Response) {
839
+ socket.respondWith(listenerResult.error);
840
+ return;
841
+ }
842
+ if (isNodeLikeError(listenerResult.error)) {
843
+ socket.errorWith(listenerResult.error);
844
+ return;
845
+ }
846
+ if (this.emitter.listenerCount("unhandledException") > 0) {
847
+ await emitAsync(this.emitter, "unhandledException", {
848
+ error: listenerResult.error,
849
+ request,
850
+ requestId,
851
+ controller: {
852
+ respondWith: socket.respondWith.bind(socket),
853
+ errorWith: socket.errorWith.bind(socket)
854
+ }
855
+ });
856
+ if (!socket.connecting || socket.destroyed) {
857
+ return;
858
+ }
859
+ }
860
+ socket.respondWith(createServerErrorResponse(listenerResult.error));
861
+ return;
862
+ }
863
+ const mockedResponse = listenerResult.data;
864
+ if (mockedResponse) {
865
+ socket.respondWith(mockedResponse);
866
+ return;
867
+ }
868
+ socket.passthrough();
869
+ };
870
+ this.onResponse = async ({
871
+ requestId,
872
+ request,
873
+ response,
874
+ isMockedResponse
875
+ }) => {
876
+ return emitAsync(this.emitter, "response", {
877
+ requestId,
878
+ request,
879
+ response,
880
+ isMockedResponse
881
+ });
882
+ };
883
+ }
884
+ setup() {
885
+ const { get: originalGet, request: originalRequest } = http2;
886
+ const { get: originalHttpsGet, request: originalHttpsRequest } = https2;
887
+ const onRequest = this.onRequest.bind(this);
888
+ const onResponse = this.onResponse.bind(this);
889
+ http2.request = new Proxy(http2.request, {
890
+ apply: (target, thisArg, args) => {
891
+ const [url, options, callback] = normalizeClientRequestArgs(
892
+ "http:",
893
+ args
894
+ );
895
+ const mockAgent = new MockAgent({
896
+ customAgent: options.agent,
897
+ onRequest,
898
+ onResponse
899
+ });
900
+ options.agent = mockAgent;
901
+ return Reflect.apply(target, thisArg, [url, options, callback]);
902
+ }
903
+ });
904
+ http2.get = new Proxy(http2.get, {
905
+ apply: (target, thisArg, args) => {
906
+ const [url, options, callback] = normalizeClientRequestArgs(
907
+ "http:",
908
+ args
909
+ );
910
+ const mockAgent = new MockAgent({
911
+ customAgent: options.agent,
912
+ onRequest,
913
+ onResponse
914
+ });
915
+ options.agent = mockAgent;
916
+ return Reflect.apply(target, thisArg, [url, options, callback]);
917
+ }
918
+ });
919
+ https2.request = new Proxy(https2.request, {
920
+ apply: (target, thisArg, args) => {
921
+ const [url, options, callback] = normalizeClientRequestArgs(
922
+ "https:",
923
+ args
924
+ );
925
+ const mockAgent = new MockHttpsAgent({
926
+ customAgent: options.agent,
927
+ onRequest,
928
+ onResponse
929
+ });
930
+ options.agent = mockAgent;
931
+ return Reflect.apply(target, thisArg, [url, options, callback]);
932
+ }
933
+ });
934
+ https2.get = new Proxy(https2.get, {
935
+ apply: (target, thisArg, args) => {
936
+ const [url, options, callback] = normalizeClientRequestArgs(
937
+ "https:",
938
+ args
939
+ );
940
+ const mockAgent = new MockHttpsAgent({
941
+ customAgent: options.agent,
942
+ onRequest,
943
+ onResponse
944
+ });
945
+ options.agent = mockAgent;
946
+ return Reflect.apply(target, thisArg, [url, options, callback]);
947
+ }
948
+ });
949
+ this.subscriptions.push(() => {
950
+ http2.get = originalGet;
951
+ http2.request = originalRequest;
952
+ https2.get = originalHttpsGet;
953
+ https2.request = originalHttpsRequest;
954
+ });
955
+ }
956
+ };
957
+ var ClientRequestInterceptor = _ClientRequestInterceptor;
958
+ ClientRequestInterceptor.symbol = Symbol("client-request-interceptor");
959
+
960
+ export {
961
+ ClientRequestInterceptor
962
+ };
963
+ //# sourceMappingURL=chunk-3OJLYEWA.mjs.map