@mswjs/interceptors 0.42.3 → 0.42.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/lib/browser/interceptors/WebSocket/index.js +14 -1
  2. package/lib/browser/interceptors/WebSocket/index.js.map +1 -1
  3. package/lib/browser/interceptors/fetch/web.js +1 -1
  4. package/lib/browser/presets/browser.js +1 -1
  5. package/lib/browser/{web-CdcYFjgm.js → web-DUlFZEtz.js} +58 -3
  6. package/lib/browser/web-DUlFZEtz.js.map +1 -0
  7. package/lib/node/{batch-interceptor-ByEZVih3.js → batch-interceptor-DMOfd-9x.js} +2 -2
  8. package/lib/node/{batch-interceptor-ByEZVih3.js.map → batch-interceptor-DMOfd-9x.js.map} +1 -1
  9. package/lib/node/buffer-utils-B4FUq-l2.js +17 -0
  10. package/lib/node/buffer-utils-B4FUq-l2.js.map +1 -0
  11. package/lib/node/create-request-id-DHo-fRTV.js +14 -0
  12. package/lib/node/create-request-id-DHo-fRTV.js.map +1 -0
  13. package/lib/node/{fetch-utils-Dw1PsmtX.js → fetch-utils-D-_xeRlK.js} +3 -14
  14. package/lib/node/{fetch-utils-Dw1PsmtX.js.map → fetch-utils-D-_xeRlK.js.map} +1 -1
  15. package/lib/node/{has-configurable-global-BU1sT1u4.js → has-configurable-global-CT6-QYdg.js} +2 -2
  16. package/lib/node/{has-configurable-global-BU1sT1u4.js.map → has-configurable-global-CT6-QYdg.js.map} +1 -1
  17. package/lib/node/index.d.ts +2 -243
  18. package/lib/node/index.js +6 -25
  19. package/lib/node/index.js.map +1 -1
  20. package/lib/node/{buffer-utils-BvPY1Tc-.js → interceptor-C8qRPjxG.js} +2 -16
  21. package/lib/node/interceptor-C8qRPjxG.js.map +1 -0
  22. package/lib/node/interceptors/ClientRequest/index.js +3 -3
  23. package/lib/node/interceptors/WebSocket/index.d.ts +2 -0
  24. package/lib/node/interceptors/WebSocket/index.js +635 -0
  25. package/lib/node/interceptors/WebSocket/index.js.map +1 -0
  26. package/lib/node/interceptors/XMLHttpRequest/node.js +5 -5
  27. package/lib/node/interceptors/fetch/node.js +5 -5
  28. package/lib/node/interceptors/http/index.js +2 -2
  29. package/lib/node/interceptors/net/index.d.ts +16 -0
  30. package/lib/node/interceptors/net/index.js +1 -1
  31. package/lib/node/{net-Ca8p1rIe.js → net-Bh16MP4u.js} +82 -92
  32. package/lib/node/net-Bh16MP4u.js.map +1 -0
  33. package/lib/node/patches-registry-DxR5TEc-.js +76 -0
  34. package/lib/node/patches-registry-DxR5TEc-.js.map +1 -0
  35. package/lib/node/remote-http-interceptor.js +4 -4
  36. package/lib/node/resolve-web-socket-url-CSvNPLGi.js +25 -0
  37. package/lib/node/resolve-web-socket-url-CSvNPLGi.js.map +1 -0
  38. package/lib/node/{source-CIgPng7r.js → source-DHVO1vzq.js} +327 -213
  39. package/lib/node/source-DHVO1vzq.js.map +1 -0
  40. package/lib/node/websocket-CC0nB0md.d.ts +285 -0
  41. package/package.json +5 -2
  42. package/src/interceptors/WebSocket/index.ts +4 -1
  43. package/src/interceptors/fetch/web.ts +11 -2
  44. package/src/interceptors/http/http-parser/index.ts +9 -1
  45. package/src/interceptors/http/http-parser.ts +21 -4
  46. package/src/interceptors/http/source.ts +368 -283
  47. package/src/interceptors/net/index.ts +39 -12
  48. package/src/interceptors/net/socket-controller.ts +126 -66
  49. package/src/utils/clone-response.ts +72 -0
  50. package/src/utils/internal-connection.ts +22 -0
  51. package/lib/browser/web-CdcYFjgm.js.map +0 -1
  52. package/lib/node/buffer-utils-BvPY1Tc-.js.map +0 -1
  53. package/lib/node/net-Ca8p1rIe.js.map +0 -1
  54. package/lib/node/source-CIgPng7r.js.map +0 -1
