diff --git a/src/interceptors/ClientRequest/index.ts b/src/interceptors/ClientRequest/index.ts index c5dac7a3..e01a197b 100644 --- a/src/interceptors/ClientRequest/index.ts +++ b/src/interceptors/ClientRequest/index.ts @@ -16,6 +16,13 @@ import { recordRawFetchHeaders, restoreHeadersPrototype, } from './utils/recordRawHeaders' +import { types } from 'node:util' + +type MutableReqProxy = T & {original: T, updateCallbacks: (onRequest: MockHttpSocketRequestCallback, onResponse: MockHttpSocketResponseCallback) => void} + +function isMutableReqProxy(target: T): target is MutableReqProxy { + return types.isProxy(target) && 'updateCallbacks' in target && typeof target.updateCallbacks === 'function'; +} export class ClientRequestInterceptor extends Interceptor { static symbol = Symbol('client-request-interceptor') @@ -24,87 +31,74 @@ export class ClientRequestInterceptor extends Interceptor { super(ClientRequestInterceptor.symbol) } - protected setup(): void { - const { get: originalGet, request: originalRequest } = http - const { get: originalHttpsGet, request: originalHttpsRequest } = https - - const onRequest = this.onRequest.bind(this) - const onResponse = this.onResponse.bind(this) - - http.request = new Proxy(http.request, { + protected buildProxy(protocol: 'http:' | 'https:', target: T, onRequest: MockHttpSocketRequestCallback, onResponse: MockHttpSocketResponseCallback): MutableReqProxy { + return Object.assign(new Proxy(target, { apply: (target, thisArg, args: Parameters) => { const [url, options, callback] = normalizeClientRequestArgs( - 'http:', + protocol, args ) - const mockAgent = new MockAgent({ + const agentOpts = { customAgent: options.agent, onRequest, onResponse, - }) + } + const mockAgent = protocol === 'http:' ? new MockAgent(agentOpts) : new MockHttpsAgent(agentOpts) options.agent = mockAgent return Reflect.apply(target, thisArg, [url, options, callback]) }, - }) - - http.get = new Proxy(http.get, { - apply: (target, thisArg, args: Parameters) => { - const [url, options, callback] = normalizeClientRequestArgs( - 'http:', - args - ) - - const mockAgent = new MockAgent({ - customAgent: options.agent, - onRequest, - onResponse, - }) - options.agent = mockAgent - - return Reflect.apply(target, thisArg, [url, options, callback]) + }), { + updateCallbacks: (_onRequest: MockHttpSocketRequestCallback, _onResponse: MockHttpSocketResponseCallback) => { + onRequest = _onRequest + onResponse = _onResponse }, + original: target, }) + } + protected setup(): void { + const { get: httpGet, request: httpRequest } = http + const { get: httpsGet, request: httpsRequest } = https - // - // HTTPS. - // - - https.request = new Proxy(https.request, { - apply: (target, thisArg, args: Parameters) => { - const [url, options, callback] = normalizeClientRequestArgs( - 'https:', - args - ) - - const mockAgent = new MockHttpsAgent({ - customAgent: options.agent, - onRequest, - onResponse, - }) - options.agent = mockAgent + const onRequest = this.onRequest.bind(this) + const onResponse = this.onResponse.bind(this) - return Reflect.apply(target, thisArg, [url, options, callback]) - }, - }) + if (isMutableReqProxy(httpRequest)) { + httpRequest.updateCallbacks(onRequest, onResponse) + this.logger.info('found existing proxy - updating for new request handlers') + } else { + http.request = this.buildProxy('http:', httpRequest, onRequest, onResponse) + this.subscriptions.push(() => { + http.request = httpRequest + }) + } - https.get = new Proxy(https.get, { - apply: (target, thisArg, args: Parameters) => { - const [url, options, callback] = normalizeClientRequestArgs( - 'https:', - args - ) + if (isMutableReqProxy(httpGet)) { + httpGet.updateCallbacks(onRequest, onResponse) + } else { + http.get = this.buildProxy('http:', httpGet, onRequest, onResponse) + this.subscriptions.push(() => { + http.get = httpGet + }) + } - const mockAgent = new MockHttpsAgent({ - customAgent: options.agent, - onRequest, - onResponse, - }) - options.agent = mockAgent + if (isMutableReqProxy(httpsRequest)) { + httpsRequest.updateCallbacks(onRequest, onResponse) + } else { + https.request = this.buildProxy('https:', httpsRequest, onRequest, onResponse) + this.subscriptions.push(() => { + https.request = httpsRequest + }) + } - return Reflect.apply(target, thisArg, [url, options, callback]) - }, - }) + if (isMutableReqProxy(httpsGet)) { + httpsGet.updateCallbacks(onRequest, onResponse) + } else { + https.get = this.buildProxy('https:', httpsGet, onRequest, onResponse) + this.subscriptions.push(() => { + https.get = httpsGet + }) + } // Spy on `Header.prototype.set` and `Header.prototype.append` calls // and record the raw header names provided. This is to support @@ -112,12 +106,6 @@ export class ClientRequestInterceptor extends Interceptor { recordRawFetchHeaders() this.subscriptions.push(() => { - http.get = originalGet - http.request = originalRequest - - https.get = originalHttpsGet - https.request = originalHttpsRequest - restoreHeadersPrototype() }) }