@mswjs/interceptors 0.42.0 → 0.42.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/lib/browser/create-request-id-Bk5YX1AM.js +720 -0
  2. package/lib/browser/create-request-id-Bk5YX1AM.js.map +1 -0
  3. package/lib/browser/{fetch-utils-zxA_SD66.js → fetch-utils-CUOrwEQf.js} +2 -2
  4. package/lib/browser/{fetch-utils-zxA_SD66.js.map → fetch-utils-CUOrwEQf.js.map} +1 -1
  5. package/lib/browser/{handle-request-CIOa9O-N.js → handle-request-CqVvBhcw.js} +3 -3
  6. package/lib/browser/{handle-request-CIOa9O-N.js.map → handle-request-CqVvBhcw.js.map} +1 -1
  7. package/lib/browser/index.js +2 -2
  8. package/lib/browser/interceptors/WebSocket/index.js +1 -1
  9. package/lib/browser/interceptors/XMLHttpRequest/web.js +1 -1
  10. package/lib/browser/interceptors/fetch/web.js +1 -1
  11. package/lib/browser/presets/browser.js +2 -2
  12. package/lib/browser/{web-C2oKsT3Q.js → web-BcKSQghf.js} +4 -4
  13. package/lib/browser/{web-C2oKsT3Q.js.map → web-BcKSQghf.js.map} +1 -1
  14. package/lib/browser/{web-BjVDkiX0.js → web-CdcYFjgm.js} +4 -4
  15. package/lib/browser/{web-BjVDkiX0.js.map → web-CdcYFjgm.js.map} +1 -1
  16. package/lib/node/{has-configurable-global-CJCh_g1I.js → has-configurable-global-IskkJJOL.js} +2 -2
  17. package/lib/node/{has-configurable-global-CJCh_g1I.js.map → has-configurable-global-IskkJJOL.js.map} +1 -1
  18. package/lib/node/interceptors/ClientRequest/index.js +2 -2
  19. package/lib/node/interceptors/XMLHttpRequest/node.js +3 -3
  20. package/lib/node/interceptors/fetch/node.js +3 -3
  21. package/lib/node/interceptors/http/index.js +1 -1
  22. package/lib/node/interceptors/net/index.d.ts +27 -1
  23. package/lib/node/interceptors/net/index.js +1 -1
  24. package/lib/node/{net-DtMnyEeg.js → net-9sRKnjIG.js} +115 -9
  25. package/lib/node/net-9sRKnjIG.js.map +1 -0
  26. package/lib/node/remote-http-interceptor.js +1 -1
  27. package/lib/node/{source-lP0yyEtA.js → source-BUVce4Q1.js} +17 -4
  28. package/lib/node/{source-lP0yyEtA.js.map → source-BUVce4Q1.js.map} +1 -1
  29. package/package.json +1 -1
  30. package/src/interceptors/http/source.ts +26 -3
  31. package/src/interceptors/net/index.ts +16 -15
  32. package/src/interceptors/net/socket-controller.ts +156 -2
  33. package/lib/browser/create-request-id-DlEd4GOA.js +0 -184
  34. package/lib/browser/create-request-id-DlEd4GOA.js.map +0 -1
  35. package/lib/node/net-DtMnyEeg.js.map +0 -1