@@ -1,6 +1,8 @@
1
- import { a as createLogger, i as Interceptor, o as formatRequest, r as toBuffer } from "./buffer-utils-BvPY1Tc-.js";
2
- import { a as isResponseError, c as isObject, d as createRequestId, f as RequestController, l as getRawFetchHeaders, n as FetchResponse, o as isResponseLike, p as InterceptorError, r as createServerErrorResponse, s as kErrorResponse, t as FetchRequest, u as recordRawFetchHeaders } from "./fetch-utils-Dw1PsmtX.js";
3
- import { i as unwrapPendingData, n as SocketController, r as kRawSocket, t as SocketInterceptor } from "./net-Ca8p1rIe.js";
1
+ import { n as createLogger, r as formatRequest, t as Interceptor } from "./interceptor-C8qRPjxG.js";
2
+ import { a as isResponseError, c as isObject, d as recordRawFetchHeaders, f as RequestController, l as copyRawHeaders, n as FetchResponse, o as isResponseLike, p as InterceptorError, r as createServerErrorResponse, s as kErrorResponse, t as FetchRequest, u as getRawFetchHeaders } from "./fetch-utils-D-_xeRlK.js";
3
+ import { t as createRequestId } from "./create-request-id-DHo-fRTV.js";
4
+ import { r as toBuffer } from "./buffer-utils-B4FUq-l2.js";
5
+ import { i as unwrapPendingData, n as SocketController, r as kRawSocket, t as SocketInterceptor } from "./net-Bh16MP4u.js";
4
6
  import { TypedEvent } from "rettime";
5
7
  import { invariant } from "outvariant";
6
8
  import { IncomingMessage, METHODS, STATUS_CODES, ServerResponse } from "node:http";
@@ -1330,7 +1332,12 @@ var HttpParser = class {
1330
1332
  if (errorCode === import_constants.ERROR.OK) return;
1331
1333
  const errorPointer = llhttp_get_error_reason(this[kPointer]);
1332
1334
  const length = new Uint8Array(llhttp_memory.buffer).indexOf(0, errorPointer) - errorPointer;
1333
- throw new Error(readStringFrom(errorPointer, length));
1335
+ const error = new Error(readStringFrom(errorPointer, length));
1336
+ if (this[kCallbacks].onError) {
1337
+ this[kCallbacks].onError(error);
1338
+ return;
1339
+ }
1340
+ throw error;
1334
1341
  }
1335
1342
  };
1336
1343
  //#endregion
