@mswjs/interceptors 0.42.5 → 0.42.6

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.
@@ -2,7 +2,7 @@ import { n as createLogger, r as formatRequest, t as Interceptor } from "./inter
2
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
3
  import { t as createRequestId } from "./create-request-id-DHo-fRTV.js";
4
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";
5
+ import { i as unwrapPendingData, n as SocketController, r as kRawSocket, t as SocketInterceptor } from "./net-B3HDxRlp.js";
6
6
  import { TypedEvent } from "rettime";
7
7
  import { invariant } from "outvariant";
8
8
  import { IncomingMessage, METHODS, STATUS_CODES, ServerResponse } from "node:http";
@@ -1656,11 +1656,16 @@ var NodeHttpRequestSource = class extends Interceptor {
1656
1656
  let tunnelUrl;
1657
1657
  let abortPendingRequest;
1658
1658
  let pendingRequestController;
1659
+ const passthroughNonHttp = () => {
1660
+ setImmediate(() => {
1661
+ if (socketController.readyState === SocketController.PENDING && !socket.destroyed) socketController.passthrough();
1662
+ });
1663
+ };
1659
1664
  const stopParsingRequests = (error) => {
1660
1665
  httpLogger.verbose("stopping HTTP request parsing: %o", error);
1661
1666
  isHttpConnection = false;
1662
1667
  if (pendingRequestController?.readyState === RequestController.PENDING) pendingRequestController.passthrough();
1663
- else socketController.decline();
1668
+ else passthroughNonHttp();
1664
1669
  requestParser?.free(error);
1665
1670
  };
1666
1671
  /**
@@ -1686,265 +1691,262 @@ var NodeHttpRequestSource = class extends Interceptor {
1686
1691
  abortPendingRequest?.();
1687
1692
  realSocketDestroy(error, callback);
1688
1693
  };
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
- };
1694
+ const executeRequestParser = (parser, chunk) => {
1695
+ if (parser.execute(chunk) !== null) {
1696
+ socket.removeListener("data", onRequestData);
1697
+ parser.free();
1698
+ requestParser = void 0;
1699
+ }
1700
+ };
1701
+ /**
1702
+ * @note Inspect the first sent packet to determine the protocol,
1703
+ * including when entering a mocked "CONNECT" tunnel.
1704
+ */
1705
+ const onRequestData = (chunk) => {
1706
+ if (isHttpConnection === false) {
1707
+ passthroughNonHttp();
1708
+ return;
1709
+ }
1697
1710
  /**
1698
- * @note Inspect the first sent packet to determine the protocol,
1699
- * including when entering a mocked "CONNECT" tunnel.
1711
+ * @note A mocked "CONNECT" request has established a tunnel.
1712
+ * The data that follows belongs to a new exchange addressed to
1713
+ * the tunnel target. The previous parser was freed at the upgrade
1714
+ * boundary, so detect the tunneled protocol anew.
1700
1715
  */
1701
- const onRequestData = (chunk) => {
1702
- if (isHttpConnection === false) {
1703
- socketController.decline();
1704
- return;
1705
- }
1716
+ if (tunnelUrl && !requestParser) {
1717
+ isHttpConnection = void 0;
1706
1718
  /**
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.
1719
+ * @note Retarget the connection to the tunnel authority.
1720
+ * The exchanges that follow belong to the tunnel target,
1721
+ * so an unclaimed exchange (HTTP or not) must pass through
1722
+ * to that target — not to the proxy, which never actually
1723
+ * established this tunnel — like a real established tunnel
1724
+ * relays its traffic.
1711
1725
  */
1712
- if (tunnelUrl && !requestParser) {
1713
- isHttpConnection = void 0;
1714
- /**
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.
1721
- */
1722
- socketController.reset({
1723
- host: tunnelUrl.hostname,
1724
- port: Number(tunnelUrl.port) || 80,
1725
- path: null
1726
- });
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
1726
+ socketController.reset({
1727
+ host: tunnelUrl.hostname,
1728
+ port: Number(tunnelUrl.port) || 80,
1729
+ path: null
1745
1730
  });
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
- },
1731
+ }
1732
+ if (requestParser) {
1733
+ executeRequestParser(requestParser, toBuffer(chunk));
1734
+ return;
1735
+ }
1736
+ const httpMessage = chunk.toString();
1737
+ const httpMethod = httpMessage.split(" ")[0] || "";
1738
+ if (!METHODS.includes(httpMethod.toUpperCase())) {
1739
+ isHttpConnection = false;
1740
+ passthroughNonHttp();
1741
+ return;
1742
+ }
1743
+ isHttpConnection = true;
1744
+ const baseUrl = tunnelUrl ?? connectionOptionsToUrl(connectionOptions, socket);
1745
+ httpLogger.verbose("handling http message %o", {
1746
+ httpMessage,
1747
+ httpMethod,
1748
+ baseUrl
1749
+ });
1750
+ const requestContextValue = requestContext.getStore() ?? connectionRequestContext;
1751
+ const initiator = requestContextValue?.initiator || socket;
1752
+ requestParser = new HttpRequestParser({
1753
+ onError: stopParsingRequests,
1754
+ connectionOptions: {
1755
+ method: httpMethod,
1756
+ url: baseUrl
1757
+ },
1758
+ /**
1759
+ * @note The message boundary ends the current exchange.
1760
+ * Schedule the controller reset so the next write on this
1761
+ * (kept-alive) socket opens a new exchange and buffers for
1762
+ * its own verdict instead of following the settled one
1763
+ * (e.g. leaking a mocked request to the server of a
1764
+ * previously passed-through exchange).
1765
+ */
1766
+ onMessageComplete: () => {
1767
+ socketController.scheduleReset();
1768
+ },
1769
+ onRequest: async (parsedRequest, requestAbortController) => {
1770
+ const request = requestContextValue?.transformRequest?.(parsedRequest) ?? parsedRequest;
1754
1771
  /**
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).
1772
+ * @note A subsequent request arriving on a kept-alive socket
1773
+ * that has already been handled (passed through or mocked).
1774
+ * Clients like Undici reuse sockets without emitting the
1775
+ * "free" event, so reset the controller here, at the HTTP
1776
+ * message boundary, to handle the new request from the
1777
+ * pending state again.
1761
1778
  */
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
1788
- });
1789
- /**
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.
1803
- */
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,
1779
+ if (socketController["readyState"] !== SocketController.PENDING) socketController.reset();
1780
+ const requestId = createRequestId();
1781
+ const requestLogger = requestContextValue?.logger ?? httpLogger;
1782
+ httpLogger.verbose("received a parsed HTTP request %o", {
1783
+ method: request.method,
1784
+ url: request.url
1785
+ });
1786
+ const requestController = new RequestController(request, {
1787
+ respondWith: async (rawResponse) => {
1788
+ httpLogger.verbose("respondWith() %o", {
1789
+ status: rawResponse.status,
1790
+ statusText: rawResponse.statusText,
1791
+ hasBody: rawResponse.body != null
1792
+ });
1793
+ /**
1794
+ * @note The client may destroy the socket (e.g. on request
1795
+ * abort) moments before a response arrives. A destroyed
1796
+ * socket cannot be claimed and has no one reading it.
1797
+ */
1798
+ if (socket.destroyed) return;
1799
+ socketController.claim();
1800
+ const originalResponse = FetchResponse.from(rawResponse, { url: request.url });
1801
+ const [response, responseClone] = !isResponseError(originalResponse) && this.emitter.listenerCount("response") > 0 ? cloneResponse(originalResponse) : [originalResponse, null];
1802
+ /**
1803
+ * @note A successful mocked response to a "CONNECT"
1804
+ * request establishes a tunnel to the requested authority
1805
+ * (e.g. "127.0.0.1:80"). The exchange that follows on this
1806
+ * socket is addressed to that authority, not to the proxy.
1807
+ */
1808
+ if (request.method === "CONNECT" && response.ok) {
1809
+ tunnelUrl = new URL(`http://${request.url}`);
1810
+ socket.on("data", onRequestData);
1811
+ }
1812
+ const respond = () => {
1813
+ return this.respondWith({
1814
+ socket: socketController[kRawSocket],
1818
1815
  request: context.request,
1819
- response: responseClone,
1820
- responseType: "mock"
1821
- }));
1822
- if (socket.connecting) socket.once("connect", respond);
1823
- else
1816
+ response
1817
+ });
1818
+ };
1819
+ if (responseClone) await this.emitter.emitAsPromise(new HttpResponseEvent({
1820
+ initiator,
1821
+ requestId,
1822
+ request: context.request,
1823
+ response: responseClone,
1824
+ responseType: "mock"
1825
+ }));
1826
+ if (socket.connecting) socket.once("connect", respond);
1827
+ else
1824
1828
  /**
1825
- * @note Reused sockets stay connected between requests and will not
1826
- * emit "connect" anymore. If that's the case, respond immediately.
1829
+ * @note Reused sockets stay connected between requests and will not
1830
+ * emit "connect" anymore. If that's the case, respond immediately.
1831
+ */
1832
+ await respond();
1833
+ },
1834
+ errorWith: (reason) => {
1835
+ if (reason instanceof Error) socket.destroy(reason);
1836
+ },
1837
+ passthrough: () => {
1838
+ const realSocket = socketController.passthrough(isHttpConnection === false ? void 0 : this.#modifyHttpHeaders(context.request));
1839
+ if (isHttpConnection === false) return;
1840
+ if (this.emitter.listenerCount("response") > 0) {
1841
+ httpLogger.verbose("found \"response\" listener, corking socket reads");
1842
+ /**
1843
+ * Suspend the delivery of the original response to the client
1844
+ * until the "response" event listeners settle. This guarantees
1845
+ * that the request promise (e.g. `await fetch()`) does not
1846
+ * resolve before the listeners are done. The real socket keeps
1847
+ * emitting data for the response parser meanwhile.
1827
1848
  */
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);
1849
+ socketController.corkReads();
1850
+ let responseParserDisposed = false;
1851
+ let responseComplete = false;
1852
+ let hasFinalResponse = false;
1853
+ const responseParser = new HttpResponseParser({
1854
+ onError: (error) => {
1855
+ disposeResponseParser(error);
1856
+ socketController.uncorkReads();
1857
+ },
1858
+ onMessageComplete: (status) => {
1859
+ responseComplete = status >= 200 || status === 101;
1860
+ },
1861
+ onResponse: async (response) => {
1862
+ hasFinalResponse = response.status >= 200 || response.status === 101;
1863
+ httpLogger.verbose("HTTP response parser parsed: %d %s", response.status, response.statusText);
1864
+ if (isResponseError(response)) {
1865
+ httpLogger.verbose("response is an error response, uncorking socket reads...");
1852
1866
  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
- }
1867
+ return;
1886
1868
  }
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
- }
1869
+ FetchResponse.setUrl(request.url, response);
1870
+ try {
1871
+ httpLogger.verbose("emitting \"response\" event");
1872
+ await this.emitter.emitAsPromise(new HttpResponseEvent({
1873
+ initiator,
1874
+ requestId,
1875
+ request: context.request,
1876
+ response,
1877
+ responseType: "original"
1878
+ }));
1879
+ } finally {
1880
+ httpLogger.verbose("uncorking socket reads");
1881
+ socketController.uncorkReads();
1882
+ /**
1883
+ * @note Informational responses other than
1884
+ * "101 Switching Protocols" are followed by a final
1885
+ * response on the same connection. Keep gating that
1886
+ * final response on the "response" event listeners.
1887
+ */
1888
+ if (!responseParserDisposed && response.status < 200 && response.status !== 101) socketController.corkReads();
1889
+ }
1890
+ }
1891
+ });
1892
+ const onResponseData = (chunk) => {
1893
+ responseParser.execute(chunk);
1894
+ if (responseComplete) disposeResponseParser();
1895
+ };
1896
+ const onResponseEnd = () => {
1897
+ disposeResponseParser();
1898
+ if (!hasFinalResponse) socketController.uncorkReads();
1899
+ };
1900
+ const disposeResponseParser = (error) => {
1901
+ responseParserDisposed = true;
1902
+ realSocket.removeListener("data", onResponseData);
1903
+ realSocket.removeListener("end", onResponseEnd);
1904
+ realSocket.removeListener("close", onResponseEnd);
1905
+ responseParser.free(error);
1906
+ };
1907
+ realSocket.on("data", onResponseData).once("end", onResponseEnd).once("close", onResponseEnd);
1905
1908
  }
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;
1940
1909
  }
1910
+ }, {
1911
+ logger: requestLogger,
1912
+ requestId
1913
+ });
1914
+ invariant(socketController["readyState"] === SocketController.PENDING, "CANNOT HANDLE ALREADY HANDLED REQUEST", request.method, request.url, socketController["readyState"]);
1915
+ /**
1916
+ * @note Create a request resolution context.
1917
+ * This is so modifications to the "request" in upstream interceptors
1918
+ * are correctly picked up by the underlying HTTP interceptor.
1919
+ */
1920
+ const context = {
1921
+ initiator,
1922
+ requestId,
1923
+ request,
1924
+ controller: requestController,
1925
+ emitter: this.emitter,
1926
+ logger: requestLogger
1927
+ };
1928
+ /**
1929
+ * @note The client destroying the socket while the request
1930
+ * is still pending means the request was aborted (e.g. via
1931
+ * `AbortController`). Abort the parsed request so its
1932
+ * handling settles and late interactions with the request
1933
+ * controller become controlled errors.
1934
+ */
1935
+ abortPendingRequest = () => {
1936
+ if (requestController.readyState === RequestController.PENDING) requestAbortController.abort();
1937
+ };
1938
+ pendingRequestController = requestController;
1939
+ try {
1940
+ await handleRequest(context);
1941
+ } finally {
1942
+ pendingRequestController = void 0;
1943
+ abortPendingRequest = void 0;
1941
1944
  }
1942
- });
1943
- executeRequestParser(requestParser, toBuffer(chunk));
1944
- };
1945
- socket.on("data", onRequestData);
1945
+ }
1946
+ });
1947
+ executeRequestParser(requestParser, toBuffer(chunk));
1946
1948
  };
1947
- addRequestDataListener();
1949
+ socket.on("data", onRequestData);
1948
1950
  socket.on("close", () => requestParser?.free());
1949
1951
  }, { signal: controller.signal });
1950
1952
  }
@@ -2133,4 +2135,4 @@ var NodeHttpRequestSource = class extends Interceptor {
2133
2135
  //#endregion
2134
2136
  export { requestContext as a, forwardHttpEvents as i, handleRequest as n, runInRequestContext as o, HttpResponseEvent as r, NodeHttpRequestSource as t };
2135
2137
 
2136
- //# sourceMappingURL=source-DHVO1vzq.js.map
2138
+ //# sourceMappingURL=source-C_hSJzNQ.js.map