@@ -381,6 +381,7 @@ var SocketController = class SocketController {
381
381
  static {
382
382
  this.PASSTHROUGH = 2;
383
383
  }
384
+ #awaitedVerdicts = 0;
384
385
  constructor(socket) {
385
386
  this[kRawSocket] = socket;
386
387
  socket[kPatched] = true;
@@ -402,9 +403,44 @@ var SocketController = class SocketController {
402
403
  invariant(this.readyState === SocketController.PENDING, "Failed to passthrough a socket connection: already handled (%s)", this.readyState);
403
404
  this.readyState = SocketController.PASSTHROUGH;
404
405
  }
406
+ /**
407
+ * Await a verdict on this connection from the given number of
408
+ * subscribers. A connection nobody awaits to inspect (or one that
409
+ * every awaited subscriber has declined) is passed through as-is.
410
+ * This makes "unclaimed after everyone declined" a state owned by
411
+ * the controller instead of the individual subscribers.
412
+ */
413
+ awaitVerdicts(count) {
414
+ this.#awaitedVerdicts = count;
415
+ if (this.#awaitedVerdicts === 0) this.passthrough();
416
+ }
417
+ /**
418
+ * Decline this socket connection. Declining means the subscriber
419
+ * has inspected the connection and will not handle it (e.g. the
420
+ * traffic is not of the protocol that subscriber implements).
421
+ * Once every awaited subscriber declines, the connection is
422
+ * passed through as-is.
423
+ */
424
+ decline() {
425
+ if (this.readyState !== SocketController.PENDING) return;
426
+ this.#awaitedVerdicts -= 1;
427
+ if (this.#awaitedVerdicts <= 0)
428
+ /**
429
+ * @note Defer the passthrough so it never transitions this
430
+ * controller in the middle of a client write. Declines are
431
+ * issued while the written data is being pushed to the server
432
+ * socket, and a synchronous transition would race the write's
433
+ * own bookkeeping (e.g. re-buffering the write after a reset
434
+ * at an exchange boundary).
435
+ */
436
+ process.nextTick(() => {
437
+ if (this.readyState === SocketController.PENDING && !this[kRawSocket].destroyed) this.passthrough();
438
+ });
439
+ }
405
440
  };
406
441
  var TcpSocketController = class extends SocketController {
407
442
  #connectionOptions;
443
+ #retargetedConnectionOptions;
408
444
  #realWriteGeneric;
409
445
  #passthroughSocket = null;
410
446
  #bufferedWrites = [];
@@ -492,8 +528,39 @@ var TcpSocketController = class extends SocketController {
492
528
  * on this socket can be handled anew. This is meant for kept-alive
493
529
  * sockets that are reused for multiple exchanges by clients that
494
530
  * don't emit the "free" event on the socket (e.g. Undici).
531
+ *
532
+ * Providing connection options retargets this connection: the
533
+ * exchanges that follow belong to the given target (e.g. the
534
+ * authority of an established "CONNECT" tunnel). An unclaimed
535
+ * exchange then passes through to that target instead of the
536
+ * originally dialed one, and a claimed exchange reports it as the
537
+ * peer. The verdict count is deliberately not re-armed: subscribers
538
+ * that declined this connection's protocol stay declined across
539
+ * the exchanges, retargeted or not.
495
540
  */
496
- reset() {
541
+ reset(connectionOptions) {
542
+ if (connectionOptions != null) {
543
+ this.#retargetedConnectionOptions = connectionOptions;
544
+ this.#connectionOptions = connectionOptions;
545
+ /**
546
+ * @note The passthrough connection to the original target, if
547
+ * any, cannot serve the retargeted exchanges. Detach its
548
+ * forwarding listeners before destroying it so its teardown is
549
+ * not mistaken for the client connection's own (e.g. its "close"
550
+ * must not close the client socket).
551
+ */
552
+ if (this.#passthroughSocket) {
553
+ this.#passthroughSocket.removeListener("connect", this.#onRealSocketConnect).removeListener("connectionAttemptFailed", this.#onRealSocketConnectionAttemptFailed).removeListener("connectionAttemptTimeout", this.#onRealSocketConnectionAttemptTimeout).removeListener("data", this.#onRealSocketData).removeListener("error", this.#onRealSocketError).removeListener("end", this.#onRealSocketEnd).removeListener("close", this.#onRealSocketClose).destroy();
554
+ this.#passthroughSocket = null;
555
+ /**
556
+ * @note The handle swapped in from the destroyed connection,
557
+ * if any, no longer carries this socket's traffic. Treat the
558
+ * socket as not swapped so the retargeted passthrough writes
559
+ * directly to the new connection until its own handle swap.
560
+ */
561
+ this.#realHandleSwapped = false;
562
+ }
563
+ }
497
564
  /**
498
565
  * @note Only settled (claimed or passed-through) sockets need a reset.
499
566
  * Resetting a pending socket again would discard the writes already
@@ -832,6 +899,16 @@ var TcpSocketController = class extends SocketController {
832
899
  this.#passthroughSocket?.resume();
833
900
  };
834
901
  /**
902
+ * Forward the client's half-close to the passthrough socket.
903
+ * The client's writable side may finish before the real handle is
904
+ * swapped in ("_final" of a connected client runs against the mock
905
+ * handle), leaving the FIN unsent. Once the handle is swapped, the
906
+ * client's own shutdown reaches the real connection natively.
907
+ */
908
+ #forwardClientFinish = () => {
909
+ if (!this.#realHandleSwapped) this.#passthroughSocket?.end();
910
+ };
911
+ /**
835
912
  * Suspend forwarding of the passthrough socket events ("data", "end", "close")
836
913
  * to the client socket. The events are buffered in order until `uncorkReads()`
837
914
  * is called. This allows the consumer to delay the delivery of the original
@@ -931,7 +1008,7 @@ var TcpSocketController = class extends SocketController {
931
1008
  super.passthrough();
932
1009
  logger$1.verbose("-> passthrough!");
933
1010
  const createRealSocket = () => {
934
- const realSocket = this.createConnection();
1011
+ const realSocket = this.#retargetedConnectionOptions ? this.#createRetargetedConnection(this.#retargetedConnectionOptions) : this.createConnection();
935
1012
  realSocket[kPatched] = true;
936
1013
  if (this.socket.timeout != null) realSocket.setTimeout(this.socket.timeout);
937
1014
  return realSocket;
@@ -964,8 +1041,34 @@ var TcpSocketController = class extends SocketController {
964
1041
  this.socket.on("drain", this.#onMockSocketDrain);
965
1042
  realSocket.removeListener("connect", this.#onRealSocketConnect).removeListener("connectionAttemptFailed", this.#onRealSocketConnectionAttemptFailed).removeListener("connectionAttemptTimeout", this.#onRealSocketConnectionAttemptTimeout).removeListener("data", this.#onRealSocketData).removeListener("error", this.#onRealSocketError).removeListener("end", this.#onRealSocketEnd).removeListener("close", this.#onRealSocketClose);
966
1043
  realSocket.once("connect", this.#onRealSocketConnect).on("connectionAttemptFailed", this.#onRealSocketConnectionAttemptFailed).on("connectionAttemptTimeout", this.#onRealSocketConnectionAttemptTimeout).on("data", this.#onRealSocketData).on("error", this.#onRealSocketError).on("end", this.#onRealSocketEnd).on("close", this.#onRealSocketClose);
1044
+ /**
1045
+ * @note Forward the client's half-close, unless the real handle
1046
+ * is already swapped in — the client's own shutdown then reaches
1047
+ * the real connection natively (see "#forwardClientFinish").
1048
+ */
1049
+ if (!this.#realHandleSwapped) if (this.socket.writableFinished) this.#forwardClientFinish();
1050
+ else {
1051
+ this.socket.removeListener("finish", this.#forwardClientFinish);
1052
+ this.socket.once("finish", this.#forwardClientFinish);
1053
+ }
967
1054
  return realSocket;
968
1055
  }
1056
+ /**
1057
+ * Create the passthrough connection to the target this controller
1058
+ * was retargeted to (see `reset()`). The original `createConnection`
1059
+ * dials the originally requested target and cannot serve retargeted
1060
+ * exchanges.
1061
+ */
1062
+ #createRetargetedConnection(connectionOptions) {
1063
+ const realSocket = new net.Socket();
1064
+ /**
1065
+ * @note Mark the socket as patched before connecting: the patched
1066
+ * "Socket.prototype.connect" exempts such sockets, establishing
1067
+ * the connection for real.
1068
+ */
1069
+ realSocket[kPatched] = true;
1070
+ return realSocket.connect(connectionOptions);
1071
+ }
969
1072
  };
970
1073
  var TlsSocketController = class extends TcpSocketController {
971
1074
  /**
@@ -1299,15 +1402,18 @@ var SocketInterceptor = class extends Interceptor {
1299
1402
  }
1300
1403
  process.nextTick(() => {
1301
1404
  if (socket.destroyed) return;
1302
- if (!interceptor.emitter.emit(new SocketConnectionEvent({
1405
+ /**
1406
+ * @note Expect a verdict on this connection from every
1407
+ * "connection" listener before emitting the event. With no
1408
+ * listeners to claim the connection (or once every listener
1409
+ * declines it), the controller passes it through as-is.
1410
+ */
1411
+ controller.awaitVerdicts(interceptor.listenerCount("connection"));
1412
+ interceptor.emitter.emit(new SocketConnectionEvent({
1303
1413
  socket: controller.serverSocket,
1304
1414
  controller,
1305
1415
  connectionOptions
1306
- }))) {
1307
- logger.verbose("no \"connection\" listeners found on the interceptor, passthrough...");
1308
- controller.passthrough();
1309
- return;
1310
- }
1416
+ }));
1311
1417
  logger.verbose("emitted \"connection\" event!");
1312
1418
  });
1313
1419
  logger.verbose("connecting the socket...");
@@ -1385,4 +1491,4 @@ var SocketInterceptor = class extends Interceptor {
1385
1491
  //#endregion
1386
1492
  export { getDeepPropertyDescriptor as a, unwrapPendingData as i, SocketController as n, patchesRegistry as o, kRawSocket as r, SocketInterceptor as t };
1387
1493
 
1388
- //# sourceMappingURL=net-DtMnyEeg.js.map
1494
+ //# sourceMappingURL=net-9sRKnjIG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"net-9sRKnjIG.js","names":["#replacements","logger","#awaitedVerdicts","#connectionOptions","#passthroughSocket","#realWriteGeneric","#bufferedWrites","#handleWrite","#clientCloseEmitted","#readsCorked","#corkedReads","#reset","#retargetedConnectionOptions","#onRealSocketConnect","#onRealSocketConnectionAttemptFailed","#onRealSocketConnectionAttemptTimeout","#onRealSocketData","#onRealSocketError","#onRealSocketEnd","#onRealSocketClose","#realHandleSwapped","#connectEmulated","#push","#acknowledgeWrite","#removeBufferedWrite","#clientEndPushed","#emitClientClose","#createRetargetedConnection","#onMockSocketDrain","#forwardClientFinish","#tlsConnectionOptions","#stopReusingUnpatchedSockets"],"sources":["../../src/utils/patches-registry.ts","../../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 { 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","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(toBuffer(finalEnd.chunk, finalEnd.encoding), finalEnd.encoding)\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 #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] = { ...args[0], localAddress: undefined, localPort: undefined }\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 #reset(): void {\n logger.verbose('resetting the socket...')\n\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 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(\n args: Parameters<net.Socket['_writeGeneric']>\n ): 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 (\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 if (!this.#clientCloseEmitted) {\n this.socket.emit('close', hadErrorOverride ?? hadError)\n }\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 this.socket.emit('close', hadError)\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 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 { 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'\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?: (\n request: http.ClientRequest,\n ...args: Array<unknown>\n ) => 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 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 original 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(\n (realTlsConnectionOptions ??\n tlsConnectionOptions) as tls.ConnectionOptions\n )\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(\n interceptor.listenerCount('connection')\n )\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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"}
@@ -1,4 +1,4 @@
1
- import { n as handleRequest, r as HttpResponseEvent } from "./source-lP0yyEtA.js";
1
+ import { n as handleRequest, r as HttpResponseEvent } from "./source-BUVce4Q1.js";
2
2
  import { a as createLogger, i as Interceptor } from "./buffer-utils-BvPY1Tc-.js";
3
3
  import { t as BatchInterceptor } from "./batch-interceptor-ByEZVih3.js";
4
4
  import { a as isResponseError, f as RequestController, n as FetchResponse, t as FetchRequest } from "./fetch-utils-Dw1PsmtX.js";
@@ -1,6 +1,6 @@
1
1
  import { a as createLogger, i as Interceptor, o as formatRequest, r as toBuffer } from "./buffer-utils-BvPY1Tc-.js";
2
2
  import { a as isResponseError, c as isObject, d as createRequestId, f as RequestController, l as getRawFetchHeaders, n as FetchResponse, o as isResponseLike, p as InterceptorError, r as createServerErrorResponse, s as kErrorResponse, t as FetchRequest, u as recordRawFetchHeaders } from "./fetch-utils-Dw1PsmtX.js";
3
- import { i as unwrapPendingData, n as SocketController, r as kRawSocket, t as SocketInterceptor } from "./net-DtMnyEeg.js";
3
+ import { i as unwrapPendingData, n as SocketController, r as kRawSocket, t as SocketInterceptor } from "./net-9sRKnjIG.js";
4
4
  import { TypedEvent } from "rettime";
5
5
  import { invariant } from "outvariant";
6
6
  import { IncomingMessage, METHODS, STATUS_CODES, ServerResponse } from "node:http";
@@ -1622,7 +1622,19 @@ var NodeHttpRequestSource = class extends Interceptor {
1622
1622
  requestParser.free();
1623
1623
  requestParser = void 0;
1624
1624
  isHttpConnection = void 0;
1625
- socketController.reset();
1625
+ /**
1626
+ * @note Retarget the connection to the tunnel authority.
1627
+ * The exchanges that follow belong to the tunnel target,
1628
+ * so an unclaimed exchange (HTTP or not) must pass through
1629
+ * to that target — not to the proxy, which never actually
1630
+ * established this tunnel — like a real established tunnel
1631
+ * relays its traffic.
1632
+ */
1633
+ socketController.reset({
1634
+ host: tunnelUrl.hostname,
1635
+ port: Number(tunnelUrl.port) || 80,
1636
+ path: null
1637
+ });
1626
1638
  }
1627
1639
  if (requestParser) {
1628
1640
  requestParser.execute(toBuffer(chunk));
@@ -1632,6 +1644,7 @@ var NodeHttpRequestSource = class extends Interceptor {
1632
1644
  const httpMethod = httpMessage.split(" ")[0] || "";
1633
1645
  if (!METHODS.includes(httpMethod.toUpperCase())) {
1634
1646
  isHttpConnection = false;
1647
+ socketController.decline();
1635
1648
  return;
1636
1649
  }
1637
1650
  isHttpConnection = true;
@@ -1926,7 +1939,7 @@ var NodeHttpRequestSource = class extends Interceptor {
1926
1939
  * agents pool and reuse for subsequent requests.
1927
1940
  */
1928
1941
  const isSelfDelimitingResponse = request.method === "HEAD" || response.headers.has("content-length") || response.headers.has("transfer-encoding") || !FetchResponse.isResponseWithBody(response.status);
1929
- if (request.method !== "CONNECT" && !isSelfDelimitingResponse) {
1942
+ if (request.method === "CONNECT" && !response.ok || request.method !== "CONNECT" && !isSelfDelimitingResponse) {
1930
1943
  /**
1931
1944
  * @note Defer the end-of-stream signal so the HTTP parser has a chance
1932
1945
  * to process already-pushed response data and fire the 'response' event
@@ -1987,4 +2000,4 @@ var NodeHttpRequestSource = class extends Interceptor {
1987
2000
  //#endregion
1988
2001
  export { requestContext as a, forwardHttpEvents as i, handleRequest as n, runInRequestContext as o, HttpResponseEvent as r, NodeHttpRequestSource as t };
1989
2002
 
1990
- //# sourceMappingURL=source-lP0yyEtA.js.map
2003
+ //# sourceMappingURL=source-BUVce4Q1.js.map