@mswjs/interceptors 0.42.3 → 0.42.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/interceptors/WebSocket/index.js +14 -1
- package/lib/browser/interceptors/WebSocket/index.js.map +1 -1
- package/lib/node/{batch-interceptor-ByEZVih3.js → batch-interceptor-DMOfd-9x.js} +2 -2
- package/lib/node/{batch-interceptor-ByEZVih3.js.map → batch-interceptor-DMOfd-9x.js.map} +1 -1
- package/lib/node/buffer-utils-B4FUq-l2.js +17 -0
- package/lib/node/buffer-utils-B4FUq-l2.js.map +1 -0
- package/lib/node/create-request-id-DHo-fRTV.js +14 -0
- package/lib/node/create-request-id-DHo-fRTV.js.map +1 -0
- package/lib/node/{fetch-utils-Dw1PsmtX.js → fetch-utils-Tm5LbwBe.js} +3 -14
- package/lib/node/{fetch-utils-Dw1PsmtX.js.map → fetch-utils-Tm5LbwBe.js.map} +1 -1
- package/lib/node/{has-configurable-global-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 +9 -0
- package/lib/node/interceptors/net/index.js +1 -1
- package/lib/node/{net-Ca8p1rIe.js → net-DkiHxhQF.js} +43 -79
- package/lib/node/net-DkiHxhQF.js.map +1 -0
- package/lib/node/patches-registry-DxR5TEc-.js +76 -0
- package/lib/node/patches-registry-DxR5TEc-.js.map +1 -0
- package/lib/node/remote-http-interceptor.js +4 -4
- package/lib/node/resolve-web-socket-url-CSvNPLGi.js +25 -0
- package/lib/node/resolve-web-socket-url-CSvNPLGi.js.map +1 -0
- package/lib/node/{source-CIgPng7r.js → source-BFZ6wg4P.js} +95 -38
- package/lib/node/source-BFZ6wg4P.js.map +1 -0
- package/lib/node/websocket-CC0nB0md.d.ts +285 -0
- package/package.json +5 -2
- package/src/interceptors/WebSocket/index.ts +4 -1
- package/src/interceptors/http/http-parser/index.ts +9 -1
- package/src/interceptors/http/http-parser.ts +21 -4
- package/src/interceptors/http/source.ts +71 -3
- package/src/interceptors/net/index.ts +39 -12
- package/src/interceptors/net/socket-controller.ts +34 -9
- package/src/utils/internal-connection.ts +22 -0
- 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"net-DkiHxhQF.js","names":["logger","#awaitedVerdicts","#connectionOptions","#passthroughSocket","#realWriteGeneric","#bufferedWrites","#handleWrite","#clientCloseEmitted","#readsCorked","#corkedReads","#reset","#retargetedConnectionOptions","#onRealSocketConnect","#onRealSocketConnectionAttemptFailed","#onRealSocketConnectionAttemptTimeout","#onRealSocketData","#onRealSocketError","#onRealSocketEnd","#onRealSocketClose","#realHandleSwapped","#resetScheduled","#connectEmulated","#push","#acknowledgeWrite","#removeBufferedWrite","#clientEndPushed","#emitClientClose","#createRetargetedConnection","#onMockSocketDrain","#forwardClientFinish","#tlsConnectionOptions","#stopReusingUnpatchedSockets"],"sources":["../../src/interceptors/net/utils/normalize-net-connect-args.ts","../../src/interceptors/net/utils/flush-writes.ts","../../src/interceptors/net/utils/get-tls-connect-options.ts","../../src/interceptors/net/utils/address-info.ts","../../src/interceptors/net/socket-controller.ts","../../src/interceptors/net/utils/normalize-tls-connect-args.ts","../../src/interceptors/net/index.ts"],"sourcesContent":["import net from 'node:net'\n\nexport interface NetworkConnectionOptions {\n /**\n * @note The port may be a string when a URL is (incorrectly) passed\n * to `net.connect()`. Node.js reads URLs as plain options objects.\n */\n port?: number | string | null\n path: string | null\n host?: string | null\n protocol?: string | null\n auth?: string | null\n family?: number | null\n hints?: number | null\n session?: Buffer\n localAddress?: string | null\n localPort?: number | null\n timeout?: number\n lookup?: net.LookupFunction\n allowHalfOpen?: boolean | null\n noDelay?: boolean | null\n keepAlive?: boolean | null\n keepAliveInitialDelay?: number | null\n autoSelectFamily?: boolean | null\n autoSelectFamilyAttemptTimeout?: number | null\n}\n\nexport type NetConnectArgs =\n | []\n | [options: net.NetConnectOpts, callback?: () => void]\n | [url: URL, callback?: () => void]\n | [port: number, host?: string, callback?: () => void]\n | [port: number, callback?: () => void]\n | [path: string, callback?: () => void]\n\nexport type NormalizedNetConnectArgs = [\n options: NetworkConnectionOptions,\n callback: (() => void) | null,\n]\n\n/**\n * Normalizes the arguments passed to `net.connect()`.\n */\nexport function normalizeNetConnectArgs(\n args: NetConnectArgs\n): NormalizedNetConnectArgs {\n if (args.length === 0) {\n return [{ path: '' }, null]\n }\n\n const callback = typeof args[1] === 'function' ? args[1] : args[2] || null\n\n if (typeof args[0] === 'string') {\n return [{ path: args[0] }, callback]\n }\n\n if (typeof args[0] === 'number') {\n return [\n {\n port: args[0],\n path: '',\n /**\n * @note The host is optional in the \"(port[, host][, callback])\"\n * signatures and defaults to \"localhost\" in Node.js.\n */\n host: typeof args[1] === 'string' ? args[1] : undefined,\n },\n callback,\n ]\n }\n\n if (typeof args[0] === 'object') {\n /**\n * @note URL arguments receive no special treatment on purpose.\n * Node.js does not support them and reads a given URL as a plain\n * options object (e.g. \"url.port\" is a string, \"url.host\" includes\n * the port). The \"port\" branch below reproduces that reading.\n */\n if ('port' in args[0]) {\n return [\n {\n path: '',\n port: Reflect.get(args[0], 'port'),\n host: Reflect.get(args[0], 'host'),\n auth: Reflect.get(args[0], 'auth'),\n family: Reflect.get(args[0], 'family'),\n hints: Reflect.get(args[0], 'hints'),\n session: Reflect.get(args[0], 'session'),\n localAddress: Reflect.get(args[0], 'localAddress'),\n localPort: Reflect.get(args[0], 'localPort'),\n timeout: Reflect.get(args[0], 'timeout'),\n lookup: Reflect.get(args[0], 'lookup'),\n allowHalfOpen: Reflect.get(args[0], 'allowHalfOpen'),\n noDelay: Reflect.get(args[0], 'noDelay'),\n keepAlive: Reflect.get(args[0], 'keepAlive'),\n keepAliveInitialDelay: Reflect.get(args[0], 'keepAliveInitialDelay'),\n autoSelectFamily: Reflect.get(args[0], 'autoSelectFamily'),\n autoSelectFamilyAttemptTimeout: Reflect.get(\n args[0],\n 'autoSelectFamilyAttemptTimeout'\n ),\n },\n callback,\n ]\n }\n\n return [\n {\n path: args[0].path || '',\n family: Reflect.get(args[0], 'family'),\n session: Reflect.get(args[0], 'session'),\n auth: Reflect.get(args[0], 'auth'),\n timeout: Reflect.get(args[0], 'timeout'),\n allowHalfOpen: Reflect.get(args[0], 'allowHalfOpen'),\n },\n callback,\n ]\n }\n\n throw new Error(`Invalid arguments passed to net.connect: ${args}`)\n}\n","import net from 'node:net'\n\n/**\n * Write the given pending data to the socket using its public\n * \"write()\" method. Unlike direct \"_writeGeneric()\" calls, public\n * writes go through the \"Writable\" queue that only dispatches the\n * next write once the previous one completes. TLS sockets rely on\n * that serialization: their \"TLSWrap\" handle supports a single\n * write in flight and crashes the process with a native\n * \"Assertion failed: !current_write_\" abort when writes overlap\n * (e.g. multiple direct \"_writeGeneric()\" calls issued on a\n * connecting socket replay back-to-back on \"connect\").\n */\nexport function writePendingData(\n socket: net.Socket,\n data: NonNullable<net.Socket['_pendingData']>,\n encoding: BufferEncoding,\n callback?: (error?: Error | null) => void\n): void {\n if (Array.isArray(data)) {\n for (let index = 0; index < data.length; index++) {\n const entry = data[index]\n const isLastEntry = index === data.length - 1\n\n /**\n * @note Per-entry callbacks are intentionally ignored, the same\n * way \"writevGeneric()\" only honors the batch-level callback.\n */\n if (isLastEntry) {\n socket.write(entry.chunk, entry.encoding, callback)\n } else {\n socket.write(entry.chunk, entry.encoding)\n }\n }\n\n return\n }\n\n socket.write(data, encoding, callback)\n}\n\nexport function unwrapPendingData(\n data: NonNullable<net.Socket['_pendingData']>,\n callback: (\n chunk: string | Buffer,\n encoding?: BufferEncoding,\n callback?: (error?: Error | null) => void\n ) => void\n) {\n if (Array.isArray(data)) {\n for (const entry of data) {\n callback(entry.chunk, entry.encoding, entry.callback)\n }\n } else {\n callback(data)\n }\n}\n","import type tls from 'node:tls'\n\n/**\n * Returns the original options the given TLS socket was created with.\n * The original `tls.connect()` stores them on the socket instance\n * under an internal symbol before connecting its transport.\n * @see https://github.com/nodejs/node/blob/3178a762d6a2b1a37b74f02266eea0f3d86603f1/lib/_tls_wrap.js#L1690\n */\nexport function getTlsConnectOptions(\n socket: tls.TLSSocket\n): tls.ConnectionOptions | undefined {\n const kConnectOptions = Object.getOwnPropertySymbols(socket).find(\n (symbol) => {\n return symbol.description === 'connect-options'\n }\n )\n\n if (kConnectOptions == null) {\n return undefined\n }\n\n return Reflect.get(socket, kConnectOptions)\n}\n","import net from 'node:net'\nimport { NetworkConnectionOptions } from './normalize-net-connect-args'\n\nexport function getAddressInfoByConnectionOptions(\n options?: NetworkConnectionOptions\n): ReturnType<net.Socket['address']> {\n if (options == null) {\n return {}\n }\n\n const isIPv6 = options.family === 6 || net.isIPv6(options.host || '')\n\n return {\n address: isIPv6 ? '::1' : '127.0.0.1',\n /**\n * @note Coerce the port to a number. Connection options may\n * describe it as a string (e.g. when created from a URL), while\n * the address info always reports a numeric port.\n */\n port: Number(options.port) || (options.protocol === 'https:' ? 443 : 80),\n family: isIPv6 ? 'IPv6' : 'IPv4',\n }\n}\n\n/**\n * Get the local address info for the given connection options.\n * This describes the client-side end of the connection: the socket\n * is bound to the loopback interface and an ephemeral port, the same\n * way the operating system binds an outgoing connection.\n */\nexport function getLocalAddressInfoByConnectionOptions(\n options?: NetworkConnectionOptions\n): ReturnType<net.Socket['address']> {\n if (options == null) {\n return {}\n }\n\n const isIPv6 = options.family === 6 || net.isIPv6(options.host || '')\n\n return {\n address: options.localAddress || (isIPv6 ? '::1' : '127.0.0.1'),\n port: options.localPort || getEphemeralPort(),\n family: isIPv6 ? 'IPv6' : 'IPv4',\n }\n}\n\n/**\n * Get a random port from the ephemeral range (IANA: 49152-65535),\n * the range the operating system draws from when binding an\n * outgoing connection.\n */\nfunction getEphemeralPort(): number {\n return 49152 + Math.floor(Math.random() * (65535 - 49152 + 1))\n}\n","import net from 'node:net'\nimport tls from 'node:tls'\nimport { invariant } from 'outvariant'\nimport { toBuffer } from '../../utils/buffer-utils'\nimport { createLogger } from '../../utils/logger'\nimport { unwrapPendingData, writePendingData } from './utils/flush-writes'\nimport { NetworkConnectionOptions } from './utils/normalize-net-connect-args'\nimport { TlsConnectionOptions } from './utils/normalize-tls-connect-args'\nimport { getTlsConnectOptions } from './utils/get-tls-connect-options'\nimport {\n getAddressInfoByConnectionOptions,\n getLocalAddressInfoByConnectionOptions,\n} from './utils/address-info'\n\nconst kListenerWrap = Symbol('kListenerWrap')\nexport const kRawSocket = Symbol('kRawSocket')\nexport const kPatched = Symbol('kPatched')\n\nconst logger = createLogger('socket')\n\n// Internally, Node.js represents the result of various operations\n// by the number they return: 0 (error), 1 (success).\ntype OperationStatus = 0 | 1\n\ndeclare module 'node:net' {\n interface Socket {\n [kPatched]?: boolean\n _httpMessage?: object | null\n _pendingData:\n | string\n | Buffer\n | Array<{\n chunk: string | Buffer\n encoding?: BufferEncoding\n callback?: (error?: Error | null) => void\n }>\n | null\n _pendingEncoding: BufferEncoding | ''\n _bytesDispatched: number\n _writeGeneric(\n writev: boolean,\n data: NonNullable<net.Socket['_pendingData']>,\n encoding: BufferEncoding,\n callback?: (error?: Error | null) => void\n ): void\n _handle: TcpHandle\n _start: () => void\n _unrefTimer: () => void\n }\n}\n\ndeclare module 'node:tls' {\n interface TLSSocket {\n _handle: TcpHandle & {\n start: () => void\n onhandshakedone: () => void\n onnewsession: (sessionId: unknown, session: Buffer) => void\n getSession: () => Buffer\n isSessionReused: () => boolean\n getServername: () => string\n getALPNNegotiatedProtocol: () => string | false\n getCipher: () => { name: string; standardName: string; version: string }\n getEphemeralKeyInfo: () => tls.EphemeralKeyInfo\n verifyError: () => void\n }\n }\n}\n\nexport interface TcpHandle {\n open: (fd: unknown) => OperationStatus\n connect: (request: TcpWrap, address: string, port: number) => void\n connect6: (request: TcpWrap, address: string, port: number) => void\n listen: (backlog: number) => OperationStatus\n onconnection?: () => void\n getpeername?: (\n addressInfo: ReturnType<net.Socket['address']>\n ) => OperationStatus\n getsockname?: (\n addressInfo: ReturnType<net.Socket['address']>\n ) => OperationStatus\n reading: boolean\n onread: () => void\n readStart: () => void\n readStop: () => void\n bytesRead: number\n bytesWritten: number\n ref?: () => void\n unref?: () => void\n hasRef?: () => boolean\n fchmod: (mode: number) => void\n setBlocking: (blocking: boolean) => OperationStatus\n setNoDelay?: (noDelay: boolean) => void\n setKeepAlive?: (keepAlive: boolean, initialDelay: number) => void\n setTypeOfService?: (tos: number) => OperationStatus\n shutdown: (reqest: unknown /* ShutdownWrap */) => OperationStatus\n close: () => void\n\n _parent?: TcpHandle\n}\n\nexport interface TcpWrap {\n oncomplete: (\n status: OperationStatus,\n owner: TcpHandle,\n request: TcpWrap,\n readable?: boolean,\n writable?: boolean\n ) => void\n}\n\n/**\n * Create a proxy `net.Socket` instance that represents the intercepted socket server-side.\n * This is the reference exposed as `socket` in the connection listener. This proxy allows\n * the user to interact with `socket` from the server's perspective (e.g. `socket.write()`\n * on the server translates to the `socket.push()` on the client).\n */\ninterface PendingServerWrite {\n chunk: string | Uint8Array\n encoding?: BufferEncoding\n callback?: (error?: Error) => void\n}\n\ninterface PendingServerEnd {\n chunk?: string | Uint8Array\n encoding?: BufferEncoding\n callback?: () => void\n}\n\nfunction toServerSocket<T extends net.Socket>(socket: T): T {\n /**\n * The server-side write buffer. Pushing data to the client honors\n * the client's read backpressure: once \"socket.push()\" reports a\n * full buffer, subsequent server writes queue here and flush when\n * the client reads again (\"_read\"). This mirrors how a real server\n * cannot write faster than the client consumes.\n */\n const pendingWrites: Array<PendingServerWrite> = []\n let pendingEnd: PendingServerEnd | undefined\n let isBackpressured = false\n let isFlushScheduled = false\n\n const flushPendingWrites = (): boolean => {\n /**\n * @note The mocked connection is not established yet. Flush once\n * it is, the same way a real server cannot write to a client\n * that has not connected.\n */\n if (socket.connecting) {\n isBackpressured = true\n\n if (!isFlushScheduled) {\n isFlushScheduled = true\n socket.once('ready', () => {\n isFlushScheduled = false\n flushPendingWrites()\n })\n }\n\n return false\n }\n\n while (pendingWrites.length > 0) {\n const nextWrite = pendingWrites.shift()!\n\n // Receiving mocked data is socket activity: refresh the client's\n // idle timer the same way its own reads would.\n socket._unrefTimer()\n\n const canPushMore = socket.push(\n toBuffer(nextWrite.chunk, nextWrite.encoding),\n nextWrite.encoding\n )\n nextWrite.callback?.()\n\n if (!canPushMore) {\n isBackpressured = true\n return false\n }\n }\n\n const wasBackpressured = isBackpressured\n isBackpressured = false\n\n if (pendingEnd) {\n const finalEnd = pendingEnd\n pendingEnd = undefined\n\n // Deliver the final chunk passed to \"end(chunk)\", if any.\n if (finalEnd.chunk != null) {\n socket.push(\n toBuffer(finalEnd.chunk, finalEnd.encoding),\n finalEnd.encoding\n )\n }\n\n socket.push(null)\n finalEnd.callback?.()\n }\n\n if (wasBackpressured) {\n socket.emit('internal:drain')\n }\n\n return true\n }\n\n /**\n * @note \"_read\" is the client asking for more data. Flush the\n * buffered server writes so the delivery resumes as the client\n * reads, completing the backpressure loop.\n */\n const realRead = socket._read.bind(socket)\n socket._read = (size: number) => {\n realRead(size)\n\n if (pendingWrites.length > 0 || pendingEnd != null || isBackpressured) {\n flushPendingWrites()\n }\n }\n\n return new Proxy(socket, {\n get: (target, property, receiver) => {\n const getRealValue = () => {\n return Reflect.get(target, property, receiver)\n }\n\n if (\n property === 'on' ||\n property === 'addListener' ||\n property === 'once' ||\n property === 'prependListener' ||\n property === 'prependOnceListener'\n ) {\n const realAddListener = getRealValue() as net.Socket['addListener']\n\n return (event: any, listener: (...args: Array<unknown>) => void) => {\n if (event === 'data') {\n const listenerWrap = (chunk: any, encoding?: BufferEncoding) => {\n listener(toBuffer(chunk, encoding))\n }\n\n Object.defineProperty(listener, kListenerWrap, {\n enumerable: false,\n writable: false,\n value: listenerWrap,\n })\n\n /**\n * @note Subscribe using the same method (e.g. \"once\") so its\n * listener semantics apply to the internal channel too.\n */\n Reflect.apply(realAddListener, target, [\n 'internal:write',\n listenerWrap,\n ])\n\n return target\n }\n\n /**\n * @note The \"drain\" event on the server socket signals the\n * flush of the server-side write buffer. It is routed through\n * an internal channel so it does not clash with the \"drain\"\n * event of the underlying client socket.\n */\n if (event === 'drain') {\n Reflect.apply(realAddListener, target, ['internal:drain', listener])\n return target\n }\n\n return realAddListener.call(target, event, listener)\n }\n }\n\n if (property === 'off' || property === 'removeListener') {\n const realRemoveListener =\n getRealValue() as net.Socket['removeListener']\n\n return (event: string, listener: any) => {\n if (event === 'data') {\n const listenerWrap = listener[kListenerWrap]\n\n if (listenerWrap) {\n // The wrap is subscribed to the internal channel,\n // not the \"data\" event (see the listener proxy above).\n return realRemoveListener.call(\n target,\n 'internal:write',\n listenerWrap\n )\n }\n }\n\n if (event === 'drain') {\n return realRemoveListener.call(target, 'internal:drain', listener)\n }\n\n return realRemoveListener.call(target, event, listener)\n }\n }\n\n // Push data to the client socket when server \"socket.write()\" is called.\n if (property === 'write') {\n return ((chunk, encoding, callback) => {\n if (typeof encoding === 'function') {\n callback = encoding\n encoding = undefined\n }\n\n pendingWrites.push({ chunk, encoding, callback })\n\n /**\n * @note Do not push more data to a client that is not\n * reading. The write stays buffered until the client reads\n * again, and \"drain\" signals the flush.\n */\n if (isBackpressured) {\n return false\n }\n\n return flushPendingWrites()\n }) as net.Socket['write']\n }\n\n // Translate server-side \"socket.end()\" to client-sode \"socket.push(null)\".\n if (property === 'end') {\n return ((...args: Parameters<net.Socket['end']>): net.Socket => {\n const callback = args[args.length - 1]\n const chunk = typeof args[0] === 'function' ? undefined : args[0]\n const encoding = typeof args[1] === 'string' ? args[1] : undefined\n\n /**\n * @note The end-of-stream is delivered once the buffered\n * writes flush so the client never observes the EOF before\n * the data that preceded it.\n */\n pendingEnd = {\n chunk,\n encoding,\n callback: typeof callback === 'function' ? callback : undefined,\n }\n flushPendingWrites()\n\n /**\n * @note Do not end the client socket's writable side.\n * The server ending the connection only signals EOF to the\n * client (the client may keep writing on half-open sockets).\n */\n return target\n }) as net.Socket['end']\n }\n\n return getRealValue()\n },\n })\n}\n\nexport abstract class SocketController {\n static PENDING = 0 as const\n static CLAIMED = 1 as const\n static PASSTHROUGH = 2 as const\n\n public readyState:\n | typeof SocketController.PENDING\n | typeof SocketController.CLAIMED\n | typeof SocketController.PASSTHROUGH\n\n #awaitedVerdicts = 0\n\n private [kRawSocket]: net.Socket\n\n constructor(socket: net.Socket) {\n this[kRawSocket] = socket\n // Mark this socket as patched so socket-related patches\n // (e.g. the \"destroyed\" getter) can tell it apart from\n // the sockets created before the interception was applied.\n socket[kPatched] = true\n this.readyState = SocketController.PENDING\n }\n\n /**\n * Claim this socket. Once claimed, the connection attempt succeeds\n * regardless of the requested host and the interceptor becomes the\n * mocked server for this connection.\n */\n public claim(): void {\n invariant(\n this.readyState === SocketController.PENDING,\n 'Failed to claim a socket connection: already handled (%s)',\n this.readyState\n )\n\n this.readyState = SocketController.CLAIMED\n }\n\n /**\n * Establish this socket connection as-is.\n */\n public passthrough(): void {\n invariant(\n this.readyState === SocketController.PENDING,\n 'Failed to passthrough a socket connection: already handled (%s)',\n this.readyState\n )\n\n this.readyState = SocketController.PASSTHROUGH\n }\n\n /**\n * Await a verdict on this connection from the given number of\n * subscribers. A connection nobody awaits to inspect (or one that\n * every awaited subscriber has declined) is passed through as-is.\n * This makes \"unclaimed after everyone declined\" a state owned by\n * the controller instead of the individual subscribers.\n */\n public awaitVerdicts(count: number): void {\n this.#awaitedVerdicts = count\n\n if (this.#awaitedVerdicts === 0) {\n this.passthrough()\n }\n }\n\n /**\n * Decline this socket connection. Declining means the subscriber\n * has inspected the connection and will not handle it (e.g. the\n * traffic is not of the protocol that subscriber implements).\n * Once every awaited subscriber declines, the connection is\n * passed through as-is.\n */\n public decline(): void {\n if (this.readyState !== SocketController.PENDING) {\n return\n }\n\n this.#awaitedVerdicts -= 1\n\n if (this.#awaitedVerdicts <= 0) {\n /**\n * @note Defer the passthrough so it never transitions this\n * controller in the middle of a client write. Declines are\n * issued while the written data is being pushed to the server\n * socket, and a synchronous transition would race the write's\n * own bookkeeping (e.g. re-buffering the write after a reset\n * at an exchange boundary).\n */\n process.nextTick(() => {\n if (\n this.readyState === SocketController.PENDING &&\n !this[kRawSocket].destroyed\n ) {\n this.passthrough()\n }\n })\n }\n }\n}\n\nexport type FlushPendingDataFunction = (\n data: NonNullable<net.Socket['_pendingData']>,\n encoding: BufferEncoding | undefined,\n callback: (data: NonNullable<net.Socket['_pendingData']>) => void\n) => void\n\ntype CorkedReadEvent =\n | { type: 'data'; chunk: Buffer }\n | { type: 'end' }\n | { type: 'close'; hadError: boolean }\n\nexport class TcpSocketController extends SocketController {\n public serverSocket: net.Socket\n\n protected pendingConnection: PromiseWithResolvers<[TcpWrap, TcpHandle]>\n\n #connectionOptions?: NetworkConnectionOptions\n #retargetedConnectionOptions?: NetworkConnectionOptions &\n net.SocketConnectOpts\n #realWriteGeneric: net.Socket['_writeGeneric']\n #passthroughSocket: net.Socket | null = null\n #bufferedWrites: Array<Parameters<net.Socket['_writeGeneric']>> = []\n #readsCorked = false\n #corkedReads: Array<CorkedReadEvent> = []\n #realHandleSwapped = false\n #resetScheduled = false\n #clientCloseEmitted = false\n #clientEndPushed = false\n #connectEmulated = false\n\n constructor(\n protected readonly socket: net.Socket,\n protected readonly createConnection: () => net.Socket,\n connectionOptions?: NetworkConnectionOptions\n ) {\n super(socket)\n\n /**\n * @note Plain TCP sockets capture the connection options from the\n * \"socket.connect()\" proxy below. TLS sockets carry additional\n * TLS-level options that never pass through \"socket.connect()\",\n * so those are provided explicitly (see \"TlsSocketController\").\n */\n this.#connectionOptions = connectionOptions\n\n // Implement the read method to prevent the \"Error: read ENOTCONN\" errors on non-existing hosts.\n this.socket._read = () => {\n /**\n * @note Resume the passthrough socket when the consumer asks for\n * more data. Node.js calls \"_read()\" once the consumer drains the\n * read buffer (e.g. after \"resume()\"). The passthrough socket may\n * have been paused when the consumer's buffer got full, and this\n * is the only signal to continue reading.\n */\n this.#passthroughSocket?.resume()\n }\n\n // Store the unpatched write method so passthrough writes can\n // delegate to it once the socket receives the real handle.\n this.#realWriteGeneric = this.socket._writeGeneric\n this.#bufferedWrites = []\n\n this.socket._writeGeneric = (...args) => {\n this.#handleWrite(args)\n }\n\n this.socket.connect = new Proxy(this.socket.connect, {\n apply: (target, thisArg, args) => {\n logger.verbose('socket.connect() %o', args)\n\n this.#connectionOptions = args[0]\n\n /**\n * @note Do not bind the intercepted socket to the requested\n * local address/port. Binding reserves the port for real, and\n * the passthrough connection (created with the original options)\n * would then bind the same port again, resulting in a conflict.\n * The requested values are still reflected in the address info\n * of a claimed socket (see \"claim()\").\n */\n if (\n args[0] != null &&\n typeof args[0] === 'object' &&\n (args[0].localAddress != null || args[0].localPort != null)\n ) {\n args[0] = {\n ...args[0],\n localAddress: undefined,\n localPort: undefined,\n }\n }\n\n return Reflect.apply(target, thisArg, args)\n },\n })\n\n /**\n * @note A single socket can be reused for connections to the same host.\n * When one connection ends, the Agent frees the socket, then uses it\n * to write the next request's HTTP message immediately. Use the \"free\"\n * event to transition the controller into the pending state so the\n * next exchange is handled anew.\n */\n socket\n .on('free', () => {\n logger.verbose('client socket freed!')\n this.reset()\n })\n .on('close', () => {\n logger.verbose('client socket closed!')\n this.#clientCloseEmitted = true\n\n /**\n * @note Destroy the passthrough socket, if any. The client\n * socket is done, and a passthrough connection left open would\n * linger until the server closes it (or error unhandled if it\n * is still connecting).\n */\n this.#passthroughSocket?.destroy()\n this.#passthroughSocket = null\n\n this.#bufferedWrites = []\n this.#readsCorked = false\n this.#corkedReads = []\n })\n\n this.serverSocket = toServerSocket(this.socket)\n\n this.pendingConnection = Promise.withResolvers()\n this.#reset()\n }\n\n /**\n * Reset this controller to the pending state so the next exchange\n * on this socket can be handled anew. This is meant for kept-alive\n * sockets that are reused for multiple exchanges by clients that\n * don't emit the \"free\" event on the socket (e.g. Undici).\n *\n * Providing connection options retargets this connection: the\n * exchanges that follow belong to the given target (e.g. the\n * authority of an established \"CONNECT\" tunnel). An unclaimed\n * exchange then passes through to that target instead of the\n * originally dialed one, and a claimed exchange reports it as the\n * peer. The verdict count is deliberately not re-armed: subscribers\n * that declined this connection's protocol stay declined across\n * the exchanges, retargeted or not.\n */\n public reset(\n connectionOptions?: NetworkConnectionOptions & net.SocketConnectOpts\n ): void {\n if (connectionOptions != null) {\n this.#retargetedConnectionOptions = connectionOptions\n this.#connectionOptions = connectionOptions\n\n /**\n * @note The passthrough connection to the original target, if\n * any, cannot serve the retargeted exchanges. Detach its\n * forwarding listeners before destroying it so its teardown is\n * not mistaken for the client connection's own (e.g. its \"close\"\n * must not close the client socket).\n */\n if (this.#passthroughSocket) {\n this.#passthroughSocket\n .removeListener('connect', this.#onRealSocketConnect)\n .removeListener(\n 'connectionAttemptFailed',\n this.#onRealSocketConnectionAttemptFailed\n )\n .removeListener(\n 'connectionAttemptTimeout',\n this.#onRealSocketConnectionAttemptTimeout\n )\n .removeListener('data', this.#onRealSocketData)\n .removeListener('error', this.#onRealSocketError)\n .removeListener('end', this.#onRealSocketEnd)\n .removeListener('close', this.#onRealSocketClose)\n .destroy()\n\n this.#passthroughSocket = null\n\n /**\n * @note The handle swapped in from the destroyed connection,\n * if any, no longer carries this socket's traffic. Treat the\n * socket as not swapped so the retargeted passthrough writes\n * directly to the new connection until its own handle swap.\n */\n this.#realHandleSwapped = false\n }\n }\n\n /**\n * @note Only settled (claimed or passed-through) sockets need a reset.\n * Resetting a pending socket again would discard the writes already\n * buffered for the next exchange (e.g. the Agent \"free\" event firing\n * after the parser has reset this controller at a message boundary).\n */\n if (this.readyState === SocketController.PENDING) {\n return\n }\n\n this.#reset()\n }\n\n /**\n * Schedule a reset of this controller to happen right before the\n * next client write. The consumer signals the boundary of the\n * current exchange (e.g. an HTTP message boundary) the moment it is\n * parsed — mid-delivery of the exchange's final chunk — when an\n * immediate reset would discard that chunk. Deferring the reset to\n * the next write lets the current exchange settle as-is while the\n * next exchange's first write already buffers for a new verdict\n * (instead of following the previous one, e.g. leaking to the\n * passthrough connection).\n */\n public scheduleReset(): void {\n this.#resetScheduled = true\n }\n\n #reset(): void {\n logger.verbose('resetting the socket...')\n\n this.#resetScheduled = false\n this.readyState = SocketController.PENDING\n this.pendingConnection = Promise.withResolvers()\n this.#bufferedWrites = []\n this.#connectEmulated = false\n\n // Release the pending data of the previous exchange, if any,\n // so kept-alive sockets do not retain every written payload.\n this.socket._pendingData = null\n this.socket._pendingEncoding = ''\n\n const wrapHandle = (handle: TcpHandle) => {\n this.pendingConnection.promise.then(() => {\n logger.verbose('connection request resolved!', this.readyState)\n\n process.nextTick(() => {\n /**\n * @note If by this point the socket hasn't been handled,\n * is still connecting, doesn't have any writes buffered,\n * and has a \"connect\" listener, assume it's the \"write after connect\"\n * scenario (e.g. undici). In that case, auto-claim the socket to\n * transition to the connected state appropriately to its handle.\n */\n if (\n this.readyState === SocketController.PENDING &&\n this.socket.connecting &&\n this.#bufferedWrites.length === 0 &&\n this.socket.listenerCount('connect') > 0\n ) {\n logger.verbose(\n 'assume connect->write socket, calling \"connect\" listeners...'\n )\n this.emulateConnect()\n }\n })\n })\n\n /**\n * Remove the \"setTypeOfService\" from the handle, if present (Node.js v24+).\n * Removing it has no effect on the socket but prevents the \"setTypeOfService EBADF\" error.\n * @see https://github.com/nodejs/node/blob/69a970f76814d40f55cf162d0cc3632fe8a7e599/lib/net.js#L661\n * @see https://github.com/nodejs/undici/blob/bf684f7de01616708a33a5d1c092177622394442/lib/dispatcher/client-h1.js#L1136\n */\n if (handle.setTypeOfService) {\n handle.setTypeOfService = undefined\n }\n\n handle.connect = handle.connect6 = (request) => {\n logger.verbose('handle.connect()')\n this.pendingConnection.resolve([request, handle])\n }\n\n logger.verbose('socket handle wrapped! waiting for connection request...')\n }\n\n if (this.socket._handle) {\n wrapHandle(this.socket._handle)\n } else {\n this.socket.prependOnceListener('connectionAttempt', () => {\n wrapHandle(this.socket._handle)\n })\n }\n }\n\n /**\n * Handle a write performed on the client socket. Installed once as\n * \"_writeGeneric\", this is the single write path for every controller\n * state, dispatching on \"readyState\".\n */\n #handleWrite(args: Parameters<net.Socket['_writeGeneric']>): void {\n const data = args[1]\n\n logger.verbose('socket write (state: %d) %o', this.readyState, args)\n\n /**\n * @note A scheduled reset marks the boundary of the previous\n * exchange (see \"scheduleReset\"). This write is the first one past\n * the boundary; reset before dispatching so it opens the next\n * exchange instead of following the previous exchange's verdict.\n */\n if (this.#resetScheduled) {\n this.reset()\n }\n\n /**\n * @note Buffer the write BEFORE pushing the data to the server socket.\n * Handling the pushed data may transition this controller within the\n * same call stack, and each transition settles the buffered entry:\n * \"passthrough()\" flushes it to the real socket, \"claim()\" drops it,\n * and a reset at an HTTP message boundary keeps it for the next exchange.\n */\n this.#bufferedWrites.push(args)\n\n if (this.readyState === SocketController.PENDING) {\n /**\n * @note Reflect the buffered write in \"_pendingData\" so it counts\n * toward \"bytesWritten\", the same way Node.js counts the pending\n * data of a connecting socket. Flushing the buffered writes resets\n * \"_pendingData\" (see `passthrough()`).\n */\n const pendingData = Array.isArray(this.socket._pendingData)\n ? this.socket._pendingData\n : []\n\n unwrapPendingData(data, (chunk, chunkEncoding) => {\n pendingData.push({ chunk, encoding: chunkEncoding })\n })\n\n this.socket._pendingData = pendingData\n\n // The server socket will NEVER have any \"data\" listeners attached\n // on the first write because the \"connection\" interceptor event\n // emits on the next tick.\n if (this.socket.listenerCount('internal:write') === 0) {\n logger.verbose(\n 'no server data listeners, scheduling to the next tick...'\n )\n\n process.nextTick(() => {\n this.#push(data)\n })\n } else {\n this.#push(data)\n }\n } else {\n this.#push(data)\n }\n\n /**\n * Dispatch on the state observed AFTER the push since handling the\n * pushed data may have transitioned this controller synchronously.\n */\n switch (this.readyState) {\n case SocketController.PENDING: {\n /**\n * @note The write either awaits the verdict on this exchange or,\n * if the push reset this controller at a message boundary, opens\n * the next exchange (the reset cleared the buffer; re-buffer it).\n */\n if (!this.#bufferedWrites.includes(args)) {\n this.#bufferedWrites.push(args)\n }\n\n this.#acknowledgeWrite(args)\n return\n }\n\n case SocketController.CLAIMED: {\n /**\n * @note Once claimed, there's nowhere else to write chunks to.\n * The data was delivered to the server socket; complete the write\n * so the socket's writable state settles (enabling \"finish\").\n */\n this.#removeBufferedWrite(args)\n this.#acknowledgeWrite(args)\n return\n }\n\n case SocketController.PASSTHROUGH: {\n /**\n * @note A synchronous \"passthrough()\" has already flushed this\n * write (with its callback) to the real socket.\n */\n if (!this.#removeBufferedWrite(args)) {\n return\n }\n\n /**\n * @note Until the handle swap, the client socket's own handle\n * cannot carry any data (it never actually connects). Write\n * directly to the passthrough socket instead. This also prevents\n * the \"connecting\" write replay of `Socket.prototype._writeGeneric`\n * from pushing the same data to the server socket twice.\n */\n if (!this.#realHandleSwapped && this.#passthroughSocket) {\n writePendingData(this.#passthroughSocket, data, args[2], args[3])\n return\n }\n\n this.#realWriteGeneric.apply(this.socket, args)\n }\n }\n }\n\n /**\n * Complete the given write by invoking its callback. The callback is\n * then removed from the write entry so flushing that entry later\n * (e.g. on passthrough) does not invoke it twice.\n */\n #acknowledgeWrite(args: Parameters<net.Socket['_writeGeneric']>): void {\n const callback = args[3]\n\n if (typeof callback === 'function') {\n callback()\n args[3] = undefined\n }\n }\n\n #removeBufferedWrite(args: Parameters<net.Socket['_writeGeneric']>): boolean {\n const index = this.#bufferedWrites.indexOf(args)\n\n if (index === -1) {\n return false\n }\n\n this.#bufferedWrites.splice(index, 1)\n return true\n }\n\n protected emulateConnect() {\n this.#connectEmulated = true\n\n /**\n * @note Reflect the connected state before notifying the listeners,\n * the same way Node.js does before emitting \"connect\".\n */\n Reflect.set(this.socket, 'connecting', false)\n\n /**\n * @note Invoke the raw listeners so the \"once\" wrappers get\n * consumed. This prevents listeners like the connection callback\n * from being invoked again when \"connect\" is emitted for real\n * (e.g. once the claimed connection completes).\n */\n for (const listener of this.socket.rawListeners('connect')) {\n listener.apply(this.socket)\n }\n }\n\n /**\n * Push the given data to the server socket.\n * This has no effect on the public-facing socket and is used\n * only for the interceptors to subscribe to \"socket.on('data')\"\n * before the data is actually written anywhere.\n */\n #push = (data: net.Socket['_pendingData']) => {\n if (data == null) {\n return\n }\n\n logger.verbose('server push %o', data)\n\n unwrapPendingData(data, (chunk, encoding) => {\n logger.verbose('server emitting \"data\" %o', { chunk, encoding })\n\n this.socket.emit('internal:write', chunk, encoding)\n })\n }\n\n #onRealSocketConnect = () => {\n if (!this.#passthroughSocket) {\n return\n }\n\n /**\n * @note The consumer may destroy the socket while the passthrough\n * connection is still being established. The passthrough \"connect\"\n * (I/O poll phase) can arrive before the client's \"close\" callback\n * (close phase) within the same event loop iteration. A destroyed\n * socket must not emit \"connect\"/\"ready\".\n */\n if (this.socket.destroyed) {\n this.#passthroughSocket.destroy()\n return\n }\n\n const replacedHandle = this.socket._handle\n const wasUnrefed =\n replacedHandle != null &&\n typeof replacedHandle.hasRef === 'function' &&\n !replacedHandle.hasRef()\n\n this.socket._handle = this.#passthroughSocket._handle\n this.#realHandleSwapped = true\n\n /**\n * @note Preserve the ref state across the handle swap. If the\n * consumer unrefed the socket while it was connecting, the swapped\n * handle must not hold the process alive either.\n */\n if (wasUnrefed) {\n this.socket._handle.unref?.()\n }\n\n /**\n * @note Close the replaced handle. Nothing references it past this\n * point, and left open, it keeps the process alive indefinitely.\n * For TLS sockets, the replaced handle is a TLSWrap; close its\n * underlying transport too (closing the wrap alone does not close\n * the TCP handle it sits on).\n */\n if (replacedHandle != null) {\n replacedHandle.close()\n replacedHandle._parent?.close()\n }\n\n Reflect.set(this.socket, 'connecting', false)\n\n /**\n * @note Read the remote address info once so it gets cached on the\n * socket. The passthrough socket controls the swapped handle and may\n * close it at any point (e.g. once the server ends the connection),\n * while Node.js keeps serving the cached info for sockets that\n * have connected. Reading must happen after the socket is no longer\n * connecting (the info of connecting sockets is never cached).\n */\n void this.socket.remoteAddress\n\n this.socket.emit('connect')\n this.socket.emit('ready')\n }\n\n #onRealSocketConnectionAttemptFailed = (\n address: string,\n port: number,\n family: number,\n error: Error\n ) => {\n this.socket.emit('connectionAttemptFailed', address, port, family, error)\n }\n\n #onRealSocketConnectionAttemptTimeout = (\n address: string,\n port: number,\n family: number\n ) => {\n this.socket.emit('connectionAttemptTimeout', address, port, family)\n }\n\n #onRealSocketData = (data: Buffer) => {\n logger.verbose('real socket \"data\" event %o', data)\n\n /**\n * @note Receiving data is socket activity. Refresh the idle timer\n * of the client socket the same way its own reads would\n * (\"socket.setTimeout()\" must not fire during an active transfer).\n * The data arrives on the real socket, which only refreshes the\n * real socket's timer.\n */\n this.socket._unrefTimer()\n\n if (this.#readsCorked) {\n logger.verbose('reads are corked, buffering the data...')\n this.#corkedReads.push({ type: 'data', chunk: data })\n return\n }\n\n if (!this.socket.push(data)) {\n logger.verbose(\n 'client socket forbade more pushes, pausing the passthrough socket...'\n )\n this.#passthroughSocket?.pause()\n }\n }\n\n #onRealSocketError = (error: Error) => {\n logger.verbose('real socket \"error\" event %o', error)\n\n if (this.socket.destroyed) {\n logger.verbose(\n 'real socket errored but client socket already destroyed, skipping...'\n )\n return\n }\n\n logger.verbose('real socket errored, forwarding %o', error)\n\n this.socket.destroy(error)\n\n // The handle swap in passthrough (this.socket._handle = realSocket._handle)\n // breaks Node's internal close machinery—destroy() emits \"error\" but never\n // emits \"close\". Consumers like Undici wait for \"close\" to finalize the\n // request, so we must emit it manually.\n // Before the swap (e.g. a failed connection attempt), the client socket\n // emits \"close\" on its own, and emitting here would duplicate it.\n if (this.#realHandleSwapped) {\n process.nextTick(() => this.socket.emit('close', true))\n }\n }\n\n #onRealSocketEnd = () => {\n // Receiving the end-of-stream is a read, the same as data.\n this.socket._unrefTimer()\n\n if (this.#readsCorked) {\n this.#corkedReads.push({ type: 'end' })\n return\n }\n\n this.#clientEndPushed = true\n this.socket.push(null)\n }\n\n #onRealSocketClose = (hadError: boolean) => {\n /**\n * @note The connection is fully closed and the swapped handle\n * cannot shut down anymore. Report a synchronous shutdown so the\n * automatic \"end()\" of a half-closed socket (\"allowHalfOpen: false\")\n * finishes without errors once the consumer reads the end-of-stream.\n */\n if (this.#realHandleSwapped && this.socket._handle) {\n this.socket._handle.shutdown = () => 1\n }\n\n if (this.#readsCorked) {\n this.#corkedReads.push({ type: 'close', hadError })\n return\n }\n\n // The client socket already emitted \"close\" (e.g. it was destroyed\n // with an error before the handle swap). Forwarding the real socket\n // \"close\" would emit it twice.\n if (this.#clientCloseEmitted) {\n return\n }\n\n // A destroyed client socket with an intact handle emits \"close\"\n // through its own machinery. Only forward the real socket \"close\"\n // when the handle swap suppressed that emission.\n if (this.socket.destroyed && !this.#realHandleSwapped) {\n return\n }\n\n this.#emitClientClose(hadError)\n }\n\n /**\n * Emit the \"close\" event on the client socket, honoring the order\n * of the socket teardown events.\n * @note The client may be paused with the received data (and the\n * end-of-stream) still buffered. Node.js never emits \"close\" before\n * \"end\" on a gracefully closed connection: the teardown waits until\n * the consumer reads the buffered data.\n */\n #emitClientClose(hadError: boolean): void {\n if (this.#clientCloseEmitted) {\n return\n }\n\n if (\n this.#clientEndPushed &&\n !this.socket.readableEnded &&\n !this.socket.destroyed\n ) {\n let closeDelivered = false\n const deliverClose = (hadErrorOverride?: boolean) => {\n if (closeDelivered) {\n return\n }\n closeDelivered = true\n\n process.nextTick(() => {\n this.#emitClientClose(hadErrorOverride ?? hadError)\n })\n }\n\n this.socket.once('end', () => {\n deliverClose()\n })\n\n /**\n * @note The consumer may also destroy the socket before reading\n * the buffered data. Node.js still emits \"close\" for such\n * sockets, but the destroy machinery of a socket with a swapped\n * (already closed) handle never completes. Deliver \"close\" here.\n */\n const realDestroy = this.socket._destroy\n this.socket._destroy = (error, callback) => {\n deliverClose(error != null)\n return realDestroy.call(this.socket, error, callback)\n }\n return\n }\n\n // Agent pools inspect the socket state inside their close listeners.\n // Match Node.js: a closed socket must already be destroyed and unwritable.\n this.socket.destroy()\n\n if (this.#realHandleSwapped) {\n this.socket.emit('close', hadError)\n }\n }\n\n #onMockSocketDrain = () => {\n logger.verbose('client socket drained!')\n this.#passthroughSocket?.resume()\n }\n\n /**\n * Forward the client's half-close to the passthrough socket.\n * The client's writable side may finish before the real handle is\n * swapped in (\"_final\" of a connected client runs against the mock\n * handle), leaving the FIN unsent. Once the handle is swapped, the\n * client's own shutdown reaches the real connection natively.\n */\n #forwardClientFinish = () => {\n if (!this.#realHandleSwapped) {\n this.#passthroughSocket?.end()\n }\n }\n\n /**\n * Suspend forwarding of the passthrough socket events (\"data\", \"end\", \"close\")\n * to the client socket. The events are buffered in order until `uncorkReads()`\n * is called. This allows the consumer to delay the delivery of the original\n * response to the client (e.g. until its own asynchronous logic settles).\n *\n * @note Pausing the client socket is not enough to delay the delivery.\n * Consumers like Undici read the pushed data from a paused socket\n * directly via `socket.read()`, which is unaffected by `socket.pause()`.\n */\n public corkReads(): void {\n this.#readsCorked = true\n }\n\n /**\n * Resume forwarding of the passthrough socket events to the client socket,\n * replaying any events buffered while the reads were corked.\n */\n public uncorkReads(): void {\n if (!this.#readsCorked) {\n return\n }\n\n this.#readsCorked = false\n\n for (const corkedRead of this.#corkedReads.splice(0)) {\n switch (corkedRead.type) {\n case 'data': {\n if (!this.socket.push(corkedRead.chunk)) {\n logger.verbose(\n 'client socket forbade more pushes, pausing the passthrough socket...'\n )\n this.#passthroughSocket?.pause()\n }\n break\n }\n\n case 'end': {\n this.#clientEndPushed = true\n this.socket.push(null)\n break\n }\n\n case 'close': {\n this.#emitClientClose(corkedRead.hadError)\n break\n }\n }\n }\n }\n\n public claim(): void {\n super.claim()\n\n /**\n * @note The client may destroy the socket before the connection\n * is claimed (e.g. abort a request in-flight). There is no\n * connection to mock then, and the destroyed socket has no handle.\n */\n if (this.socket.destroyed) {\n logger.verbose('socket already destroyed, skipping claim...')\n return\n }\n\n /**\n * @note Skip already connected sockets (e.g. kept-alive sockets\n * reused for the next exchange). Sockets with an emulated \"connect\"\n * only appear connected and must still complete the mock connection.\n */\n if (!this.socket.connecting && !this.#connectEmulated) {\n logger.verbose('socket already connected, skipping claim...')\n return\n }\n\n logger.verbose('-> claim!')\n\n /**\n * @note Reflect the local end of the claimed socket, the same way\n * the operating system reports the bound address of an outgoing\n * connection via \"socket.address()\"/\"localAddress\"/\"localPort\".\n * Patching the handle also prevents Node.js from handling the\n * \"getsockname\" errors of the never-connected raw handle.\n * @see https://github.com/nodejs/node/blob/13eb80f3b718452213e0fc449702aefbbfe4110f/lib/net.js#L971\n */\n this.socket._handle.getsockname = (addressInfo) => {\n Object.assign(\n addressInfo,\n getLocalAddressInfoByConnectionOptions(this.#connectionOptions)\n )\n return 0\n }\n\n /**\n * @note Reflect the connection target as the peer of the claimed\n * socket, the same way a connected socket reports the server it\n * connected to via \"remoteAddress\"/\"remotePort\"/\"remoteFamily\".\n */\n this.socket._handle.getpeername = (addressInfo) => {\n Object.assign(\n addressInfo,\n getAddressInfoByConnectionOptions(this.#connectionOptions)\n )\n return 0\n }\n\n this.#bufferedWrites = []\n\n // Release the buffered write payloads. They were already delivered\n // to the server socket, and there is nowhere else to flush them.\n this.socket._pendingData = null\n this.socket._pendingEncoding = ''\n\n this.pendingConnection.promise.then(([request, handle]) => {\n logger.verbose('connection request resolved, mocking the connection...')\n\n /**\n * @note \"afterConnect\" asserts that the socket is connecting.\n * Restore the flag if the connect was emulated earlier (emulation\n * flips it so its listeners observe a connected socket).\n * \"afterConnect\" itself sets it back to false.\n */\n if (this.#connectEmulated) {\n Reflect.set(this.socket, 'connecting', true)\n }\n\n /**\n * @see https://github.com/nodejs/node/blob/9cd6630870b776e96c5cf0ac68c31e2f46df3835/lib/net.js#L1142\n */\n request.oncomplete(0, handle, request, true, true)\n })\n }\n\n public passthrough(flushPendingData?: FlushPendingDataFunction): net.Socket {\n super.passthrough()\n\n logger.verbose('-> passthrough!')\n\n const createRealSocket = () => {\n const realSocket = this.#retargetedConnectionOptions\n ? this.#createRetargetedConnection(this.#retargetedConnectionOptions)\n : this.createConnection()\n\n // Mark the passthrough socket as patched so it's exempt from\n // the unpatched socket detection (it never enters agent pools,\n // but this skips the detection cost on its every \"destroyed\" read).\n realSocket[kPatched] = true\n\n if (this.socket.timeout != null) {\n realSocket.setTimeout(this.socket.timeout)\n }\n\n return realSocket\n }\n\n // If keepalive, reuse the existing real socket.\n const realSocket =\n this.#passthroughSocket && !this.#passthroughSocket.destroyed\n ? this.#passthroughSocket\n : createRealSocket()\n\n if (realSocket !== this.#passthroughSocket) {\n this.#passthroughSocket = realSocket\n }\n\n if (this.#bufferedWrites.length === 0) {\n logger.verbose(\n 'passthrough with empty writes buffer (state: %d)',\n this.readyState\n )\n }\n\n /**\n * Flush any writes during the pending phase to the passthrough socket.\n * @note These are written directly on the passthrough socket to prevent\n * them from being forwarded as \"data\" events on the server (already emitted).\n */\n for (let i = 0; i < this.#bufferedWrites.length; i++) {\n const pendingWrite = this.#bufferedWrites[i]\n\n if (i === 0 && typeof flushPendingData === 'function') {\n const data = pendingWrite[1]\n const encoding = pendingWrite[2]\n flushPendingData(data, encoding, (nextData) => {\n pendingWrite[1] = nextData\n })\n }\n\n const [, data, encoding, callback] = pendingWrite\n writePendingData(realSocket, data, encoding, callback)\n }\n\n this.#bufferedWrites = []\n this.socket._pendingData = null\n this.socket._pendingEncoding = ''\n\n this.socket.address = realSocket.address.bind(realSocket)\n\n this.socket.removeListener('drain', this.#onMockSocketDrain)\n this.socket.on('drain', this.#onMockSocketDrain)\n\n realSocket\n .removeListener('connect', this.#onRealSocketConnect)\n .removeListener(\n 'connectionAttemptFailed',\n this.#onRealSocketConnectionAttemptFailed\n )\n .removeListener(\n 'connectionAttemptTimeout',\n this.#onRealSocketConnectionAttemptTimeout\n )\n .removeListener('data', this.#onRealSocketData)\n .removeListener('error', this.#onRealSocketError)\n .removeListener('end', this.#onRealSocketEnd)\n .removeListener('close', this.#onRealSocketClose)\n\n realSocket\n .once('connect', this.#onRealSocketConnect)\n .on('connectionAttemptFailed', this.#onRealSocketConnectionAttemptFailed)\n .on(\n 'connectionAttemptTimeout',\n this.#onRealSocketConnectionAttemptTimeout\n )\n .on('data', this.#onRealSocketData)\n .on('error', this.#onRealSocketError)\n .on('end', this.#onRealSocketEnd)\n .on('close', this.#onRealSocketClose)\n\n /**\n * @note Forward the client's half-close, unless the real handle\n * is already swapped in — the client's own shutdown then reaches\n * the real connection natively (see \"#forwardClientFinish\").\n */\n if (!this.#realHandleSwapped) {\n if (this.socket.writableFinished) {\n this.#forwardClientFinish()\n } else {\n this.socket.removeListener('finish', this.#forwardClientFinish)\n this.socket.once('finish', this.#forwardClientFinish)\n }\n }\n\n return realSocket\n }\n\n /**\n * Create the passthrough connection to the target this controller\n * was retargeted to (see `reset()`). The original `createConnection`\n * dials the originally requested target and cannot serve retargeted\n * exchanges.\n */\n #createRetargetedConnection(\n connectionOptions: net.SocketConnectOpts\n ): net.Socket {\n const realSocket = new net.Socket()\n\n /**\n * @note Mark the socket as patched before connecting: the patched\n * \"Socket.prototype.connect\" exempts such sockets, establishing\n * the connection for real.\n */\n realSocket[kPatched] = true\n\n return realSocket.connect(connectionOptions)\n }\n}\n\nexport class TlsSocketController extends TcpSocketController {\n /**\n * @note The TLS connection options must be provided explicitly.\n * They cannot be captured from \"socket.connect()\" like for plain\n * TCP sockets because \"tls.connect()\" fixes them at the TLS socket\n * construction, before its transport ever connects.\n */\n #tlsConnectionOptions?: TlsConnectionOptions\n\n constructor(\n protected readonly socket: tls.TLSSocket,\n protected readonly createConnection: () => tls.TLSSocket,\n tlsConnectionOptions?: TlsConnectionOptions\n ) {\n super(socket, createConnection, tlsConnectionOptions)\n\n this.#tlsConnectionOptions = tlsConnectionOptions\n\n socket.prependListener('secureConnect', () => {\n /**\n * @note Reflect the negotiated ALPN protocol from the handle that\n * completed the handshake. The socket sets \"alpnProtocol\" only in\n * its own \"_finishInit\", which never runs for passthrough\n * connections (the handshake completes on the passthrough socket\n * whose handle this socket inherits).\n */\n socket.alpnProtocol = socket._handle.getALPNNegotiatedProtocol()\n })\n }\n\n protected emulateConnect(): void {\n /**\n * @note The client chooses its wire protocol when we emulate the\n * handshake. A later passthrough handshake must preserve that choice\n * instead of negotiating a different protocol for buffered writes.\n */\n if (this.#tlsConnectionOptions) {\n const alpnProtocol = this.socket._handle.getALPNNegotiatedProtocol()\n this.#tlsConnectionOptions.ALPNProtocols = alpnProtocol\n ? [alpnProtocol]\n : []\n }\n\n super.emulateConnect()\n\n // For TLS sockets, also invoke the \"secureConnect\" callbacks since some consumers,\n // like Undici, listen to those to start writing to the socket.\n for (const listener of this.socket.rawListeners('secureConnect')) {\n listener.apply(this.socket)\n }\n }\n\n public claim(): void {\n /**\n * @note The client may destroy the socket before the connection\n * is claimed. Defer to the parent class, which transitions the\n * ready state and skips the mock connection of destroyed sockets.\n */\n if (this.socket.destroyed) {\n super.claim()\n return\n }\n\n /**\n * @note Reflect that the mocked connection is not authorized.\n * There is no real peer certificate to verify. The identity check\n * itself is skipped for claimed connections (see the\n * \"isSessionReused\" mock below).\n */\n this.socket.prependOnceListener('secureConnect', () => {\n Reflect.set(this.socket, 'authorized', false)\n Reflect.set(\n this.socket,\n 'authorizationError',\n 'MOCKED_CONNECTION_NOT_VERIFIED'\n )\n })\n\n // Run this logic before the parent's class method so it executes first.\n // TLSWrap methods have to be patched before TCPWrap fires \"oncomplete\".\n const handle = this.socket._handle\n\n handle.start = () => void 0\n\n /**\n * Mock this to prevent the \"Error: Worker exited unexpectedly\" error.\n * This will trigger when \"secure\" is emitted.\n * @see https://github.com/nodejs/node/blob/bdc8131fa78089b81b74dbff467365afb6536e6a/lib/internal/tls/wrap.js#L1648\n */\n handle.verifyError = () => void 0\n\n handle.getSession = () => {\n return Buffer.from('mocked session')\n }\n\n /**\n * @note Skip the server identity check for this mocked connection.\n * Node.js runs \"options.checkServerIdentity\" in \"onConnectSecure\"\n * against the peer certificate, and a mocked connection has no\n * real peer certificate — the caller's (or the default) validation\n * would fail and destroy the socket. The check is overridden on the\n * socket's internal connect options since the emulated handshake\n * still completes through the regular \"onConnectSecure\" path.\n * @see https://github.com/nodejs/node/blob/3178a762d6a2b1a37b74f02266eea0f3d86603f1/lib/_tls_wrap.js#L1621\n */\n const realTlsConnectOptions = getTlsConnectOptions(this.socket)\n\n if (realTlsConnectOptions) {\n realTlsConnectOptions.checkServerIdentity = () => {\n return undefined\n }\n }\n\n handle.getCipher = () => {\n return {\n name: 'TLS_AES_256_GCM_SHA384',\n standardName: 'TLS_AES_256_GCM_SHA384',\n version: 'TLSv1.3',\n }\n }\n\n /**\n * Mock this to prevent a segfault on Node.js 26+. The native\n * implementation reads the negotiated group (\"SSL_get0_group_name\")\n * of a handshake that never happened. Node.js itself calls this\n * in \"onConnectSecure\" to validate the \"minDHSize\" option.\n * Reflect the ephemeral key exchange matching the mocked cipher.\n * @see https://github.com/nodejs/node/blob/3178a762d6a2b1a37b74f02266eea0f3d86603f1/lib/_tls_wrap.js#L1636\n */\n handle.getEphemeralKeyInfo = () => {\n return {\n type: 'ECDH',\n name: 'X25519',\n size: 253,\n }\n }\n\n const requestedAlpnProtocols = this.#tlsConnectionOptions?.ALPNProtocols\n\n if (\n Array.isArray(requestedAlpnProtocols) &&\n requestedAlpnProtocols.length > 0\n ) {\n const [preferredProtocol] = requestedAlpnProtocols\n\n /**\n * @note Reflect the client's preferred ALPN protocol as the\n * negotiated one. The mocked server accepts whatever the\n * client prefers.\n */\n handle.getALPNNegotiatedProtocol = () => {\n return typeof preferredProtocol === 'string' ? preferredProtocol : false\n }\n }\n\n this.socket.once('connect', () => {\n /**\n * @note A TLS 1.3 handshake derives five secrets, each reported\n * via a separate \"keylog\" event before the handshake completes.\n * Reflect them with mocked key material matching the mocked\n * cipher (SHA-384 secrets; the format is \"LABEL <client random>\n * <secret>\", each value hex-encoded).\n */\n const mockedClientRandom = '0'.repeat(64)\n const mockedSecret = '0'.repeat(96)\n const keylogLabels = [\n 'SERVER_HANDSHAKE_TRAFFIC_SECRET',\n 'EXPORTER_SECRET',\n 'SERVER_TRAFFIC_SECRET_0',\n 'CLIENT_HANDSHAKE_TRAFFIC_SECRET',\n 'CLIENT_TRAFFIC_SECRET_0',\n ]\n\n for (const keylogLabel of keylogLabels) {\n this.socket.emit(\n 'keylog',\n Buffer.from(`${keylogLabel} ${mockedClientRandom} ${mockedSecret}\\n`)\n )\n }\n\n handle.onhandshakedone()\n\n /**\n * @note A TLS 1.3 server issues two session tickets by default,\n * each emitting a separate \"session\" event on the client.\n */\n handle.onnewsession(1, Buffer.from('mocked session'))\n handle.onnewsession(2, Buffer.from('mocked session'))\n })\n\n super.claim()\n }\n\n public passthrough(\n flushPendingData?: FlushPendingDataFunction\n ): tls.TLSSocket {\n const realSocket = super.passthrough(flushPendingData) as tls.TLSSocket\n\n /**\n * @note Remove the internal \"connect\" listener added by the TLS socket.\n * Normally, that listener manages the SSL handshake. But since we're in passthrough,\n * we delegate that to the real socket. Leaving the listener on the mock socket while\n * inheriting the real socket's handle will result in the handshake performed twice, which is a no-op.\n * @see https://github.com/nodejs/node/blob/abddfc921bf2af02a04a6a5d2bca8e2d91d80958/lib/internal/tls/wrap.js#L1105\n *\n * This prevents the following error:\n * # node (vitest 4)[8686]: static void node::crypto::TLSWrap::Start(const FunctionCallbackInfo<Value> &) at ../src/crypto/crypto_tls.cc:589\n # Assertion failed: !wrap->started_\n */\n for (const connectListener of this.socket.listeners('connect')) {\n if (\n connectListener === this.socket._start ||\n ('listener' in connectListener &&\n connectListener.listener === this.socket._start)\n ) {\n this.socket.removeListener('connect', connectListener as () => void)\n }\n }\n\n realSocket\n .on('secure', () => {\n this.socket.emit('secure')\n })\n .on('session', (...args) => {\n this.socket.emit('session', ...args)\n })\n .on('keylog', (...args) => {\n this.socket.emit('keylog', ...args)\n })\n .on('OCSPResponse', (...args) => {\n this.socket.emit('OCSPResponse', ...args)\n })\n\n return realSocket\n }\n}\n","import tls from 'node:tls'\nimport {\n type NetConnectArgs,\n type NetworkConnectionOptions,\n normalizeNetConnectArgs,\n} from './normalize-net-connect-args'\n\ntype TlsConnectArgs =\n | []\n | [options: tls.ConnectionOptions, callback?: () => void]\n | [url: URL, callback?: () => void]\n | [port: number, options?: tls.ConnectionOptions, callback?: () => void]\n | [\n port: number,\n host?: string,\n options?: tls.ConnectionOptions,\n callback?: () => void,\n ]\n\nexport type TlsConnectionOptions = tls.ConnectionOptions &\n NetworkConnectionOptions\n\ntype NormalizedTlsConnectionArgs = [\n options: TlsConnectionOptions,\n callback?: () => void,\n]\n\nexport function normalizeTlsConnectArgs(\n args: TlsConnectArgs\n): NormalizedTlsConnectionArgs {\n /**\n * @note Despite incorrect type definitions, \"tls.connect()\" has all the\n * options of \"net.connect()\" and then those specific to TLS connections,\n * like \"session\" or \"socket\".\n * @see https://github.com/nodejs/node/blob/bdc8131fa78089b81b74dbff467365afb6536e6a/lib/internal/tls/wrap.js#L1615\n */\n const netConnectArgs = normalizeNetConnectArgs(args as NetConnectArgs)\n const options = netConnectArgs[0] as TlsConnectionOptions\n const callback = netConnectArgs[1]\n\n if (args[0] !== null && typeof args[0] === 'object') {\n Object.assign(options, args[0])\n } else if (args[1] !== null && typeof args[1] === 'object') {\n Object.assign(options, args[1])\n } else if (args[2] !== null && typeof args[2] === 'object') {\n Object.assign(options, args[2])\n }\n\n return callback ? [options, callback] : [options]\n}\n","import net from 'node:net'\nimport tls from 'node:tls'\nimport http from 'node:http'\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport { TypedEvent } from 'rettime'\nimport {\n type NetworkConnectionOptions,\n normalizeNetConnectArgs,\n} from './utils/normalize-net-connect-args'\nimport {\n kPatched,\n TcpSocketController,\n TlsSocketController,\n} from './socket-controller'\nimport { normalizeTlsConnectArgs } from './utils/normalize-tls-connect-args'\nimport { getTlsConnectOptions } from './utils/get-tls-connect-options'\nimport { createLogger } from '../../utils/logger'\nimport { patchesRegistry } from '../../utils/patches-registry'\nimport { Interceptor } from '#/src/interceptor'\nimport '../../utils/internal-connection'\n\n/**\n * @note Initialize once across entry points and package copies. The runner\n * is inert without the socket patch, so it needs no disposal lifecycle.\n */\nglobalThis.__MSW_INTERNAL_CONNECTION_CONTEXT ??= (() => {\n const context = new AsyncLocalStorage<{ consumed: boolean }>()\n\n return {\n run<T>(callback: () => T): T {\n return context.run({ consumed: false }, callback)\n },\n consume(): boolean {\n const connection = context.getStore()\n\n if (!connection || connection.consumed) {\n return false\n }\n\n // Socket events inherit this context. Consume it before connecting\n // so requests from user event listeners remain intercepted.\n connection.consumed = true\n return true\n },\n }\n})()\n\ndeclare module 'node:http' {\n interface Agent {\n /**\n * @note An undocumented method backing every agent-driven request\n * (see \"#stopReusingUnpatchedSockets\").\n */\n addRequest?: (request: http.ClientRequest, ...args: Array<unknown>) => void\n }\n}\n\ninterface SocketConnectionEventData {\n socket: net.Socket | tls.TLSSocket\n connectionOptions: NetworkConnectionOptions\n controller: TcpSocketController | TlsSocketController\n}\n\nclass SocketConnectionEvent<\n DataType extends SocketConnectionEventData = SocketConnectionEventData,\n> extends TypedEvent<DataType, void, 'connection'> {\n public socket: net.Socket | tls.TLSSocket\n public connectionOptions: NetworkConnectionOptions\n public controller: TcpSocketController | TlsSocketController\n\n constructor(data: DataType) {\n super(...(['connection', {}] as any))\n\n this.socket = data.socket\n this.connectionOptions = data.connectionOptions\n this.controller = data.controller\n }\n}\n\ntype SocketEventMap = {\n connection: SocketConnectionEvent\n}\n\nconst logger = createLogger('socket')\n\n/**\n * A DNS lookup function for intercepted sockets. It always succeeds,\n * resolving any hostname to the loopback address. This ensures the\n * \"lookup\"/\"connectionAttempt\" socket events fire even for non-existent\n * hosts, and no real DNS resolution is performed. Passthrough\n * connections are created with the original options and use the real\n * (or the caller's custom) lookup instead.\n */\nconst mockLookup: net.LookupFunction = (hostname, dnsOptions, callback) => {\n const family = dnsOptions.family === 6 ? 6 : 4\n const address = family === 6 ? '::1' : '127.0.0.1'\n\n /**\n * @note Call back asynchronously since DNS lookup is always\n * asynchronous in Node.js. Calling back synchronously emits\n * the \"lookup\"/\"connectionAttempt\" socket events before the\n * consumer gets a chance to add listeners for them.\n */\n process.nextTick(() => {\n /**\n * @note Honor the Node.js lookup contract: the callback receives\n * an array of addresses only when the \"all\" option is set\n * (e.g. during the family autoselection). Otherwise, it receives\n * a single address and its family. Node.js rejects an array in\n * the latter case with \"ERR_INVALID_IP_ADDRESS\".\n */\n if (dnsOptions.all) {\n callback(null, [{ address, family }])\n return\n }\n\n callback(null, address, family)\n })\n}\n\n/**\n * Interceptor for `net.Socket` connections.\n */\nexport class SocketInterceptor extends Interceptor<SocketEventMap> {\n static symbol = Symbol.for('socket-interceptor')\n\n protected predicate(): boolean {\n return true\n }\n\n protected setup(): void {\n const interceptor = this\n\n /**\n * @note A synchronous re-entrancy latch for creating passthrough\n * TLS connections. The original \"tls.connect()\" constructs a TLS\n * socket and calls \"socket.connect()\" on it synchronously, and that\n * call must reach Node.js as-is instead of being intercepted again.\n */\n let isCreatingPassthroughConnection = false\n\n this.subscriptions.push(\n /**\n * @note Intercept connections at the \"net.Socket.prototype.connect\"\n * level instead of patching the \"net.connect()\" module function.\n * ESM consumers snapshot the module bindings at import time\n * (\"import * as net from 'node:net'\"), so reassigning \"net.connect\"\n * is invisible to them. Every client connection ends up calling\n * \"Socket.prototype.connect\" (including the one made by the original\n * \"net.connect()\"), and prototype mutations are visible regardless\n * of how the module was imported.\n */\n patchesRegistry.applyPatch(\n net.Socket.prototype,\n 'connect',\n (realSocketConnect) => {\n return function connect(this: net.Socket, ...args: [any, any]) {\n const socket = this\n\n /**\n * @note Skip the sockets this interceptor already controls\n * (the mock connect call below, re-connects of an intercepted\n * socket, passthrough sockets) and the sockets created while\n * dialing a passthrough TLS connection. Their connects must\n * reach Node.js as-is.\n */\n if (socket[kPatched] || isCreatingPassthroughConnection) {\n return realSocketConnect.apply(socket, args)\n }\n\n if (globalThis.__MSW_INTERNAL_CONNECTION_CONTEXT?.consume()) {\n // Internal connections must bypass every socket consumer,\n // including HTTP upgrade interception. Mark reconnects too.\n socket[kPatched] = true\n return realSocketConnect.apply(socket, args)\n }\n\n logger.verbose('socket.connect() %o', args)\n\n /**\n * @note The original \"net.connect()\"/\"tls.connect()\" normalize\n * the arguments themselves and call this method with the\n * normalized array instead of the individual arguments.\n */\n const connectArgs = (\n Array.isArray(args[0]) ? args[0] : args\n ) as typeof args\n\n const [transportConnectionOptions, connectionCallback] =\n normalizeNetConnectArgs(connectArgs)\n\n logger.verbose('connection options %o', {\n transportConnectionOptions,\n connectionCallback,\n })\n\n let controller: TcpSocketController\n let connectionOptions: NetworkConnectionOptions\n\n if (socket instanceof tls.TLSSocket) {\n /**\n * @note TLS sockets arrive here from the original\n * \"tls.connect()\", which connects the transport of the TLS\n * socket it constructs (\"connectionCallback\" is its internal\n * \"_start\" handshake trigger). The original TLS connection\n * options are recovered from the socket instance since the\n * construction has already happened.\n */\n const realTlsConnectionOptions = getTlsConnectOptions(socket)\n const [tlsConnectionOptions] = normalizeTlsConnectArgs([\n {\n ...transportConnectionOptions,\n ...realTlsConnectionOptions,\n } as tls.ConnectionOptions,\n ])\n\n connectionOptions = tlsConnectionOptions\n controller = new TlsSocketController(\n socket,\n () => {\n /**\n * @note Create the passthrough connection via the original\n * \"tls.connect()\" with the effective connection options\n * (the real DNS lookup and the caller's certificate\n * validation included). The latch exempts the transport\n * connect of that connection from interception.\n */\n isCreatingPassthroughConnection = true\n\n try {\n return tls.connect(tlsConnectionOptions)\n } finally {\n isCreatingPassthroughConnection = false\n }\n },\n tlsConnectionOptions\n )\n } else {\n /**\n * @note Create passthrough connections without the connection\n * callback. The callback is already registered as a \"connect\"\n * listener on the consumer's socket. Passing it to the\n * passthrough connection would invoke it twice.\n */\n const passthroughArgs = connectArgs.filter((arg) => {\n return typeof arg !== 'function'\n }) as typeof args\n\n /**\n * @note Create the passthrough socket with the original\n * options, the same way \"net.connect()\" does. Connect it via\n * the unpatched method so the passthrough connection is not\n * intercepted again.\n */\n const socketOptions =\n connectArgs[0] !== null &&\n typeof connectArgs[0] === 'object' &&\n !('href' in connectArgs[0])\n ? connectArgs[0]\n : {}\n\n connectionOptions = transportConnectionOptions\n controller = new TcpSocketController(socket, () => {\n const passthroughSocket = new net.Socket(socketOptions)\n Reflect.apply(\n realSocketConnect,\n passthroughSocket,\n passthroughArgs\n )\n return passthroughSocket\n })\n }\n\n process.nextTick(() => {\n if (socket.destroyed) {\n return\n }\n\n /**\n * @note Expect a verdict on this connection from every\n * \"connection\" listener before emitting the event. With no\n * listeners to claim the connection (or once every listener\n * declines it), the controller passes it through as-is.\n */\n controller.awaitVerdicts(interceptor.listenerCount('connection'))\n\n interceptor.emitter.emit(\n new SocketConnectionEvent({\n socket: controller.serverSocket,\n controller,\n connectionOptions,\n })\n )\n\n logger.verbose('emitted \"connection\" event!')\n })\n\n logger.verbose('connecting the socket...')\n\n /**\n * @note The requested local address/port are stripped from the\n * actual \"socket.connect()\" call by the controller to prevent\n * binding the intercepted socket (see the \"connect\" proxy).\n */\n const mockConnectionOptions = {\n ...transportConnectionOptions,\n }\n\n // Patch the lookup option so DNS lookup always succeeds.\n mockConnectionOptions.lookup = mockLookup\n\n try {\n /**\n * @note The normalized options are looser than the declared\n * \"SocketConnectOpts\" (e.g. the port may be a string when a\n * URL is passed). Node.js validates them at runtime.\n * This call goes through the controller's \"connect\" proxy\n * and lands back in this patch, where the \"kPatched\" check\n * above delegates it to the unpatched method.\n */\n return socket.connect(\n mockConnectionOptions as net.SocketConnectOpts,\n connectionCallback ?? undefined\n )\n } catch (error) {\n /**\n * @note \"socket.connect()\" can throw synchronously on invalid\n * input (e.g. a bad port). Destroy the socket so the pending\n * interception tick does not act on it, then let the error\n * propagate to the consumer like in Node.js.\n */\n socket.destroy()\n throw error\n }\n }\n }\n ),\n this.#stopReusingUnpatchedSockets()\n )\n\n /**\n * @note \"net.connect()\"/\"net.createConnection()\" need no patching of\n * their own: both construct a \"net.Socket\" and call the patched\n * \"Socket.prototype.connect\" on it.\n */\n }\n\n /**\n * Prevent the `net.Socket` instances created before this interceptor\n * was applied from getting reused by an `Agent`. Purging them from the\n * keep-alive pool forces the agent to establish new (intercepted)\n * connections instead.\n */\n #stopReusingUnpatchedSockets(): () => void {\n /**\n * @note \"Agent.prototype.addRequest\" is undocumented but stable:\n * its signature changed once (the keep-alive Agent rewrite in\n * Node.js 0.12), and the ecosystem (agent-base, agentkeepalive,\n * APM wrappers) has relied on it ever since. \"https.Agent\"\n * inherits it, so a single patch covers both.\n */\n if (typeof http.Agent.prototype.addRequest !== 'function') {\n return () => {}\n }\n\n return patchesRegistry.applyPatch(\n http.Agent.prototype,\n 'addRequest',\n (realAddRequest) => {\n return function (this: http.Agent, ...args) {\n /**\n * @note Destroy the free sockets created before the interceptor\n * was applied. Destroying flips their \"destroyed\" state\n * synchronously, so the original \"addRequest\" below discards\n * them and dials a new (intercepted) connection instead.\n * The \"freeSockets\" pool only contains idle sockets by\n * definition, so destroying them aborts nothing in-flight.\n */\n for (const sockets of Object.values(this.freeSockets)) {\n if (sockets == null) {\n continue\n }\n\n for (const socket of sockets) {\n if (!socket[kPatched]) {\n socket.destroy()\n }\n }\n }\n\n return realAddRequest?.apply(this, args)\n }\n }\n )\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { invariant } from "outvariant";
|
|
2
|
+
//#region src/utils/patches-registry.ts
|
|
3
|
+
var PatchesRegistry = class {
|
|
4
|
+
#replacements = /* @__PURE__ */ new Map();
|
|
5
|
+
applyPatch(owner, key, getNextValue) {
|
|
6
|
+
const ownerReplacements = this.#replacements.get(owner);
|
|
7
|
+
invariant(!ownerReplacements?.has(key), `Failed to replace a global value at "${String(key)}": already replaced.`);
|
|
8
|
+
const match = getDeepPropertyDescriptor(owner, key);
|
|
9
|
+
if (typeof match === "undefined") {
|
|
10
|
+
console.warn(`Failed to replace a global value at "${String(key)}": not a global value.`);
|
|
11
|
+
return () => {};
|
|
12
|
+
}
|
|
13
|
+
if (match.descriptor.configurable) Object.defineProperty(owner, key, {
|
|
14
|
+
value: getNextValue(owner[key]),
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true
|
|
17
|
+
});
|
|
18
|
+
else if (match.descriptor.writable) owner[key] = getNextValue(owner[key]);
|
|
19
|
+
else throw new Error(`Failed to patch a non-configurable non-writable property "${key.toString()}"`);
|
|
20
|
+
const restorePatch = () => {
|
|
21
|
+
const currentReplacements = this.#replacements.get(owner);
|
|
22
|
+
if (!currentReplacements?.has(key)) return;
|
|
23
|
+
if (match.owner === owner)
|
|
24
|
+
/**
|
|
25
|
+
* @note Restoring non-configurable properties works as long as "writable: true"
|
|
26
|
+
* and none of the other descriptor properties except for "value" have changed.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(match.owner, key, match.descriptor);
|
|
29
|
+
else
|
|
30
|
+
/**
|
|
31
|
+
* @todo Delete the proxy property set by the registry.
|
|
32
|
+
* If the match's owner isn't the original owner, the property is likely nested in the prototype.
|
|
33
|
+
* The registry does not meddle with those, they are left intact.
|
|
34
|
+
*/
|
|
35
|
+
Reflect.deleteProperty(owner, key);
|
|
36
|
+
currentReplacements.delete(key);
|
|
37
|
+
if (currentReplacements.size === 0) this.#replacements.delete(owner);
|
|
38
|
+
};
|
|
39
|
+
if (ownerReplacements) ownerReplacements.set(key, restorePatch);
|
|
40
|
+
else this.#replacements.set(owner, /* @__PURE__ */ new Map([[key, restorePatch]]));
|
|
41
|
+
return restorePatch;
|
|
42
|
+
}
|
|
43
|
+
restoreAllPatches() {
|
|
44
|
+
const errors = [];
|
|
45
|
+
for (const [, ownerReplacements] of this.#replacements) for (const [, restorePatch] of ownerReplacements) try {
|
|
46
|
+
restorePatch();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof Error) errors.push(error);
|
|
49
|
+
else throw error;
|
|
50
|
+
}
|
|
51
|
+
if (errors.length > 0) throw new AggregateError(errors, "FOO!");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const patchesRegistry = new PatchesRegistry();
|
|
55
|
+
/**
|
|
56
|
+
* Returns a property descriptor for the given property on the owner.
|
|
57
|
+
* Walks down the prototype chain if the property does not exist on the owner.
|
|
58
|
+
* Handy for getting a global property descriptor where `globalThis` is
|
|
59
|
+
* replaced with a controlled class (e.g. ServiceWorkerGlobalScope).
|
|
60
|
+
*/
|
|
61
|
+
function getDeepPropertyDescriptor(owner, key) {
|
|
62
|
+
let currentOwner = owner;
|
|
63
|
+
let descriptor;
|
|
64
|
+
while (currentOwner) {
|
|
65
|
+
descriptor = Object.getOwnPropertyDescriptor(currentOwner, key);
|
|
66
|
+
if (descriptor) return {
|
|
67
|
+
owner: currentOwner,
|
|
68
|
+
descriptor
|
|
69
|
+
};
|
|
70
|
+
currentOwner = Object.getPrototypeOf(currentOwner);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { patchesRegistry as n, getDeepPropertyDescriptor as t };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=patches-registry-DxR5TEc-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patches-registry-DxR5TEc-.js","names":["#replacements"],"sources":["../../src/utils/patches-registry.ts"],"sourcesContent":["import { invariant } from 'outvariant'\n\nclass PatchesRegistry {\n #replacements = new Map<object, Map<PropertyKey, () => void>>()\n\n public applyPatch<Owner extends object, K extends keyof Owner>(\n owner: Owner,\n key: K,\n getNextValue: (realValue: Owner[K]) => Owner[K]\n ): () => void {\n const ownerReplacements = this.#replacements.get(owner)\n\n invariant(\n !ownerReplacements?.has(key),\n `Failed to replace a global value at \"${String(key)}\": already replaced.`\n )\n\n const match = getDeepPropertyDescriptor(owner, key)\n\n if (typeof match === 'undefined') {\n console.warn(\n `Failed to replace a global value at \"${String(key)}\": not a global value.`\n )\n return () => {}\n }\n\n if (match.descriptor.configurable) {\n Object.defineProperty(owner, key, {\n value: getNextValue(owner[key]),\n enumerable: true,\n configurable: true,\n })\n } else if (match.descriptor.writable) {\n owner[key] = getNextValue(owner[key])\n } else {\n throw new Error(\n `Failed to patch a non-configurable non-writable property \"${key.toString()}\"`\n )\n }\n\n const restorePatch = () => {\n const currentReplacements = this.#replacements.get(owner)\n\n if (!currentReplacements?.has(key)) {\n return\n }\n\n if (match.owner === owner) {\n /**\n * @note Restoring non-configurable properties works as long as \"writable: true\"\n * and none of the other descriptor properties except for \"value\" have changed.\n */\n Object.defineProperty(match.owner, key, match.descriptor)\n } else {\n /**\n * @todo Delete the proxy property set by the registry.\n * If the match's owner isn't the original owner, the property is likely nested in the prototype.\n * The registry does not meddle with those, they are left intact.\n */\n Reflect.deleteProperty(owner, key)\n }\n\n currentReplacements.delete(key)\n\n if (currentReplacements.size === 0) {\n this.#replacements.delete(owner)\n }\n }\n\n if (ownerReplacements) {\n ownerReplacements.set(key, restorePatch)\n } else {\n this.#replacements.set(owner, new Map([[key, restorePatch]]))\n }\n\n return restorePatch\n }\n\n public restoreAllPatches(): void {\n const errors: Array<Error> = []\n\n for (const [, ownerReplacements] of this.#replacements) {\n for (const [, restorePatch] of ownerReplacements) {\n try {\n restorePatch()\n } catch (error) {\n if (error instanceof Error) {\n errors.push(error)\n } else {\n throw error\n }\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'FOO!')\n }\n }\n}\n\nexport const patchesRegistry = new PatchesRegistry()\n\ninterface DeepDescriptorMatch {\n owner: object\n descriptor: PropertyDescriptor\n}\n\n/**\n * Returns a property descriptor for the given property on the owner.\n * Walks down the prototype chain if the property does not exist on the owner.\n * Handy for getting a global property descriptor where `globalThis` is\n * replaced with a controlled class (e.g. ServiceWorkerGlobalScope).\n */\nexport function getDeepPropertyDescriptor<Owner extends object>(\n owner: Owner,\n key: PropertyKey\n): DeepDescriptorMatch | undefined {\n let currentOwner: Owner | null = owner\n let descriptor: PropertyDescriptor | undefined\n\n while (currentOwner) {\n descriptor = Object.getOwnPropertyDescriptor(currentOwner, key)\n\n if (descriptor) {\n return {\n owner: currentOwner,\n descriptor,\n }\n }\n\n currentOwner = Object.getPrototypeOf(currentOwner)\n }\n}\n"],"mappings":";;AAEA,IAAM,kBAAN,MAAsB;CACpB,gCAAgB,IAAI,IAA0C;CAE9D,WACE,OACA,KACA,cACY;EACZ,MAAM,oBAAoB,KAAKA,cAAc,IAAI,KAAK;EAEtD,UACE,CAAC,mBAAmB,IAAI,GAAG,GAC3B,wCAAwC,OAAO,GAAG,EAAE,qBACtD;EAEA,MAAM,QAAQ,0BAA0B,OAAO,GAAG;EAElD,IAAI,OAAO,UAAU,aAAa;GAChC,QAAQ,KACN,wCAAwC,OAAO,GAAG,EAAE,uBACtD;GACA,aAAa,CAAC;EAChB;EAEA,IAAI,MAAM,WAAW,cACnB,OAAO,eAAe,OAAO,KAAK;GAChC,OAAO,aAAa,MAAM,IAAI;GAC9B,YAAY;GACZ,cAAc;EAChB,CAAC;OACI,IAAI,MAAM,WAAW,UAC1B,MAAM,OAAO,aAAa,MAAM,IAAI;OAEpC,MAAM,IAAI,MACR,6DAA6D,IAAI,SAAS,EAAE,EAC9E;EAGF,MAAM,qBAAqB;GACzB,MAAM,sBAAsB,KAAKA,cAAc,IAAI,KAAK;GAExD,IAAI,CAAC,qBAAqB,IAAI,GAAG,GAC/B;GAGF,IAAI,MAAM,UAAU;;;;;GAKlB,OAAO,eAAe,MAAM,OAAO,KAAK,MAAM,UAAU;;;;;;;GAOxD,QAAQ,eAAe,OAAO,GAAG;GAGnC,oBAAoB,OAAO,GAAG;GAE9B,IAAI,oBAAoB,SAAS,GAC/B,KAAKA,cAAc,OAAO,KAAK;EAEnC;EAEA,IAAI,mBACF,kBAAkB,IAAI,KAAK,YAAY;OAEvC,KAAKA,cAAc,IAAI,uBAAO,IAAI,IAAI,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC;EAG9D,OAAO;CACT;CAEA,oBAAiC;EAC/B,MAAM,SAAuB,CAAC;EAE9B,KAAK,MAAM,GAAG,sBAAsB,KAAKA,eACvC,KAAK,MAAM,GAAG,iBAAiB,mBAC7B,IAAI;GACF,aAAa;EACf,SAAS,OAAO;GACd,IAAI,iBAAiB,OACnB,OAAO,KAAK,KAAK;QAEjB,MAAM;EAEV;EAIJ,IAAI,OAAO,SAAS,GAClB,MAAM,IAAI,eAAe,QAAQ,MAAM;CAE3C;AACF;AAEA,MAAa,kBAAkB,IAAI,gBAAgB;;;;;;;AAanD,SAAgB,0BACd,OACA,KACiC;CACjC,IAAI,eAA6B;CACjC,IAAI;CAEJ,OAAO,cAAc;EACnB,aAAa,OAAO,yBAAyB,cAAc,GAAG;EAE9D,IAAI,YACF,OAAO;GACL,OAAO;GACP;EACF;EAGF,eAAe,OAAO,eAAe,YAAY;CACnD;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as handleRequest, r as HttpResponseEvent } from "./source-
|
|
2
|
-
import {
|
|
3
|
-
import { t as BatchInterceptor } from "./batch-interceptor-
|
|
4
|
-
import { a as isResponseError,
|
|
1
|
+
import { n as handleRequest, r as HttpResponseEvent } from "./source-BFZ6wg4P.js";
|
|
2
|
+
import { n as createLogger, t as Interceptor } from "./interceptor-C8qRPjxG.js";
|
|
3
|
+
import { t as BatchInterceptor } from "./batch-interceptor-DMOfd-9x.js";
|
|
4
|
+
import { a as isResponseError, d as RequestController, n as FetchResponse, t as FetchRequest } from "./fetch-utils-Tm5LbwBe.js";
|
|
5
5
|
import { ClientRequestInterceptor } from "./interceptors/ClientRequest/index.js";
|
|
6
6
|
import { XMLHttpRequestInterceptor } from "./interceptors/XMLHttpRequest/node.js";
|
|
7
7
|
import { FetchInterceptor } from "./interceptors/fetch/node.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/utils/resolve-web-socket-url.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolve potentially relative WebSocket URLs the same way
|
|
4
|
+
* the browser does (replace the protocol, use the origin, etc).
|
|
5
|
+
*
|
|
6
|
+
* @see https://websockets.spec.whatwg.org//#dom-websocket-websocket
|
|
7
|
+
*/
|
|
8
|
+
function resolveWebSocketUrl(url) {
|
|
9
|
+
if (typeof url === "string") return resolveWebSocketUrl(new URL(url, typeof location !== "undefined" ? location.href : void 0));
|
|
10
|
+
if (url.protocol === "http:") url.protocol = "ws:";
|
|
11
|
+
else if (url.protocol === "https:") url.protocol = "wss:";
|
|
12
|
+
if (url.protocol !== "ws:" && url.protocol !== "wss:")
|
|
13
|
+
/**
|
|
14
|
+
* @note These errors are modeled after the browser errors.
|
|
15
|
+
* The exact error messages aren't provided in the specification.
|
|
16
|
+
* Node.js uses more obscure error messages that I don't wish to replicate.
|
|
17
|
+
*/
|
|
18
|
+
throw new SyntaxError(`Failed to construct 'WebSocket': The URL's scheme must be either 'http', 'https', 'ws', or 'wss'. '${url.protocol}' is not allowed.`);
|
|
19
|
+
if (url.hash !== "") throw new SyntaxError(`Failed to construct 'WebSocket': The URL contains a fragment identifier ('${url.hash}'). Fragment identifiers are not allowed in WebSocket URLs.`);
|
|
20
|
+
return url.href;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { resolveWebSocketUrl as t };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=resolve-web-socket-url-CSvNPLGi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-web-socket-url-CSvNPLGi.js","names":[],"sources":["../../src/utils/resolve-web-socket-url.ts"],"sourcesContent":["/**\n * Resolve potentially relative WebSocket URLs the same way\n * the browser does (replace the protocol, use the origin, etc).\n *\n * @see https://websockets.spec.whatwg.org//#dom-websocket-websocket\n */\nexport function resolveWebSocketUrl(url: string | URL): string {\n if (typeof url === 'string') {\n /**\n * @note Cast the string to a URL first so the parsing errors\n * are thrown as a part of the WebSocket constructor, not consumers.\n */\n const urlRecord = new URL(\n url,\n typeof location !== 'undefined' ? location.href : undefined\n )\n\n return resolveWebSocketUrl(urlRecord)\n }\n\n if (url.protocol === 'http:') {\n url.protocol = 'ws:'\n } else if (url.protocol === 'https:') {\n url.protocol = 'wss:'\n }\n\n if (url.protocol !== 'ws:' && url.protocol !== 'wss:') {\n /**\n * @note These errors are modeled after the browser errors.\n * The exact error messages aren't provided in the specification.\n * Node.js uses more obscure error messages that I don't wish to replicate.\n */\n throw new SyntaxError(\n `Failed to construct 'WebSocket': The URL's scheme must be either 'http', 'https', 'ws', or 'wss'. '${url.protocol}' is not allowed.`\n )\n }\n\n if (url.hash !== '') {\n throw new SyntaxError(\n `Failed to construct 'WebSocket': The URL contains a fragment identifier ('${url.hash}'). Fragment identifiers are not allowed in WebSocket URLs.`\n )\n }\n\n return url.href\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,oBAAoB,KAA2B;CAC7D,IAAI,OAAO,QAAQ,UAUjB,OAAO,oBAAoB,IALL,IACpB,KACA,OAAO,aAAa,cAAc,SAAS,OAAO,KAAA,CAGjB,CAAC;CAGtC,IAAI,IAAI,aAAa,SACnB,IAAI,WAAW;MACV,IAAI,IAAI,aAAa,UAC1B,IAAI,WAAW;CAGjB,IAAI,IAAI,aAAa,SAAS,IAAI,aAAa;;;;;;CAM7C,MAAM,IAAI,YACR,sGAAsG,IAAI,SAAS,kBACrH;CAGF,IAAI,IAAI,SAAS,IACf,MAAM,IAAI,YACR,6EAA6E,IAAI,KAAK,4DACxF;CAGF,OAAO,IAAI;AACb"}
|
|
@@ -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 RequestController, f as InterceptorError, l as getRawFetchHeaders, n as FetchResponse, o as isResponseLike, r as createServerErrorResponse, s as kErrorResponse, t as FetchRequest, u as recordRawFetchHeaders } from "./fetch-utils-Tm5LbwBe.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-DkiHxhQF.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
|
};
|
|
@@ -1590,6 +1605,14 @@ var NodeHttpRequestSource = class extends Interceptor {
|
|
|
1590
1605
|
let requestParser;
|
|
1591
1606
|
let tunnelUrl;
|
|
1592
1607
|
let abortPendingRequest;
|
|
1608
|
+
let pendingRequestController;
|
|
1609
|
+
const stopParsingRequests = (error) => {
|
|
1610
|
+
httpLogger.verbose("stopping HTTP request parsing: %o", error);
|
|
1611
|
+
isHttpConnection = false;
|
|
1612
|
+
if (pendingRequestController?.readyState === RequestController.PENDING) pendingRequestController.passthrough();
|
|
1613
|
+
else socketController.decline();
|
|
1614
|
+
requestParser?.free(error);
|
|
1615
|
+
};
|
|
1593
1616
|
/**
|
|
1594
1617
|
* @note Capture the request context of the connection itself.
|
|
1595
1618
|
* The socket is created synchronously within the request async
|
|
@@ -1618,7 +1641,10 @@ var NodeHttpRequestSource = class extends Interceptor {
|
|
|
1618
1641
|
* A single socket cannot be used for different protocols.
|
|
1619
1642
|
*/
|
|
1620
1643
|
socket.on("data", (chunk) => {
|
|
1621
|
-
if (isHttpConnection === false)
|
|
1644
|
+
if (isHttpConnection === false) {
|
|
1645
|
+
socketController.decline();
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1622
1648
|
/**
|
|
1623
1649
|
* @note A mocked "CONNECT" request has established a tunnel.
|
|
1624
1650
|
* The data that follows belongs to a new exchange addressed to
|
|
@@ -1665,6 +1691,7 @@ var NodeHttpRequestSource = class extends Interceptor {
|
|
|
1665
1691
|
const requestContextValue = requestContext.getStore() ?? connectionRequestContext;
|
|
1666
1692
|
const initiator = requestContextValue?.initiator || socket;
|
|
1667
1693
|
requestParser = new HttpRequestParser({
|
|
1694
|
+
onError: stopParsingRequests,
|
|
1668
1695
|
connectionOptions: {
|
|
1669
1696
|
method: httpMethod,
|
|
1670
1697
|
url: baseUrl
|
|
@@ -1751,7 +1778,8 @@ var NodeHttpRequestSource = class extends Interceptor {
|
|
|
1751
1778
|
if (reason instanceof Error) socket.destroy(reason);
|
|
1752
1779
|
},
|
|
1753
1780
|
passthrough: () => {
|
|
1754
|
-
const realSocket = socketController.passthrough(this.#modifyHttpHeaders(context.request));
|
|
1781
|
+
const realSocket = socketController.passthrough(isHttpConnection === false ? void 0 : this.#modifyHttpHeaders(context.request));
|
|
1782
|
+
if (isHttpConnection === false) return;
|
|
1755
1783
|
if (this.emitter.listenerCount("response") > 0) {
|
|
1756
1784
|
httpLogger.verbose("found \"response\" listener, corking socket reads");
|
|
1757
1785
|
/**
|
|
@@ -1762,36 +1790,63 @@ var NodeHttpRequestSource = class extends Interceptor {
|
|
|
1762
1790
|
* emitting data for the response parser meanwhile.
|
|
1763
1791
|
*/
|
|
1764
1792
|
socketController.corkReads();
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
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");
|
|
1793
|
+
let responseParserDisposed = false;
|
|
1794
|
+
let responseComplete = false;
|
|
1795
|
+
let hasFinalResponse = false;
|
|
1796
|
+
const responseParser = new HttpResponseParser({
|
|
1797
|
+
onError: (error) => {
|
|
1798
|
+
disposeResponseParser(error);
|
|
1784
1799
|
socketController.uncorkReads();
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1800
|
+
},
|
|
1801
|
+
onMessageComplete: (status) => {
|
|
1802
|
+
responseComplete = status >= 200 || status === 101;
|
|
1803
|
+
},
|
|
1804
|
+
onResponse: async (response) => {
|
|
1805
|
+
hasFinalResponse = response.status >= 200 || response.status === 101;
|
|
1806
|
+
httpLogger.verbose("HTTP response parser parsed: %d %s", response.status, response.statusText);
|
|
1807
|
+
if (isResponseError(response)) {
|
|
1808
|
+
httpLogger.verbose("response is an error response, uncorking socket reads...");
|
|
1809
|
+
socketController.uncorkReads();
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
FetchResponse.setUrl(request.url, response);
|
|
1813
|
+
try {
|
|
1814
|
+
httpLogger.verbose("emitting \"response\" event");
|
|
1815
|
+
await this.emitter.emitAsPromise(new HttpResponseEvent({
|
|
1816
|
+
initiator,
|
|
1817
|
+
requestId,
|
|
1818
|
+
request: context.request,
|
|
1819
|
+
response,
|
|
1820
|
+
responseType: "original"
|
|
1821
|
+
}));
|
|
1822
|
+
} finally {
|
|
1823
|
+
httpLogger.verbose("uncorking socket reads");
|
|
1824
|
+
socketController.uncorkReads();
|
|
1825
|
+
/**
|
|
1826
|
+
* @note Informational responses other than
|
|
1827
|
+
* "101 Switching Protocols" are followed by a final
|
|
1828
|
+
* response on the same connection. Keep gating that
|
|
1829
|
+
* final response on the "response" event listeners.
|
|
1830
|
+
*/
|
|
1831
|
+
if (!responseParserDisposed && response.status < 200 && response.status !== 101) socketController.corkReads();
|
|
1832
|
+
}
|
|
1792
1833
|
}
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1834
|
+
});
|
|
1835
|
+
const onResponseData = (chunk) => {
|
|
1836
|
+
responseParser.execute(chunk);
|
|
1837
|
+
if (responseComplete) disposeResponseParser();
|
|
1838
|
+
};
|
|
1839
|
+
const onResponseClose = () => {
|
|
1840
|
+
disposeResponseParser();
|
|
1841
|
+
if (!hasFinalResponse) socketController.uncorkReads();
|
|
1842
|
+
};
|
|
1843
|
+
const disposeResponseParser = (error) => {
|
|
1844
|
+
responseParserDisposed = true;
|
|
1845
|
+
realSocket.removeListener("data", onResponseData);
|
|
1846
|
+
realSocket.removeListener("close", onResponseClose);
|
|
1847
|
+
responseParser.free(error);
|
|
1848
|
+
};
|
|
1849
|
+
realSocket.on("data", onResponseData).once("close", onResponseClose);
|
|
1795
1850
|
}
|
|
1796
1851
|
}
|
|
1797
1852
|
}, {
|
|
@@ -1822,9 +1877,11 @@ var NodeHttpRequestSource = class extends Interceptor {
|
|
|
1822
1877
|
abortPendingRequest = () => {
|
|
1823
1878
|
if (requestController.readyState === RequestController.PENDING) requestAbortController.abort();
|
|
1824
1879
|
};
|
|
1880
|
+
pendingRequestController = requestController;
|
|
1825
1881
|
try {
|
|
1826
1882
|
await handleRequest(context);
|
|
1827
1883
|
} finally {
|
|
1884
|
+
pendingRequestController = void 0;
|
|
1828
1885
|
abortPendingRequest = void 0;
|
|
1829
1886
|
}
|
|
1830
1887
|
}
|
|
@@ -2019,4 +2076,4 @@ var NodeHttpRequestSource = class extends Interceptor {
|
|
|
2019
2076
|
//#endregion
|
|
2020
2077
|
export { requestContext as a, forwardHttpEvents as i, handleRequest as n, runInRequestContext as o, HttpResponseEvent as r, NodeHttpRequestSource as t };
|
|
2021
2078
|
|
|
2022
|
-
//# sourceMappingURL=source-
|
|
2079
|
+
//# sourceMappingURL=source-BFZ6wg4P.js.map
|