diff --git a/ext/node/polyfills/https.ts b/ext/node/polyfills/https.ts index 2902134c90ed4c..bd1ed321ed084b 100644 --- a/ext/node/polyfills/https.ts +++ b/ext/node/polyfills/https.ts @@ -107,176 +107,191 @@ export function get(...args: any[]) { return req; } -export class Agent extends HttpAgent { - declare maxCachedSessions: number; - declare _sessionCache: { map: Record; list: string[] }; - - constructor(options: any) { - options = { __proto__: null, ...options }; - options.defaultPort ??= 443; - options.protocol ??= "https:"; - super(options); - - this.maxCachedSessions = this.options.maxCachedSessions; - if (this.maxCachedSessions === undefined) { - this.maxCachedSessions = 100; - } - - this._sessionCache = { - map: {}, - list: [], - }; +// Defined as a regular function (not a `class`) so that `https.Agent()` may be +// invoked without `new`, matching Node: +// https://github.com/nodejs/node/blob/main/lib/https.js +export function Agent(this: any, options: any) { + if (!(this instanceof Agent)) { + return new (Agent as any)(options); } - getName(options: any = {}) { - let name = super.getName(options); + options = { __proto__: null, ...options }; + options.defaultPort ??= 443; + options.protocol ??= "https:"; + HttpAgent.call(this, options); - name += ":"; - if (options.ca) name += options.ca; + this.maxCachedSessions = this.options.maxCachedSessions; + if (this.maxCachedSessions === undefined) { + this.maxCachedSessions = 100; + } - name += ":"; - if (options.cert) name += options.cert; + this._sessionCache = { + map: {}, + list: [], + }; +} +Object.setPrototypeOf(Agent.prototype, HttpAgent.prototype); +Object.setPrototypeOf(Agent, HttpAgent); - name += ":"; - if (options.clientCertEngine) name += options.clientCertEngine; +Agent.prototype.getName = function getName(this: any, options: any = {}) { + let name = HttpAgent.prototype.getName.call(this, options); - name += ":"; - if (options.ciphers) name += options.ciphers; + name += ":"; + if (options.ca) name += options.ca; - name += ":"; - if (options.key) name += options.key; + name += ":"; + if (options.cert) name += options.cert; - name += ":"; - if (options.pfx) name += options.pfx; + name += ":"; + if (options.clientCertEngine) name += options.clientCertEngine; - name += ":"; - if (options.rejectUnauthorized !== undefined) { - name += options.rejectUnauthorized; - } + name += ":"; + if (options.ciphers) name += options.ciphers; - name += ":"; - if (options.servername && options.servername !== options.host) { - name += options.servername; - } + name += ":"; + if (options.key) name += options.key; - name += ":"; - if (options.minVersion) name += options.minVersion; + name += ":"; + if (options.pfx) name += options.pfx; - name += ":"; - if (options.maxVersion) name += options.maxVersion; + name += ":"; + if (options.rejectUnauthorized !== undefined) { + name += options.rejectUnauthorized; + } - name += ":"; - if (options.secureProtocol) name += options.secureProtocol; + name += ":"; + if (options.servername && options.servername !== options.host) { + name += options.servername; + } - name += ":"; - if (options.crl) name += options.crl; + name += ":"; + if (options.minVersion) name += options.minVersion; - name += ":"; - if (options.honorCipherOrder !== undefined) { - name += options.honorCipherOrder; - } + name += ":"; + if (options.maxVersion) name += options.maxVersion; + + name += ":"; + if (options.secureProtocol) name += options.secureProtocol; - name += ":"; - if (options.ecdhCurve) name += options.ecdhCurve; + name += ":"; + if (options.crl) name += options.crl; - name += ":"; - if (options.dhparam) name += options.dhparam; + name += ":"; + if (options.honorCipherOrder !== undefined) { + name += options.honorCipherOrder; + } - name += ":"; - if (options.secureOptions !== undefined) name += options.secureOptions; + name += ":"; + if (options.ecdhCurve) name += options.ecdhCurve; - name += ":"; - if (options.sessionIdContext) name += options.sessionIdContext; + name += ":"; + if (options.dhparam) name += options.dhparam; - name += ":"; - if (options.sigalgs) name += JSON.stringify(options.sigalgs); + name += ":"; + if (options.secureOptions !== undefined) name += options.secureOptions; - name += ":"; - if (options.privateKeyIdentifier) name += options.privateKeyIdentifier; + name += ":"; + if (options.sessionIdContext) name += options.sessionIdContext; - name += ":"; - if (options.privateKeyEngine) name += options.privateKeyEngine; + name += ":"; + if (options.sigalgs) name += JSON.stringify(options.sigalgs); - return name; - } + name += ":"; + if (options.privateKeyIdentifier) name += options.privateKeyIdentifier; - _getSession(key: string) { - return this._sessionCache.map[key]; - } + name += ":"; + if (options.privateKeyEngine) name += options.privateKeyEngine; - _cacheSession(key: string, session: any) { - if (this.maxCachedSessions === 0) return; + return name; +}; - if (this._sessionCache.map[key]) { - this._sessionCache.map[key] = session; - return; - } +Agent.prototype._getSession = function _getSession(this: any, key: string) { + return this._sessionCache.map[key]; +}; - if (this._sessionCache.list.length >= this.maxCachedSessions) { - const oldKey = this._sessionCache.list.shift()!; - delete this._sessionCache.map[oldKey]; - } +Agent.prototype._cacheSession = function _cacheSession( + this: any, + key: string, + session: any, +) { + if (this.maxCachedSessions === 0) return; - this._sessionCache.list.push(key); + if (this._sessionCache.map[key]) { this._sessionCache.map[key] = session; + return; } - _evictSession(key: string) { - const index = this._sessionCache.list.indexOf(key); - if (index === -1) return; - - this._sessionCache.list.splice(index, 1); - delete this._sessionCache.map[key]; + if (this._sessionCache.list.length >= this.maxCachedSessions) { + const oldKey = this._sessionCache.list.shift()!; + delete this._sessionCache.map[oldKey]; } - createConnection(options: any, cb?: any) { - if (typeof options === "number") { - // createConnection(port, host, options) signature - const args = arguments; - const opts: any = {}; - if (args[0] !== null && typeof args[0] === "object") { - Object.assign(opts, args[0]); - } else if (args[1] !== null && typeof args[1] === "object") { - Object.assign(opts, args[1]); - } else if (args[2] !== null && typeof args[2] === "object") { - Object.assign(opts, args[2]); - } - if (typeof args[0] === "number") opts.port = args[0]; - if (typeof args[1] === "string") opts.host = args[1]; - if (typeof args[args.length - 1] === "function") { - cb = args[args.length - 1]; - } - options = opts; - } - - // Look up cached TLS session for reuse - if (options._agentKey) { - const session = this._getSession(options._agentKey); - if (session) { - options = { session, ...options }; - } - } + this._sessionCache.list.push(key); + this._sessionCache.map[key] = session; +}; - const socket = tls.connect(options as any); +Agent.prototype._evictSession = function _evictSession( + this: any, + key: string, +) { + const index = this._sessionCache.list.indexOf(key); + if (index === -1) return; - // Cache session on new session event - if (options._agentKey) { - socket.on("session", (session: any) => { - this._cacheSession(options._agentKey, session); - }); + this._sessionCache.list.splice(index, 1); + delete this._sessionCache.map[key]; +}; - socket.once("close", (err: any) => { - if (err) this._evictSession(options._agentKey); - }); +Agent.prototype.createConnection = function createConnection( + this: any, + options: any, + cb?: any, +) { + if (typeof options === "number") { + // createConnection(port, host, options) signature + const args = arguments; + const opts: any = {}; + if (args[0] !== null && typeof args[0] === "object") { + Object.assign(opts, args[0]); + } else if (args[1] !== null && typeof args[1] === "object") { + Object.assign(opts, args[1]); + } else if (args[2] !== null && typeof args[2] === "object") { + Object.assign(opts, args[2]); + } + if (typeof args[0] === "number") opts.port = args[0]; + if (typeof args[1] === "string") opts.host = args[1]; + if (typeof args[args.length - 1] === "function") { + cb = args[args.length - 1]; } + options = opts; + } - if (cb) { - socket.once("secureConnect", cb); + // Look up cached TLS session for reuse + if (options._agentKey) { + const session = this._getSession(options._agentKey); + if (session) { + options = { session, ...options }; } + } + + const socket = tls.connect(options as any); - return socket; + // Cache session on new session event + if (options._agentKey) { + socket.on("session", (session: any) => { + this._cacheSession(options._agentKey, session); + }); + + socket.once("close", (err: any) => { + if (err) this._evictSession(options._agentKey); + }); } -} + + if (cb) { + socket.once("secureConnect", cb); + } + + return socket; +}; export const globalAgent = new Agent({ keepAlive: true, diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index 8ddb94991ed8a2..c270b6e64334b8 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -1757,6 +1757,7 @@ "parallel/test-http2-zero-length-header.js": {}, "parallel/test-http2-zero-length-write.js": {}, "parallel/test-https-agent-abort-controller.js": {}, + "parallel/test-https-agent-constructor.js": {}, "parallel/test-https-agent-getname.js": {}, "parallel/test-https-agent-servername.js": {}, "parallel/test-https-agent-sockets-leak.js": {},