pax_global_header00006660000000000000000000000064141131113440014503gustar00rootroot0000000000000052 comment=778781a0521c713ca7605fc3ba31fb8e4be37d6b resolve-alpn-1.2.1/000077500000000000000000000000001411311134400141135ustar00rootroot00000000000000resolve-alpn-1.2.1/.editorconfig000066400000000000000000000002601411311134400165660ustar00rootroot00000000000000root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 resolve-alpn-1.2.1/.github/000077500000000000000000000000001411311134400154535ustar00rootroot00000000000000resolve-alpn-1.2.1/.github/workflows/000077500000000000000000000000001411311134400175105ustar00rootroot00000000000000resolve-alpn-1.2.1/.github/workflows/nodejs.yml000066400000000000000000000011151411311134400215130ustar00rootroot00000000000000name: Node CI on: [push, pull_request] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: node-version: [12.x, 14.x, 15.x] os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v1 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - name: npm install, build, and test run: | npm install npm run build --if-present npm test - name: Codecov uses: codecov/codecov-action@v1 resolve-alpn-1.2.1/.gitignore000066400000000000000000000000641411311134400161030ustar00rootroot00000000000000.nyc_output node_modules package-lock.json coverage resolve-alpn-1.2.1/.npmrc000066400000000000000000000000241411311134400152270ustar00rootroot00000000000000package-lock=false resolve-alpn-1.2.1/LICENSE000066400000000000000000000020601411311134400151160ustar00rootroot00000000000000MIT License Copyright (c) 2018 Szymon Marczak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. resolve-alpn-1.2.1/README.md000066400000000000000000000035411411311134400153750ustar00rootroot00000000000000# `resolve-alpn` [![Node CI](https://github.com/szmarczak/resolve-alpn/workflows/Node%20CI/badge.svg)](https://github.com/szmarczak/resolve-alpn/actions) [![codecov](https://codecov.io/gh/szmarczak/resolve-alpn/branch/master/graph/badge.svg)](https://codecov.io/gh/szmarczak/resolve-alpn) ## API ### resolveALPN(options, connect = tls.connect) Returns an object with an `alpnProtocol` property. The `socket` property may be also present. ```js const result = await resolveALPN({ host: 'nghttp2.org', port: 443, ALPNProtocols: ['h2', 'http/1.1'], servername: 'nghttp2.org' }); console.log(result); // {alpnProtocol: 'h2'} ``` **Note:** While the `servername` option is not required in this case, many other servers do. It's best practice to set it anyway. **Note:** If the socket times out, the promise will resolve and `result.timeout` will be set to `true`. #### options Same as [TLS options](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback). ##### options.resolveSocket By default, the socket gets destroyed and the promise resolves.
If you set this to true, it will return the socket in a `socket` property. ```js const result = await resolveALPN({ host: 'nghttp2.org', port: 443, ALPNProtocols: ['h2', 'http/1.1'], servername: 'nghttp2.org', resolveSocket: true }); console.log(result); // {alpnProtocol: 'h2', socket: tls.TLSSocket} // Remember to destroy the socket if you don't use it! result.socket.destroy(); ``` #### connect Type: `Function | AsyncFunction`\ Default: [`tls.connect`](https://nodejs.org/dist/latest-v16.x/docs/api/tls.html#tls_tls_connect_options_callback) **Note:** No matter which function is used (synchronous or asynchronous), it **must** accept a `callback` function as a second argument. The `callback` function gets executed when the socket has successfully connected. ## License MIT resolve-alpn-1.2.1/index.js000066400000000000000000000015331411311134400155620ustar00rootroot00000000000000'use strict'; const tls = require('tls'); module.exports = (options = {}, connect = tls.connect) => new Promise((resolve, reject) => { let timeout = false; let socket; const callback = async () => { await socketPromise; socket.off('timeout', onTimeout); socket.off('error', reject); if (options.resolveSocket) { resolve({alpnProtocol: socket.alpnProtocol, socket, timeout}); if (timeout) { await Promise.resolve(); socket.emit('timeout'); } } else { socket.destroy(); resolve({alpnProtocol: socket.alpnProtocol, timeout}); } }; const onTimeout = async () => { timeout = true; callback(); }; const socketPromise = (async () => { try { socket = await connect(options, callback); socket.on('error', reject); socket.once('timeout', onTimeout); } catch (error) { reject(error); } })(); }); resolve-alpn-1.2.1/package.json000066400000000000000000000014711411311134400164040ustar00rootroot00000000000000{ "name": "resolve-alpn", "version": "1.2.1", "description": "Detects the ALPN protocol", "main": "index.js", "scripts": { "test": "xo && nyc --reporter=lcovonly --reporter=text --reporter=html ava" }, "files": [ "index.js" ], "repository": { "type": "git", "url": "git+https://github.com/szmarczak/resolve-alpn.git" }, "keywords": [ "alpn", "tls", "socket", "http2" ], "author": "Szymon Marczak", "license": "MIT", "bugs": { "url": "https://github.com/szmarczak/resolve-alpn/issues" }, "homepage": "https://github.com/szmarczak/resolve-alpn#readme", "devDependencies": { "ava": "^3.15.0", "nyc": "^15.1.0", "pem": "1.14.3", "xo": "^0.38.2" } } resolve-alpn-1.2.1/test.js000066400000000000000000000074401411311134400154350ustar00rootroot00000000000000const http2 = require('http2'); const tls = require('tls'); const {promisify} = require('util'); const test = require('ava'); const pem = require('pem'); const resolveALPN = require('.'); const createCertificate = promisify(pem.createCertificate); const createServer = async () => { const caKeys = await createCertificate({ days: 1, selfSigned: true }); const caRootKey = caKeys.serviceKey; const caRootCert = caKeys.certificate; const keys = await createCertificate({ serviceCertificate: caRootCert, serviceKey: caRootKey, serial: Date.now(), days: 500, country: '', state: '', locality: '', organization: '', organizationUnit: '', commonName: 'localhost' }); const key = keys.clientKey; const cert = keys.certificate; const s = http2.createSecureServer({cert, key, allowHTTP1: true}); s.listen = promisify(s.listen); s.close = promisify(s.close); s.options = { host: 'localhost', rejectUnauthorized: false, ALPNProtocols: ['h2'] }; s.on('listening', () => { s.options.port = s.address().port; }); return s; }; let s; test.before('setup', async () => { s = await createServer(); await s.listen(); }); test.after('cleanup', async () => { await s.close(); }); test('works', async t => { const result = await resolveALPN(s.options); t.deepEqual(result, { alpnProtocol: 'h2', timeout: false }); }); test('`resolveSocket` option', async t => { const result = await resolveALPN({ ...s.options, resolveSocket: true }); t.is(result.alpnProtocol, 'h2'); t.true(result.socket instanceof tls.TLSSocket); result.socket.destroy(); }); test('empty options', async t => { const error = await t.throwsAsync(() => resolveALPN()); const {code} = error; t.true(code === 'ECONNREFUSED' || code === 'ERR_MISSING_ARGS' || code === 'EADDRNOTAVAIL', error.stack); }); test('works with timeout', async t => { t.timeout(100); const {socket, timeout} = await resolveALPN({ host: '123.123.123.123', port: 443, ALPNProtocols: ['h2'], timeout: 1, resolveSocket: true }); await new Promise((resolve, reject) => { socket.once('error', error => { reject(error); t.fail(error); }); socket.once('timeout', resolve); }); socket.destroy(); t.true(timeout); }); test('accept custom createConnection function', async t => { const custom = Symbol('custom'); const result = await resolveALPN({ ...s.options, resolveSocket: true }, (options, callback) => { const socket = tls.connect(options, callback); socket[custom] = true; return socket; }); t.is(result.alpnProtocol, 'h2'); t.true(result.socket instanceof tls.TLSSocket); t.true(result.socket[custom]); result.socket.destroy(); }); test('async createConnection function', async t => { const custom = Symbol('custom'); const result = await resolveALPN({ ...s.options, resolveSocket: true }, async (options, callback) => { return new Promise((resolve, reject) => { const socket = tls.connect(options, callback); socket[custom] = true; socket.once('error', reject); socket.once('connect', () => { socket.off('error', reject); resolve(socket); }); }); }); t.is(result.alpnProtocol, 'h2'); t.true(result.socket instanceof tls.TLSSocket); t.true(result.socket[custom]); result.socket.destroy(); }); test('waits for promise to be resolved', async t => { const custom = Symbol('custom'); const result = await resolveALPN({ ...s.options, resolveSocket: true }, async (options, callback) => { const socket = tls.connect(options, callback); socket[custom] = true; await new Promise(resolve => { socket.once('secureConnect', resolve); }); return socket; }); t.is(result.alpnProtocol, 'h2'); t.true(result.socket instanceof tls.TLSSocket); t.true(result.socket[custom]); t.is(result.socket.listenerCount('error'), 0); result.socket.destroy(); });