diff --git a/src/index.ts b/src/index.ts index b32619c..1ec38bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -73,19 +73,34 @@ export class Socket { allowHalfOpen: this.allowHalfOpen, }; if (this.secureTransport === 'on') { - this.socket = tls.connect(connectOptions); + this.socket = tls.connect({ + ...connectOptions, + ALPNProtocols: options?.alpn, + servername: options?.sni, + }); } else { this.socket = net.connect(connectOptions); } } else { - this.socket = new tls.TLSSocket(addressOrSocket); + this.socket = new tls.TLSSocket(addressOrSocket, { + ALPNProtocols: options?.alpn, + }); } if (this.socket instanceof tls.TLSSocket) { this.socket.on('secureConnect', () => { + // Typescript doesn't deduce that it can only be TLSSocket in this scope + const tlsSocket = this.socket as tls.TLSSocket; + + let alpnProtocol: string | undefined; + if (typeof tlsSocket.alpnProtocol === 'string') { + alpnProtocol = tlsSocket.alpnProtocol; + } + this.openedResolve({ remoteAddress: this.socket.remoteAddress, localAddress: this.socket.localAddress, + alpn: alpnProtocol, }); }); } else { diff --git a/src/types.ts b/src/types.ts index 038fa2e..894537d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,6 +13,19 @@ export interface SocketOptions { * This option is similar to that offered by the Node.js net module and allows interoperability with code which utilizes it. */ allowHalfOpen?: boolean; + /** + * The Application-Layer Protocol Negotiation list to send, as an array of strings. + * If the server agrees with one of the protocols specified in this list, it will + * return the matching protocol in the info property. May be specified if and only + * if secureTransport is on or starttls. + */ + alpn?: string[]; + /** + * The Server Name Indication TLS option to send as part of the TLS handshake. + * If specified, requests that the server send a certificate with a matching + * common name. May be specified if and only if secureTransport is on or starttls. + */ + sni?: string; } export interface SocketAddress { @@ -25,4 +38,5 @@ export interface SocketAddress { export interface SocketInfo { remoteAddress?: string; localAddress?: string; + alpn?: string; } diff --git a/test/tls.test.ts b/test/tls.test.ts index f8a0872..008825c 100644 --- a/test/tls.test.ts +++ b/test/tls.test.ts @@ -5,12 +5,13 @@ import tap from 'tap'; import { SocketError, connect } from '../src'; import { listenAndGetSocketAddress, writeAndReadSocket } from './utils'; -function getTLSServer(): tls.Server { +function getTLSServer(alpnProtocols?: string[]): tls.Server { const server = tls.createServer({ key: fs.readFileSync(path.join(__dirname, '/certs/server/server.key')), cert: fs.readFileSync(path.join(__dirname, '/certs/server/server.crt')), ca: fs.readFileSync(path.join(__dirname, '/certs/ca/ca.crt')), rejectUnauthorized: false, + ALPNProtocols: alpnProtocols }); return server; @@ -59,6 +60,30 @@ void tap.test('Socket `connect` with TLS', (t) => { t.end(); }); + void t.test('can get correct alpn protocol', async (t) => { + const server = getTLSServer(['h2c']); + const address = await listenAndGetSocketAddress(server); + const socket = connect( + address, + { + secureTransport: 'on', + alpn: ['h2c'] + } + ); + t.throws( + () => { + socket.startTls(); + }, + new SocketError("secureTransport must be set to 'starttls'"), + 'calling .startTls() throws an error', + ); + const socketInfo = await socket.opened; + t.equal(socketInfo.alpn, 'h2c'); + await socket.close(); + server.close(); + t.end(); + }); + t.end(); });