pax_global_header 0000666 0000000 0000000 00000000064 14113111344 0014503 g ustar 00root root 0000000 0000000 52 comment=778781a0521c713ca7605fc3ba31fb8e4be37d6b
resolve-alpn-1.2.1/ 0000775 0000000 0000000 00000000000 14113111344 0014113 5 ustar 00root root 0000000 0000000 resolve-alpn-1.2.1/.editorconfig 0000664 0000000 0000000 00000000260 14113111344 0016566 0 ustar 00root root 0000000 0000000 root = 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/ 0000775 0000000 0000000 00000000000 14113111344 0015453 5 ustar 00root root 0000000 0000000 resolve-alpn-1.2.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14113111344 0017510 5 ustar 00root root 0000000 0000000 resolve-alpn-1.2.1/.github/workflows/nodejs.yml 0000664 0000000 0000000 00000001115 14113111344 0021513 0 ustar 00root root 0000000 0000000 name: 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/.gitignore 0000664 0000000 0000000 00000000064 14113111344 0016103 0 ustar 00root root 0000000 0000000 .nyc_output
node_modules
package-lock.json
coverage
resolve-alpn-1.2.1/.npmrc 0000664 0000000 0000000 00000000024 14113111344 0015227 0 ustar 00root root 0000000 0000000 package-lock=false
resolve-alpn-1.2.1/LICENSE 0000664 0000000 0000000 00000002060 14113111344 0015116 0 ustar 00root root 0000000 0000000 MIT 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.md 0000664 0000000 0000000 00000003541 14113111344 0015375 0 ustar 00root root 0000000 0000000 # `resolve-alpn`
[](https://github.com/szmarczak/resolve-alpn/actions)
[](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.js 0000664 0000000 0000000 00000001533 14113111344 0015562 0 ustar 00root root 0000000 0000000 '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.json 0000664 0000000 0000000 00000001471 14113111344 0016404 0 ustar 00root root 0000000 0000000 {
"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.js 0000664 0000000 0000000 00000007440 14113111344 0015435 0 ustar 00root root 0000000 0000000 const 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();
});