diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index ce8086c821..d5a71c73e6 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -20,7 +20,7 @@ import type https from 'https'; import fs from 'fs'; import tls from 'tls'; import stream from 'stream'; -import { createSocket } from '../utils/happy-eyeballs'; +import { createSocket, createTLSSocket } from '../utils/happy-eyeballs'; import { isUnderTest, ManualPromise } from '../utils'; import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy'; import { SocksProxy } from '../common/socksProxy'; @@ -42,22 +42,21 @@ class ALPNCache { const result = new ManualPromise(); this._cache.set(cacheKey, result); result.then(success); - const socket = tls.connect({ + createTLSSocket({ host, port, servername: net.isIP(host) ? undefined : host, ALPNProtocols: ['h2', 'http/1.1'], rejectUnauthorized: false, - }); - socket.on('secureConnect', () => { - // The server may not respond with ALPN, in which case we default to http/1.1. - result.resolve(socket.alpnProtocol || 'http/1.1'); - socket.end(); - }); - socket.on('error', error => { + }).then(socket => { + socket.on('secureConnect', () => { + // The server may not respond with ALPN, in which case we default to http/1.1. + result.resolve(socket.alpnProtocol || 'http/1.1'); + socket.end(); + }); + }).catch(error => { debugLogger.log('client-certificates', `ALPN error: ${error.message}`); result.resolve('http/1.1'); - socket.end(); }); } } diff --git a/packages/playwright-core/src/utils/happy-eyeballs.ts b/packages/playwright-core/src/utils/happy-eyeballs.ts index 37d5c1b271..80663b8192 100644 --- a/packages/playwright-core/src/utils/happy-eyeballs.ts +++ b/packages/playwright-core/src/utils/happy-eyeballs.ts @@ -20,6 +20,7 @@ import * as https from 'https'; import * as net from 'net'; import * as tls from 'tls'; import { ManualPromise } from './manualPromise'; +import { assert } from './debug'; // Implementation(partial) of Happy Eyeballs 2 algorithm described in // https://www.rfc-editor.org/rfc/rfc8305 @@ -66,7 +67,41 @@ export async function createSocket(host: string, port: number): Promise void) | undefined, useTLS: boolean) { +export async function createTLSSocket(options: tls.ConnectionOptions): Promise { + return new Promise((resolve, reject) => { + assert(options.host, 'host is required'); + if (net.isIP(options.host)) { + const socket = tls.connect(options) + socket.on('connect', () => resolve(socket)); + socket.on('error', error => reject(error)); + } else { + createConnectionAsync(options, (err, socket) => { + if (err) + reject(err); + if (socket) + resolve(socket); + }, true).catch(err => reject(err)); + } + }); +} + +export async function createConnectionAsync( + options: http.ClientRequestArgs, + oncreate: ((err: Error | null, socket?: tls.TLSSocket) => void) | undefined, + useTLS: true +): Promise; + +export async function createConnectionAsync( + options: http.ClientRequestArgs, + oncreate: ((err: Error | null, socket?: net.Socket) => void) | undefined, + useTLS: false +): Promise; + +export async function createConnectionAsync( + options: http.ClientRequestArgs, + oncreate: ((err: Error | null, socket?: any) => void) | undefined, + useTLS: boolean +): Promise { const lookup = (options as any).__testHookLookup || lookupAddresses; const hostname = clientRequestArgsToHostName(options); const addresses = await lookup(hostname);