@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.
- package/lib/browser/interceptors/WebSocket/index.js +14 -1
- package/lib/browser/interceptors/WebSocket/index.js.map +1 -1
- package/lib/browser/interceptors/fetch/web.js +1 -1
- package/lib/browser/presets/browser.js +1 -1
- package/lib/browser/{web-CdcYFjgm.js → web-DUlFZEtz.js} +58 -3
- package/lib/browser/web-DUlFZEtz.js.map +1 -0
- package/lib/node/{batch-interceptor-ByEZVih3.js → batch-interceptor-DMOfd-9x.js} +2 -2
- package/lib/node/{batch-interceptor-ByEZVih3.js.map → batch-interceptor-DMOfd-9x.js.map} +1 -1
- package/lib/node/buffer-utils-B4FUq-l2.js +17 -0
- package/lib/node/buffer-utils-B4FUq-l2.js.map +1 -0
- package/lib/node/create-request-id-DHo-fRTV.js +14 -0
- package/lib/node/create-request-id-DHo-fRTV.js.map +1 -0
- package/lib/node/{fetch-utils-Dw1PsmtX.js → fetch-utils-D-_xeRlK.js} +3 -14
- package/lib/node/{fetch-utils-Dw1PsmtX.js.map → fetch-utils-D-_xeRlK.js.map} +1 -1
- package/lib/node/{has-configurable-global-BU1sT1u4.js → has-configurable-global-CT6-QYdg.js} +2 -2
- package/lib/node/{has-configurable-global-BU1sT1u4.js.map → has-configurable-global-CT6-QYdg.js.map} +1 -1
- package/lib/node/index.d.ts +2 -243
- package/lib/node/index.js +6 -25
- package/lib/node/index.js.map +1 -1
- package/lib/node/{buffer-utils-BvPY1Tc-.js → interceptor-C8qRPjxG.js} +2 -16
- package/lib/node/interceptor-C8qRPjxG.js.map +1 -0
- package/lib/node/interceptors/ClientRequest/index.js +3 -3
- package/lib/node/interceptors/WebSocket/index.d.ts +2 -0
- package/lib/node/interceptors/WebSocket/index.js +635 -0
- package/lib/node/interceptors/WebSocket/index.js.map +1 -0
- package/lib/node/interceptors/XMLHttpRequest/node.js +5 -5
- package/lib/node/interceptors/fetch/node.js +5 -5
- package/lib/node/interceptors/http/index.js +2 -2
- package/lib/node/interceptors/net/index.d.ts +16 -0
- package/lib/node/interceptors/net/index.js +1 -1
- package/lib/node/{net-Ca8p1rIe.js → net-Bh16MP4u.js} +82 -92
- package/lib/node/net-Bh16MP4u.js.map +1 -0
- package/lib/node/patches-registry-DxR5TEc-.js +76 -0
- package/lib/node/patches-registry-DxR5TEc-.js.map +1 -0
- package/lib/node/remote-http-interceptor.js +4 -4
- package/lib/node/resolve-web-socket-url-CSvNPLGi.js +25 -0
- package/lib/node/resolve-web-socket-url-CSvNPLGi.js.map +1 -0
- package/lib/node/{source-CIgPng7r.js → source-DHVO1vzq.js} +327 -213
- package/lib/node/source-DHVO1vzq.js.map +1 -0
- package/lib/node/websocket-CC0nB0md.d.ts +285 -0
- package/package.json +5 -2
- package/src/interceptors/WebSocket/index.ts +4 -1
- package/src/interceptors/fetch/web.ts +11 -2
- package/src/interceptors/http/http-parser/index.ts +9 -1
- package/src/interceptors/http/http-parser.ts +21 -4
- package/src/interceptors/http/source.ts +368 -283
- package/src/interceptors/net/index.ts +39 -12
- package/src/interceptors/net/socket-controller.ts +126 -66
- package/src/utils/clone-response.ts +72 -0
- package/src/utils/internal-connection.ts +22 -0
- package/lib/browser/web-CdcYFjgm.js.map +0 -1
- package/lib/node/buffer-utils-BvPY1Tc-.js.map +0 -1
- package/lib/node/net-Ca8p1rIe.js.map +0 -1
- package/lib/node/source-CIgPng7r.js.map +0 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as isResponseError, c as isObject, d as
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
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
|
|
1624
|
-
*
|
|
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
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
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
|
|
1674
|
-
*
|
|
1675
|
-
*
|
|
1676
|
-
*
|
|
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
|
-
|
|
1681
|
-
|
|
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
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1689
|
-
*
|
|
1690
|
-
*
|
|
1691
|
-
*
|
|
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
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
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
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
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
|
-
*
|
|
1759
|
-
*
|
|
1760
|
-
*
|
|
1761
|
-
|
|
1762
|
-
|
|
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
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
realSocket.
|
|
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
|
-
|
|
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-
|
|
2136
|
+
//# sourceMappingURL=source-DHVO1vzq.js.map
|