@@ -1340,6 +1347,7 @@ var HttpRequestParser = class extends HttpParser {
1340
1347
  #upgrade = false;
1341
1348
  constructor(options) {
1342
1349
  super(1, {
1350
+ onError: options.onError,
1343
1351
  onHeadersComplete: ({ rawHeaders, method, url: path, upgrade }) => {
1344
1352
  this.#upgrade = upgrade;
1345
1353
  /**
@@ -1386,6 +1394,7 @@ read: () => {} });
1386
1394
  },
1387
1395
  onMessageComplete: () => {
1388
1396
  this.#requestBodyStream?.push(null);
1397
+ this.#requestBodyStream = void 0;
1389
1398
  /**
1390
1399
  * @note An upgraded exchange (e.g. "CONNECT", WebSocket) has
1391
1400
  * no message boundary: it takes the connection over, and the
@@ -1395,17 +1404,20 @@ read: () => {} });
1395
1404
  }
1396
1405
  });
1397
1406
  }
1398
- free() {
1407
+ free(error) {
1399
1408
  this.destroy();
1400
- this.#requestBodyStream?.destroy();
1409
+ this.#requestBodyStream?.destroy(error);
1401
1410
  this.#requestBodyStream = void 0;
1402
1411
  }
1403
1412
  };
1404
1413
  var HttpResponseParser = class extends HttpParser {
1405
1414
  #responseBodyStream;
1415
+ #status = 0;
1406
1416
  constructor(options) {
1407
1417
  super(2, {
1418
+ onError: options.onError,
1408
1419
  onHeadersComplete: ({ rawHeaders, statusCode: status, statusMessage: statusText }) => {
1420
+ this.#status = status;
1409
1421
  const headers = FetchResponse.parseRawHeaders([...rawHeaders]);
1410
1422
  const response = new FetchResponse(FetchResponse.isResponseWithBody(status) ? Readable.toWeb(this.#responseBodyStream = new Readable({ read() {} })) : null, {
1411
1423
  status,
@@ -1420,11 +1432,14 @@ var HttpResponseParser = class extends HttpParser {
1420
1432
  },
1421
1433
  onMessageComplete: () => {
1422
1434
  this.#responseBodyStream?.push(null);
1435
+ this.#responseBodyStream = null;
1436
+ options.onMessageComplete?.(this.#status);
1423
1437
  }
1424
1438
  });
1425
1439
  }
1426
- free() {
1440
+ free(error) {
1427
1441
  this.destroy();
1442
+ if (error) this.#responseBodyStream?.destroy(error);
1428
1443
  this.#responseBodyStream = null;
1429
1444
  }
1430
1445
  };
@@ -1558,6 +1573,56 @@ async function handleRequest(options) {
1558
1573
  return options.controller.handled;
1559
1574
  }
1560
1575
  //#endregion
1576
+ //#region src/utils/clone-response.ts
1577
+ /** Clone for observers without letting their unread body block caller cancellation. */
1578
+ function cloneResponse(response) {
1579
+ const clone = FetchResponse.clone(response);
1580
+ if (!response.body || !clone.body) return [response, clone];
1581
+ const observer = wrapResponse(clone);
1582
+ return [wrapResponse(response, observer.cancel).response, observer.response];
1583
+ }
1584
+ function wrapResponse(response, onCancel) {
1585
+ const body = response.body;
1586
+ const reader = body.getReader();
1587
+ const cancel = (reason) => {
1588
+ return body.locked ? reader.cancel(reason) : body.cancel(reason);
1589
+ };
1590
+ const wrappedResponse = new FetchResponse(new ReadableStream({
1591
+ async pull(controller) {
1592
+ try {
1593
+ const { done, value } = await reader.read();
1594
+ if (done) {
1595
+ controller.close();
1596
+ reader.releaseLock();
1597
+ return;
1598
+ }
1599
+ controller.enqueue(value);
1600
+ } catch (error) {
1601
+ controller.error(error);
1602
+ reader.releaseLock();
1603
+ }
1604
+ },
1605
+ async cancel(reason) {
1606
+ try {
1607
+ const cancellation = cancel(reason);
1608
+ if (onCancel) await Promise.all([cancellation, onCancel(reason)]);
1609
+ else cancellation.catch(() => {});
1610
+ } finally {
1611
+ reader.releaseLock();
1612
+ }
1613
+ }
1614
+ }, { highWaterMark: 0 }), response);
1615
+ copyRawHeaders(response.headers, wrappedResponse.headers);
1616
+ Object.defineProperties(wrappedResponse, {
1617
+ type: { value: response.type },
1618
+ redirected: { value: response.redirected }
1619
+ });
1620
+ return {
1621
+ response: wrappedResponse,
1622
+ cancel
1623
+ };
1624
+ }
1625
+ //#endregion
1561
1626
  //#region src/interceptors/http/source.ts
1562
1627
  const httpLogger = createLogger("http-request");
1563
1628
  /**
@@ -1590,6 +1655,14 @@ var NodeHttpRequestSource = class extends Interceptor {
1590
1655
  let requestParser;
1591
1656
  let tunnelUrl;
1592
1657
  let abortPendingRequest;
1658
+ let pendingRequestController;
1659
+ const stopParsingRequests = (error) => {
1660
+ httpLogger.verbose("stopping HTTP request parsing: %o", error);
1661
+ isHttpConnection = false;
1662
+ if (pendingRequestController?.readyState === RequestController.PENDING) pendingRequestController.passthrough();
1663
+ else socketController.decline();
1664
+ requestParser?.free(error);
1665
+ };
1593
1666
  /**
1594
1667
  * @note Capture the request context of the connection itself.
1595
1668
  * The socket is created synchronously within the request async
@@ -1613,224 +1686,265 @@ var NodeHttpRequestSource = class extends Interceptor {
1613
1686
  abortPendingRequest?.();
1614
1687
  realSocketDestroy(error, callback);
1615
1688
  };
1616
- /**
1617
- * @note Only inspect the first sent packet to determine the protocol.
1618
- * A single socket cannot be used for different protocols.
1619
- */
1620
- socket.on("data", (chunk) => {
1621
- if (isHttpConnection === false) return;
1689
+ const addRequestDataListener = () => {
1690
+ const executeRequestParser = (parser, chunk) => {
1691
+ if (parser.execute(chunk) !== null) {
1692
+ socket.removeListener("data", onRequestData);
1693
+ parser.free();
1694
+ requestParser = void 0;
1695
+ }
1696
+ };
1622
1697
  /**
1623
- * @note A mocked "CONNECT" request has established a tunnel.
1624
- * The data that follows belongs to a new exchange addressed to
1625
- * the tunnel target. The parser stopped at the tunnel boundary
1626
- * (HTTP upgrade semantics), so tear it down and detect the
1627
- * tunneled protocol anew, like on a fresh connection.
1698
+ * @note Inspect the first sent packet to determine the protocol,
1699
+ * including when entering a mocked "CONNECT" tunnel.
1628
1700
  */
1629
- if (tunnelUrl && requestParser) {
1630
- requestParser.free();
1631
- requestParser = void 0;
1632
- isHttpConnection = void 0;
1633
- /**
1634
- * @note Retarget the connection to the tunnel authority.
1635
- * The exchanges that follow belong to the tunnel target,
1636
- * so an unclaimed exchange (HTTP or not) must pass through
1637
- * to that target — not to the proxy, which never actually
1638
- * established this tunnel — like a real established tunnel
1639
- * relays its traffic.
1640
- */
1641
- socketController.reset({
1642
- host: tunnelUrl.hostname,
1643
- port: Number(tunnelUrl.port) || 80,
1644
- path: null
1645
- });
1646
- }
1647
- if (requestParser) {
1648
- requestParser.execute(toBuffer(chunk));
1649
- return;
1650
- }
1651
- const httpMessage = chunk.toString();
1652
- const httpMethod = httpMessage.split(" ")[0] || "";
1653
- if (!METHODS.includes(httpMethod.toUpperCase())) {
1654
- isHttpConnection = false;
1655
- socketController.decline();
1656
- return;
1657
- }
1658
- isHttpConnection = true;
1659
- const baseUrl = tunnelUrl ?? connectionOptionsToUrl(connectionOptions, socket);
1660
- httpLogger.verbose("handling http message %o", {
1661
- httpMessage,
1662
- httpMethod,
1663
- baseUrl
1664
- });
1665
- const requestContextValue = requestContext.getStore() ?? connectionRequestContext;
1666
- const initiator = requestContextValue?.initiator || socket;
1667
- requestParser = new HttpRequestParser({
1668
- connectionOptions: {
1669
- method: httpMethod,
1670
- url: baseUrl
1671
- },
1701
+ const onRequestData = (chunk) => {
1702
+ if (isHttpConnection === false) {
1703
+ socketController.decline();
1704
+ return;
1705
+ }
1672
1706
  /**
1673
- * @note The message boundary ends the current exchange.
1674
- * Schedule the controller reset so the next write on this
1675
- * (kept-alive) socket opens a new exchange and buffers for
1676
- * its own verdict instead of following the settled one
1677
- * (e.g. leaking a mocked request to the server of a
1678
- * previously passed-through exchange).
1707
+ * @note A mocked "CONNECT" request has established a tunnel.
1708
+ * The data that follows belongs to a new exchange addressed to
1709
+ * the tunnel target. The previous parser was freed at the upgrade
1710
+ * boundary, so detect the tunneled protocol anew.
1679
1711
  */
1680
- onMessageComplete: () => {
1681
- socketController.scheduleReset();
1682
- },
1683
- onRequest: async (parsedRequest, requestAbortController) => {
1684
- const request = requestContextValue?.transformRequest?.(parsedRequest) ?? parsedRequest;
1712
+ if (tunnelUrl && !requestParser) {
1713
+ isHttpConnection = void 0;
1685
1714
  /**
1686
- * @note A subsequent request arriving on a kept-alive socket
1687
- * that has already been handled (passed through or mocked).
1688
- * Clients like Undici reuse sockets without emitting the
1689
- * "free" event, so reset the controller here, at the HTTP
1690
- * message boundary, to handle the new request from the
1691
- * pending state again.
1715
+ * @note Retarget the connection to the tunnel authority.
1716
+ * The exchanges that follow belong to the tunnel target,
1717
+ * so an unclaimed exchange (HTTP or not) must pass through
1718
+ * to that target not to the proxy, which never actually
1719
+ * established this tunnel like a real established tunnel
1720
+ * relays its traffic.
1692
1721
  */
1693
- if (socketController["readyState"] !== SocketController.PENDING) socketController.reset();
1694
- const requestId = createRequestId();
1695
- const requestLogger = requestContextValue?.logger ?? httpLogger;
1696
- httpLogger.verbose("received a parsed HTTP request %o", {
1697
- method: request.method,
1698
- url: request.url
1722
+ socketController.reset({
1723
+ host: tunnelUrl.hostname,
1724
+ port: Number(tunnelUrl.port) || 80,
1725
+ path: null
1699
1726
  });
1700
- const requestController = new RequestController(request, {
1701
- respondWith: async (rawResponse) => {
1702
- httpLogger.verbose("respondWith() %o", {
1703
- status: rawResponse.status,
1704
- statusText: rawResponse.statusText,
1705
- hasBody: rawResponse.body != null
1706
- });
1707
- /**
1708
- * @note The client may destroy the socket (e.g. on request
1709
- * abort) moments before a response arrives. A destroyed
1710
- * socket cannot be claimed and has no one reading it.
1711
- */
1712
- if (socket.destroyed) return;
1713
- socketController.claim();
1714
- const response = FetchResponse.from(rawResponse, { url: request.url });
1715
- /**
1716
- * @note A successful mocked response to a "CONNECT"
1717
- * request establishes a tunnel to the requested authority
1718
- * (e.g. "127.0.0.1:80"). The exchange that follows on this
1719
- * socket is addressed to that authority, not to the proxy.
1720
- */
1721
- if (request.method === "CONNECT" && response.ok) tunnelUrl = new URL(`http://${request.url}`);
1722
- /**
1723
- * @note Clone the response before "respondWith" because it will
1724
- * consume its body. This way, we can have a readable response copy
1725
- * for the "response" event below.
1726
- */
1727
- const responseClone = isResponseError(response) ? null : response.clone();
1728
- const respond = () => {
1729
- return this.respondWith({
1730
- socket: socketController[kRawSocket],
1731
- request: context.request,
1732
- response
1727
+ }
1728
+ if (requestParser) {
1729
+ executeRequestParser(requestParser, toBuffer(chunk));
1730
+ return;
1731
+ }
1732
+ const httpMessage = chunk.toString();
1733
+ const httpMethod = httpMessage.split(" ")[0] || "";
1734
+ if (!METHODS.includes(httpMethod.toUpperCase())) {
1735
+ isHttpConnection = false;
1736
+ socketController.decline();
1737
+ return;
1738
+ }
1739
+ isHttpConnection = true;
1740
+ const baseUrl = tunnelUrl ?? connectionOptionsToUrl(connectionOptions, socket);
1741
+ httpLogger.verbose("handling http message %o", {
1742
+ httpMessage,
1743
+ httpMethod,
1744
+ baseUrl
1745
+ });
1746
+ const requestContextValue = requestContext.getStore() ?? connectionRequestContext;
1747
+ const initiator = requestContextValue?.initiator || socket;
1748
+ requestParser = new HttpRequestParser({
1749
+ onError: stopParsingRequests,
1750
+ connectionOptions: {
1751
+ method: httpMethod,
1752
+ url: baseUrl
1753
+ },
1754
+ /**
1755
+ * @note The message boundary ends the current exchange.
1756
+ * Schedule the controller reset so the next write on this
1757
+ * (kept-alive) socket opens a new exchange and buffers for
1758
+ * its own verdict instead of following the settled one
1759
+ * (e.g. leaking a mocked request to the server of a
1760
+ * previously passed-through exchange).
1761
+ */
1762
+ onMessageComplete: () => {
1763
+ socketController.scheduleReset();
1764
+ },
1765
+ onRequest: async (parsedRequest, requestAbortController) => {
1766
+ const request = requestContextValue?.transformRequest?.(parsedRequest) ?? parsedRequest;
1767
+ /**
1768
+ * @note A subsequent request arriving on a kept-alive socket
1769
+ * that has already been handled (passed through or mocked).
1770
+ * Clients like Undici reuse sockets without emitting the
1771
+ * "free" event, so reset the controller here, at the HTTP
1772
+ * message boundary, to handle the new request from the
1773
+ * pending state again.
1774
+ */
1775
+ if (socketController["readyState"] !== SocketController.PENDING) socketController.reset();
1776
+ const requestId = createRequestId();
1777
+ const requestLogger = requestContextValue?.logger ?? httpLogger;
1778
+ httpLogger.verbose("received a parsed HTTP request %o", {
1779
+ method: request.method,
1780
+ url: request.url
1781
+ });
1782
+ const requestController = new RequestController(request, {
1783
+ respondWith: async (rawResponse) => {
1784
+ httpLogger.verbose("respondWith() %o", {
1785
+ status: rawResponse.status,
1786
+ statusText: rawResponse.statusText,
1787
+ hasBody: rawResponse.body != null
1733
1788
  });
1734
- };
1735
- if (responseClone) await this.emitter.emitAsPromise(new HttpResponseEvent({
1736
- initiator,
1737
- requestId,
1738
- request: context.request,
1739
- response: responseClone,
1740
- responseType: "mock"
1741
- }));
1742
- if (socket.connecting) socket.once("connect", respond);
1743
- else
1744
- /**
1745
- * @note Reused sockets stay connected between requests and will not
1746
- * emit "connect" anymore. If that's the case, respond immediately.
1747
- */
1748
- await respond();
1749
- },
1750
- errorWith: (reason) => {
1751
- if (reason instanceof Error) socket.destroy(reason);
1752
- },
1753
- passthrough: () => {
1754
- const realSocket = socketController.passthrough(this.#modifyHttpHeaders(context.request));
1755
- if (this.emitter.listenerCount("response") > 0) {
1756
- httpLogger.verbose("found \"response\" listener, corking socket reads");
1757
1789
  /**
1758
- * Suspend the delivery of the original response to the client
1759
- * until the "response" event listeners settle. This guarantees
1760
- * that the request promise (e.g. `await fetch()`) does not
1761
- * resolve before the listeners are done. The real socket keeps
1762
- * emitting data for the response parser meanwhile.
1790
+ * @note The client may destroy the socket (e.g. on request
1791
+ * abort) moments before a response arrives. A destroyed
1792
+ * socket cannot be claimed and has no one reading it.
1793
+ */
1794
+ if (socket.destroyed) return;
1795
+ socketController.claim();
1796
+ const originalResponse = FetchResponse.from(rawResponse, { url: request.url });
1797
+ const [response, responseClone] = !isResponseError(originalResponse) && this.emitter.listenerCount("response") > 0 ? cloneResponse(originalResponse) : [originalResponse, null];
1798
+ /**
1799
+ * @note A successful mocked response to a "CONNECT"
1800
+ * request establishes a tunnel to the requested authority
1801
+ * (e.g. "127.0.0.1:80"). The exchange that follows on this
1802
+ * socket is addressed to that authority, not to the proxy.
1763
1803
  */
1764
- socketController.corkReads();
1765
- const responseParser = new HttpResponseParser({ onResponse: async (response) => {
1766
- httpLogger.verbose("HTTP response parser parsed: %d %s", response.status, response.statusText);
1767
- if (isResponseError(response)) {
1768
- httpLogger.verbose("response is an error response, uncorking socket reads...");
1769
- socketController.uncorkReads();
1770
- return;
1771
- }
1772
- FetchResponse.setUrl(request.url, response);
1773
- try {
1774
- httpLogger.verbose("emitting \"response\" event");
1775
- await this.emitter.emitAsPromise(new HttpResponseEvent({
1776
- initiator,
1777
- requestId,
1778
- request: context.request,
1779
- response,
1780
- responseType: "original"
1781
- }));
1782
- } finally {
1783
- httpLogger.verbose("uncorking socket reads");
1784
- socketController.uncorkReads();
1785
- /**
1786
- * @note Informational responses other than
1787
- * "101 Switching Protocols" are followed by a final
1788
- * response on the same connection. Keep gating that
1789
- * final response on the "response" event listeners.
1790
- */
1791
- if (response.status < 200 && response.status !== 101) socketController.corkReads();
1792
- }
1793
- } });
1794
- realSocket.on("data", (chunk) => responseParser.execute(chunk)).on("close", () => responseParser.free());
1804
+ if (request.method === "CONNECT" && response.ok) {
1805
+ tunnelUrl = new URL(`http://${request.url}`);
1806
+ addRequestDataListener();
1807
+ }
1808
+ const respond = () => {
1809
+ return this.respondWith({
1810
+ socket: socketController[kRawSocket],
1811
+ request: context.request,
1812
+ response
1813
+ });
1814
+ };
1815
+ if (responseClone) await this.emitter.emitAsPromise(new HttpResponseEvent({
1816
+ initiator,
1817
+ requestId,
1818
+ request: context.request,
1819
+ response: responseClone,
1820
+ responseType: "mock"
1821
+ }));
1822
+ if (socket.connecting) socket.once("connect", respond);
1823
+ else
1824
+ /**
1825
+ * @note Reused sockets stay connected between requests and will not
1826
+ * emit "connect" anymore. If that's the case, respond immediately.
1827
+ */
1828
+ await respond();
1829
+ },
1830
+ errorWith: (reason) => {
1831
+ if (reason instanceof Error) socket.destroy(reason);
1832
+ },
1833
+ passthrough: () => {
1834
+ const realSocket = socketController.passthrough(isHttpConnection === false ? void 0 : this.#modifyHttpHeaders(context.request));
1835
+ if (isHttpConnection === false) return;
1836
+ if (this.emitter.listenerCount("response") > 0) {
1837
+ httpLogger.verbose("found \"response\" listener, corking socket reads");
1838
+ /**
1839
+ * Suspend the delivery of the original response to the client
1840
+ * until the "response" event listeners settle. This guarantees
1841
+ * that the request promise (e.g. `await fetch()`) does not
1842
+ * resolve before the listeners are done. The real socket keeps
1843
+ * emitting data for the response parser meanwhile.
1844
+ */
1845
+ socketController.corkReads();
1846
+ let responseParserDisposed = false;
1847
+ let responseComplete = false;
1848
+ let hasFinalResponse = false;
1849
+ const responseParser = new HttpResponseParser({
1850
+ onError: (error) => {
1851
+ disposeResponseParser(error);
1852
+ socketController.uncorkReads();
1853
+ },
1854
+ onMessageComplete: (status) => {
1855
+ responseComplete = status >= 200 || status === 101;
1856
+ },
1857
+ onResponse: async (response) => {
1858
+ hasFinalResponse = response.status >= 200 || response.status === 101;
1859
+ httpLogger.verbose("HTTP response parser parsed: %d %s", response.status, response.statusText);
1860
+ if (isResponseError(response)) {
1861
+ httpLogger.verbose("response is an error response, uncorking socket reads...");
1862
+ socketController.uncorkReads();
1863
+ return;
1864
+ }
1865
+ FetchResponse.setUrl(request.url, response);
1866
+ try {
1867
+ httpLogger.verbose("emitting \"response\" event");
1868
+ await this.emitter.emitAsPromise(new HttpResponseEvent({
1869
+ initiator,
1870
+ requestId,
1871
+ request: context.request,
1872
+ response,
1873
+ responseType: "original"
1874
+ }));
1875
+ } finally {
1876
+ httpLogger.verbose("uncorking socket reads");
1877
+ socketController.uncorkReads();
1878
+ /**
1879
+ * @note Informational responses other than
1880
+ * "101 Switching Protocols" are followed by a final
1881
+ * response on the same connection. Keep gating that
1882
+ * final response on the "response" event listeners.
1883
+ */
1884
+ if (!responseParserDisposed && response.status < 200 && response.status !== 101) socketController.corkReads();
1885
+ }
1886
+ }
1887
+ });
1888
+ const onResponseData = (chunk) => {
1889
+ responseParser.execute(chunk);
1890
+ if (responseComplete) disposeResponseParser();
1891
+ };
1892
+ const onResponseEnd = () => {
1893
+ disposeResponseParser();
1894
+ if (!hasFinalResponse) socketController.uncorkReads();
1895
+ };
1896
+ const disposeResponseParser = (error) => {
1897
+ responseParserDisposed = true;
1898
+ realSocket.removeListener("data", onResponseData);
1899
+ realSocket.removeListener("end", onResponseEnd);
1900
+ realSocket.removeListener("close", onResponseEnd);
1901
+ responseParser.free(error);
1902
+ };
1903
+ realSocket.on("data", onResponseData).once("end", onResponseEnd).once("close", onResponseEnd);
1904
+ }
1795
1905
  }
1906
+ }, {
1907
+ logger: requestLogger,
1908
+ requestId
1909
+ });
1910
+ invariant(socketController["readyState"] === SocketController.PENDING, "CANNOT HANDLE ALREADY HANDLED REQUEST", request.method, request.url, socketController["readyState"]);
1911
+ /**
1912
+ * @note Create a request resolution context.
1913
+ * This is so modifications to the "request" in upstream interceptors
1914
+ * are correctly picked up by the underlying HTTP interceptor.
1915
+ */
1916
+ const context = {
1917
+ initiator,
1918
+ requestId,
1919
+ request,
1920
+ controller: requestController,
1921
+ emitter: this.emitter,
1922
+ logger: requestLogger
1923
+ };
1924
+ /**
1925
+ * @note The client destroying the socket while the request
1926
+ * is still pending means the request was aborted (e.g. via
1927
+ * `AbortController`). Abort the parsed request so its
1928
+ * handling settles and late interactions with the request
1929
+ * controller become controlled errors.
1930
+ */
1931
+ abortPendingRequest = () => {
1932
+ if (requestController.readyState === RequestController.PENDING) requestAbortController.abort();
1933
+ };
1934
+ pendingRequestController = requestController;
1935
+ try {
1936
+ await handleRequest(context);
1937
+ } finally {
1938
+ pendingRequestController = void 0;
1939
+ abortPendingRequest = void 0;
1796
1940
  }
1797
- }, {
1798
- logger: requestLogger,
1799
- requestId
1800
- });
1801
- invariant(socketController["readyState"] === SocketController.PENDING, "CANNOT HANDLE ALREADY HANDLED REQUEST", request.method, request.url, socketController["readyState"]);
1802
- /**
1803
- * @note Create a request resolution context.
1804
- * This is so modifications to the "request" in upstream interceptors
1805
- * are correctly picked up by the underlying HTTP interceptor.
1806
- */
1807
- const context = {
1808
- initiator,
1809
- requestId,
1810
- request,
1811
- controller: requestController,
1812
- emitter: this.emitter,
1813
- logger: requestLogger
1814
- };
1815
- /**
1816
- * @note The client destroying the socket while the request
1817
- * is still pending means the request was aborted (e.g. via
1818
- * `AbortController`). Abort the parsed request so its
1819
- * handling settles and late interactions with the request
1820
- * controller become controlled errors.
1821
- */
1822
- abortPendingRequest = () => {
1823
- if (requestController.readyState === RequestController.PENDING) requestAbortController.abort();
1824
- };
1825
- try {
1826
- await handleRequest(context);
1827
- } finally {
1828
- abortPendingRequest = void 0;
1829
1941
  }
1830
- }
1831
- });
1832
- requestParser.execute(toBuffer(chunk));
1833
- });
1942
+ });
1943
+ executeRequestParser(requestParser, toBuffer(chunk));
1944
+ };
1945
+ socket.on("data", onRequestData);
1946
+ };
1947
+ addRequestDataListener();
1834
1948
  socket.on("close", () => requestParser?.free());
1835
1949
  }, { signal: controller.signal });
1836
1950
  }
@@ -2019,4 +2133,4 @@ var NodeHttpRequestSource = class extends Interceptor {
2019
2133
  //#endregion
2020
2134
  export { requestContext as a, forwardHttpEvents as i, handleRequest as n, runInRequestContext as o, HttpResponseEvent as r, NodeHttpRequestSource as t };
2021
2135
 
2022
- //# sourceMappingURL=source-CIgPng7r.js.map
2136
+ //# sourceMappingURL=source-DHVO1vzq.js.map