From 74f6443ccd5e8b72f6325fcb46d366b560c304ae Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sat, 7 Aug 2021 15:18:01 +0300 Subject: [PATCH 01/12] run tests on windows and macos --- .github/workflows/main.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 18531b3..4fea0bd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,10 +5,14 @@ on: jobs: test: name: Node.js ${{ matrix.node-version }} - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest node-version: - 14 - 12 From 5fda254e70ee608870ba21c3788165ab156b3be8 Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sat, 7 Aug 2021 15:21:11 +0300 Subject: [PATCH 02/12] add os to test run name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4fea0bd..64fc85b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,7 +4,7 @@ on: - pull_request jobs: test: - name: Node.js ${{ matrix.node-version }} + name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false From 9569cf7dde473d098444075a1cff41267a3795cf Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sat, 7 Aug 2021 15:29:03 +0300 Subject: [PATCH 03/12] log address and family --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index f3e2210..4eb5129 100644 --- a/index.js +++ b/index.js @@ -25,7 +25,8 @@ const getAvailablePort = options => new Promise((resolve, reject) => { server.unref(); server.on('error', reject); server.listen(options, () => { - const {port} = server.address(); + const {port, address, family} = server.address(); + console.log(port, address, family); server.close(() => { resolve(port); }); From 9d7f808ff987bc62926ee64e03491b05fbd71e6d Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sat, 7 Aug 2021 16:04:25 +0300 Subject: [PATCH 04/12] add test --- index.js | 3 +-- test.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 4eb5129..f3e2210 100644 --- a/index.js +++ b/index.js @@ -25,8 +25,7 @@ const getAvailablePort = options => new Promise((resolve, reject) => { server.unref(); server.on('error', reject); server.listen(options, () => { - const {port, address, family} = server.address(); - console.log(port, address, family); + const {port} = server.address(); server.close(() => { resolve(port); }); diff --git a/test.js b/test.js index ba4c794..9f89668 100644 --- a/test.js +++ b/test.js @@ -153,3 +153,21 @@ test('ports are locked for up to 30 seconds', async t => { t.is(port3, port); global.setInterval = setInterval; }); + +const bindPort = async ({port, host}) => { + const server = net.createServer(); + await promisify(server.listen.bind(server))({port, host}); + return server; +}; + +test('preferred ports is bound up with different hosts', async t => { + const desiredPorts = [10990, 10991, 10992, 10993]; + + await bindPort({port: desiredPorts[0]}); + await bindPort({port: desiredPorts[1], host: '::'}); + await bindPort({port: desiredPorts[2], host: '127.0.0.1'}); + + const port = await getPort({port: desiredPorts}); + + t.is(port, desiredPorts[3]); +}); From b47498284651bcaa2f53cffa2eba5f72a8401b3a Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sat, 7 Aug 2021 16:45:13 +0300 Subject: [PATCH 05/12] add fix --- index.js | 67 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index f3e2210..edf1b22 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ 'use strict'; const net = require('net'); +const os = require('os'); class Locked extends Error { constructor(port) { @@ -20,17 +21,59 @@ const releaseOldLockedPortsIntervalMs = 1000 * 15; // Lazily create interval on first use let interval; -const getAvailablePort = options => new Promise((resolve, reject) => { - const server = net.createServer(); - server.unref(); - server.on('error', reject); - server.listen(options, () => { - const {port} = server.address(); - server.close(() => { - resolve(port); +const getHosts = () => { + const interfaces = os.networkInterfaces(); + const results = []; + + for (const _interface of Object.values(interfaces)) { + for (const config of _interface) { + results.push(config.address); + } + } + + // Add undefined value, for createServer function, do not use host and default 0.0.0.0 host + return results.concat(undefined, '0.0.0.0'); +}; + +const checkAvailablePort = options => + new Promise((resolve, reject) => { + const server = net.createServer(); + server.unref(); + server.on('error', reject); + server.listen(options, () => { + const {port} = server.address(); + server.close(() => { + resolve(port); + }); }); }); -}); + +const getAvailablePort = async (options, hosts) => { + if (options.host || options.port === 0) { + return checkAvailablePort(options); + } + + let index = 0; + const hostsCopy = [...hosts]; + + while (index < hostsCopy.length) { + const host = hostsCopy[index]; + try { + await checkAvailablePort({port: options.port, host}); // eslint-disable-line no-await-in-loop + } catch (error) { + if (['EADDRNOTAVAIL', 'EINVAL'].includes(error.code)) { + hostsCopy.splice(hostsCopy.indexOf(host), 1); + continue; + } else { + throw error; + } + } + + ++index; + } + + return options.port; +}; const portCheckSequence = function * (ports) { if (ports) { @@ -59,15 +102,17 @@ module.exports = async options => { } } + const hosts = getHosts(); + for (const port of portCheckSequence(ports)) { try { - let availablePort = await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop + let availablePort = await getAvailablePort({...options, port}, hosts); // eslint-disable-line no-await-in-loop while (lockedPorts.old.has(availablePort) || lockedPorts.young.has(availablePort)) { if (port !== 0) { throw new Locked(port); } - availablePort = await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop + availablePort = await getAvailablePort({...options, port}, hosts); // eslint-disable-line no-await-in-loop } lockedPorts.young.add(availablePort); From e058a5f9e6a62fff5bffda9fba4855be9655b22b Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sat, 7 Aug 2021 17:44:11 +0300 Subject: [PATCH 06/12] fix comment, change port in test --- index.js | 3 ++- test.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index edf1b22..dded176 100644 --- a/index.js +++ b/index.js @@ -31,7 +31,8 @@ const getHosts = () => { } } - // Add undefined value, for createServer function, do not use host and default 0.0.0.0 host + // Add undefined value for createServer function to use default host, + // and default IPv4 host in case createServer defaults to IPv6. return results.concat(undefined, '0.0.0.0'); }; diff --git a/test.js b/test.js index 9f89668..59e41f1 100644 --- a/test.js +++ b/test.js @@ -164,7 +164,7 @@ test('preferred ports is bound up with different hosts', async t => { const desiredPorts = [10990, 10991, 10992, 10993]; await bindPort({port: desiredPorts[0]}); - await bindPort({port: desiredPorts[1], host: '::'}); + await bindPort({port: desiredPorts[1], host: '0.0.0.0'}); await bindPort({port: desiredPorts[2], host: '127.0.0.1'}); const port = await getPort({port: desiredPorts}); From b83b4c4f4eebceae2d438f43e2b1f6c5d3e32442 Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sun, 26 Sep 2021 16:52:18 +0300 Subject: [PATCH 07/12] rewrite loop as for-of --- index.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index dded176..a3b090a 100644 --- a/index.js +++ b/index.js @@ -54,23 +54,14 @@ const getAvailablePort = async (options, hosts) => { return checkAvailablePort(options); } - let index = 0; - const hostsCopy = [...hosts]; - - while (index < hostsCopy.length) { - const host = hostsCopy[index]; + for (const host of hosts) { try { await checkAvailablePort({port: options.port, host}); // eslint-disable-line no-await-in-loop } catch (error) { - if (['EADDRNOTAVAIL', 'EINVAL'].includes(error.code)) { - hostsCopy.splice(hostsCopy.indexOf(host), 1); - continue; - } else { + if (!['EADDRNOTAVAIL', 'EINVAL'].includes(error.code)) { throw error; } } - - ++index; } return options.port; From b18105621d8af43f8c77a83da666eb8545b3ba9e Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sun, 26 Sep 2021 16:53:42 +0300 Subject: [PATCH 08/12] rename getHosts to getLocalHosts --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index a3b090a..b5d4b0e 100644 --- a/index.js +++ b/index.js @@ -21,7 +21,7 @@ const releaseOldLockedPortsIntervalMs = 1000 * 15; // Lazily create interval on first use let interval; -const getHosts = () => { +const getLocalHosts = () => { const interfaces = os.networkInterfaces(); const results = []; @@ -94,7 +94,7 @@ module.exports = async options => { } } - const hosts = getHosts(); + const hosts = getLocalHosts(); for (const port of portCheckSequence(ports)) { try { From 38299e98daf84845d8f91e11a7623454a9c6af39 Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sun, 26 Sep 2021 16:59:30 +0300 Subject: [PATCH 09/12] use set instead of array --- index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index b5d4b0e..449a09f 100644 --- a/index.js +++ b/index.js @@ -23,17 +23,20 @@ let interval; const getLocalHosts = () => { const interfaces = os.networkInterfaces(); - const results = []; + const results = new Set(); for (const _interface of Object.values(interfaces)) { for (const config of _interface) { - results.push(config.address); + results.add(config.address); } } // Add undefined value for createServer function to use default host, // and default IPv4 host in case createServer defaults to IPv6. - return results.concat(undefined, '0.0.0.0'); + results.add(undefined); + results.add('0.0.0.0'); + + return results; }; const checkAvailablePort = options => From 81dad7b4cd17553c74a8f2d9b359b7a71481b356 Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sun, 26 Sep 2021 17:07:18 +0300 Subject: [PATCH 10/12] add docs --- readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5c5f09c..cfda1f4 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,8 @@ # get-port -> Get an available [TCP port](https://en.wikipedia.org/wiki/Port_(computer_networking)) +> Get an available [TCP port](https://en.wikipedia.org/wiki/Port_(computer_networking)). +> +> Checks availability on all local addresses defined in [OS network interfaces](https://nodejs.org/api/os.html#os_os_networkinterfaces). ## Install From 99b43553423c8b46664884777d92994030060406 Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sun, 26 Sep 2021 19:46:05 +0300 Subject: [PATCH 11/12] move default host values to set initializer --- index.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 449a09f..73a7199 100644 --- a/index.js +++ b/index.js @@ -23,7 +23,9 @@ let interval; const getLocalHosts = () => { const interfaces = os.networkInterfaces(); - const results = new Set(); + // Add undefined value for createServer function to use default host, + // and default IPv4 host in case createServer defaults to IPv6. + const results = new Set([undefined, '0.0.0.0']); for (const _interface of Object.values(interfaces)) { for (const config of _interface) { @@ -31,11 +33,6 @@ const getLocalHosts = () => { } } - // Add undefined value for createServer function to use default host, - // and default IPv4 host in case createServer defaults to IPv6. - results.add(undefined); - results.add('0.0.0.0'); - return results; }; From d82147a78e4c33f47faa8ce188a9091231993e0d Mon Sep 17 00:00:00 2001 From: Mihkel Eidast Date: Sun, 26 Sep 2021 19:47:21 +0300 Subject: [PATCH 12/12] improve docs --- readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index cfda1f4..f16fcd6 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ > Get an available [TCP port](https://en.wikipedia.org/wiki/Port_(computer_networking)). > -> Checks availability on all local addresses defined in [OS network interfaces](https://nodejs.org/api/os.html#os_os_networkinterfaces). +> ## Install @@ -70,6 +70,8 @@ Type: `string` The host on which port resolution should be performed. Can be either an IPv4 or IPv6 address. +By default, `get-port` checks availability on all local addresses defined in [OS network interfaces](https://nodejs.org/api/os.html#os_os_networkinterfaces). If this option is set, it will only check the specified host. + ### getPort.makeRange(from, to) Make a range of ports `from`...`to`.