123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- "use strict";
- /*
- * Based on the packages get-port https://www.npmjs.com/package/get-port
- * and portfinder https://www.npmjs.com/package/portfinder
- * The code structure is similar to get-port, but it searches
- * ports deterministically like portfinder
- */
- const net = require("net");
- const os = require("os");
- const minPort = 1024;
- const maxPort = 65_535;
- /**
- * @return {Set<string|undefined>}
- */
- const getLocalHosts = () => {
- const interfaces = os.networkInterfaces();
- // Add undefined value for createServer function to use default host,
- // and default IPv4 host in case createServer defaults to IPv6.
- // eslint-disable-next-line no-undefined
- const results = new Set([undefined, "0.0.0.0"]);
- for (const _interface of Object.values(interfaces)) {
- if (_interface) {
- for (const config of _interface) {
- results.add(config.address);
- }
- }
- }
- return results;
- };
- /**
- * @param {number} basePort
- * @param {string | undefined} host
- * @return {Promise<number>}
- */
- const checkAvailablePort = (basePort, host) =>
- new Promise((resolve, reject) => {
- const server = net.createServer();
- server.unref();
- server.on("error", reject);
- server.listen(basePort, host, () => {
- // Next line should return AdressInfo because we're calling it after listen() and before close()
- const { port } = /** @type {import("net").AddressInfo} */ (
- server.address()
- );
- server.close(() => {
- resolve(port);
- });
- });
- });
- /**
- * @param {number} port
- * @param {Set<string|undefined>} hosts
- * @return {Promise<number>}
- */
- const getAvailablePort = async (port, hosts) => {
- /**
- * Errors that mean that host is not available.
- * @type {Set<string | undefined>}
- */
- const nonExistentInterfaceErrors = new Set(["EADDRNOTAVAIL", "EINVAL"]);
- /* Check if the post is available on every local host name */
- for (const host of hosts) {
- try {
- await checkAvailablePort(port, host); // eslint-disable-line no-await-in-loop
- } catch (error) {
- /* We throw an error only if the interface exists */
- if (
- !nonExistentInterfaceErrors.has(
- /** @type {NodeJS.ErrnoException} */ (error).code
- )
- ) {
- throw error;
- }
- }
- }
- return port;
- };
- /**
- * @param {number} basePort
- * @param {string=} host
- * @return {Promise<number>}
- */
- async function getPorts(basePort, host) {
- if (basePort < minPort || basePort > maxPort) {
- throw new Error(`Port number must lie between ${minPort} and ${maxPort}`);
- }
- let port = basePort;
- const localhosts = getLocalHosts();
- let hosts;
- if (host && !localhosts.has(host)) {
- hosts = new Set([host]);
- } else {
- /* If the host is equivalent to localhost
- we need to check every equivalent host
- else the port might falsely appear as available
- on some operating systems */
- hosts = localhosts;
- }
- /** @type {Set<string | undefined>} */
- const portUnavailableErrors = new Set(["EADDRINUSE", "EACCES"]);
- while (port <= maxPort) {
- try {
- const availablePort = await getAvailablePort(port, hosts); // eslint-disable-line no-await-in-loop
- return availablePort;
- } catch (error) {
- /* Try next port if port is busy; throw for any other error */
- if (
- !portUnavailableErrors.has(
- /** @type {NodeJS.ErrnoException} */ (error).code
- )
- ) {
- throw error;
- }
- port += 1;
- }
- }
- throw new Error("No available ports found");
- }
- module.exports = getPorts;
|