diff --git a/.travis.yml b/.travis.yml index 2ae9d62..f3fa8cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,3 @@ language: node_js node_js: - '10' - '8' - - '6' diff --git a/index.d.ts b/index.d.ts index 0cf6141..92ceccd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,8 +1,8 @@ export interface Options { /** - * A preferred port or an array of preferred ports to use. + * A preferred port or an iterable of preferred ports to use. */ - readonly port?: number | ReadonlyArray, + readonly port?: number | Iterable, /** * The host on which port resolution should be performed. Can be either an IPv4 or IPv6 address. @@ -10,9 +10,22 @@ export interface Options { readonly host?: string; } -/** - * Get an available TCP port number. - * - * @returns Port number. - */ -export default function getPort(options?: Options): Promise; +declare const getPort: { + /** + * Get an available TCP port number. + * + * @returns Port number. + */ + (options?: Options): Promise; + + /** + * Make a range of ports `from...to`. + * + * @param from - First port of the range. Must be in the range `1024...65535`. + * @param to - Last port of the range. Must be in the range `1024...65535` and must be greater than `from`. + * @returns The ports in the range. + */ + makeRange(from: number, to: number): Iterable; +} + +export default getPort; diff --git a/index.js b/index.js index 9963251..3352dc4 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ 'use strict'; const net = require('net'); -const isAvailable = options => new Promise((resolve, reject) => { +const getAvailablePort = options => new Promise((resolve, reject) => { const server = net.createServer(); server.unref(); server.on('error', reject); @@ -13,23 +13,58 @@ const isAvailable = options => new Promise((resolve, reject) => { }); }); -const getPort = options => { - options = Object.assign({}, options); +const portCheckSequence = function * (ports) { + if (ports) { + yield * ports; + } + + yield 0; // Fall back to 0 if anything else failed +}; + +module.exports = async options => { + let ports = null; - if (typeof options.port === 'number') { - options.port = [options.port]; + if (options) { + ports = typeof options.port === 'number' ? [options.port] : options.port; } - return (options.port || []).reduce( - (seq, port) => seq.catch( - () => isAvailable(Object.assign({}, options, {port})) - ), - Promise.reject() - ); + for (const port of portCheckSequence(ports)) { + try { + return await getAvailablePort({...options, port}); // eslint-disable-line no-await-in-loop + } catch (error) { + if (error.code !== 'EADDRINUSE') { + throw error; + } + } + } + + throw new Error('No available ports found'); }; -module.exports = options => options ? - getPort(options).catch(() => getPort(Object.assign(options, {port: 0}))) : - getPort({port: 0}); +module.exports.makeRange = (from, to) => { + if (!Number.isInteger(from) || !Number.isInteger(to)) { + throw new TypeError('`from` and `to` must be integer numbers'); + } + + if (from < 1024 || from > 65535) { + throw new RangeError('`from` must be between 1024 and 65535'); + } + + if (to < 1024 || to > 65536) { + throw new RangeError('`to` must be between 1024 and 65536'); + } + + if (to < from) { + throw new RangeError('`to` must be greater than or equal to `from`'); + } + + const generator = function * (from, to) { + for (let port = from; port <= to; port++) { + yield port; + } + }; + + return generator(from, to); +}; module.exports.default = module.exports; diff --git a/index.test-d.ts b/index.test-d.ts index eca3d91..0cbf934 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -5,3 +5,5 @@ expectType(await getPort()); expectType(await getPort({port: 3000})); expectType(await getPort({port: [3000, 3001, 3002]})); expectType(await getPort({host: 'https://localhost'})); + +expectType>(getPort.makeRange(1024, 1025)); diff --git a/package.json b/package.json index 1cde216..08e7e95 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "url": "sindresorhus.com" }, "engines": { - "node": ">=6" + "node": ">=8" }, "scripts": { "test": "xo && ava && tsd-check" diff --git a/readme.md b/readme.md index eaf7c4b..c4b9a8f 100644 --- a/readme.md +++ b/readme.md @@ -39,6 +39,15 @@ Pass in an array of preferred ports: })(); ``` +Use the `makeRange()` helper in case you need a port in a certain range: + +```js +(async () => { + console.log(await getPort({port: getPort.makeRange(3000, 3100)})); + // Will use any port from 3000 to 3100, otherwise fall back to a random port +})(); +``` + ## API ### getPort([options]) @@ -51,9 +60,9 @@ Type: `Object` ##### port -Type: `number | number[]` +Type: `number | Iterable` -A preferred port or an array of preferred ports to use. +A preferred port or an iterable of preferred ports to use. ##### host @@ -61,6 +70,24 @@ Type: `string` The host on which port resolution should be performed. Can be either an IPv4 or IPv6 address. +### getPort.makeRange(from, to) + +Make a range of ports `from...to`. + +Returns an `Iterable` for ports in the given range. + +#### from + +Type: `number` + +First port of the range. Must be in the range `1024...65535`. + +#### to + +Type: `number` + +Last port of the range. Must be in the range `1024...65535` and must be greater than `from`. + ## Beware diff --git a/test.js b/test.js index e4bac3a..fbe2835 100644 --- a/test.js +++ b/test.js @@ -93,3 +93,37 @@ test('all preferred ports in array are unavailable', async t => { t.not(port, desiredPorts[0]); t.not(port, desiredPorts[1]); }); + +test('non-array iterables work', async t => { + const desiredPorts = (function * () { + yield 9920; + })(); + const port = await getPort({ + port: desiredPorts, + host: '0.0.0.0' + }); + t.is(port, 9920); +}); + +test('makeRange throws on invalid ranges', t => { + t.throws(() => { + getPort.makeRange(1025, 1024); + }); + + // Invalid port values + t.throws(() => { + getPort.makeRange(0, 0); + }); + t.throws(() => { + getPort.makeRange(1023, 1023); + }); + t.throws(() => { + getPort.makeRange(65536, 65536); + }); +}); + +test('makeRange produces valid ranges', t => { + t.deepEqual([...getPort.makeRange(1024, 1024)], [1024]); + t.deepEqual([...getPort.makeRange(1024, 1025)], [1024, 1025]); + t.deepEqual([...getPort.makeRange(1024, 1027)], [1024, 1025, 1026, 1027]); +});