244 lines
5.0 KiB
JavaScript
244 lines
5.0 KiB
JavaScript
import { createServer } from 'net';
|
|
import { networkInterfaces } from 'os';
|
|
import { getMemo, setMemo } from 'fs-memo';
|
|
|
|
const unsafePorts = /* @__PURE__ */ new Set([
|
|
1,
|
|
7,
|
|
9,
|
|
11,
|
|
13,
|
|
15,
|
|
17,
|
|
19,
|
|
20,
|
|
21,
|
|
22,
|
|
23,
|
|
25,
|
|
37,
|
|
42,
|
|
43,
|
|
53,
|
|
69,
|
|
77,
|
|
79,
|
|
87,
|
|
95,
|
|
101,
|
|
102,
|
|
103,
|
|
104,
|
|
109,
|
|
110,
|
|
111,
|
|
113,
|
|
115,
|
|
117,
|
|
119,
|
|
123,
|
|
135,
|
|
137,
|
|
139,
|
|
143,
|
|
161,
|
|
179,
|
|
389,
|
|
427,
|
|
465,
|
|
512,
|
|
513,
|
|
514,
|
|
515,
|
|
526,
|
|
530,
|
|
531,
|
|
532,
|
|
540,
|
|
548,
|
|
554,
|
|
556,
|
|
563,
|
|
587,
|
|
601,
|
|
636,
|
|
989,
|
|
990,
|
|
993,
|
|
995,
|
|
1719,
|
|
1720,
|
|
1723,
|
|
2049,
|
|
3659,
|
|
4045,
|
|
5060,
|
|
5061,
|
|
6e3,
|
|
6566,
|
|
6665,
|
|
6666,
|
|
6667,
|
|
6668,
|
|
6669,
|
|
6697,
|
|
10080
|
|
]);
|
|
function isUnsafePort(port) {
|
|
return unsafePorts.has(port);
|
|
}
|
|
function isSafePort(port) {
|
|
return !isUnsafePort(port);
|
|
}
|
|
|
|
function log(...args) {
|
|
console.log("[get-port]", ...args);
|
|
}
|
|
async function getPort(config = {}) {
|
|
if (typeof config === "number" || typeof config === "string") {
|
|
config = { port: parseInt(config + "") || 0 };
|
|
}
|
|
const options = {
|
|
name: "default",
|
|
random: false,
|
|
ports: [],
|
|
portRange: [],
|
|
alternativePortRange: [3e3, 3100],
|
|
host: void 0,
|
|
memoName: "port",
|
|
verbose: false,
|
|
...config,
|
|
port: parseInt(process.env.PORT || "") || config.port || 3e3
|
|
};
|
|
if (options.random) {
|
|
return getRandomPort(options.host);
|
|
}
|
|
const portsToCheck = [
|
|
options.port,
|
|
...options.ports,
|
|
...generateRange(...options.portRange)
|
|
].filter((port) => {
|
|
if (!port) {
|
|
return false;
|
|
}
|
|
if (!isSafePort(port)) {
|
|
if (options.verbose) {
|
|
log("Ignoring unsafe port:", port);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
const memoOptions = { name: options.memoName, dir: options.memoDir };
|
|
const memoKey = "port_" + options.name;
|
|
const memo = await getMemo(memoOptions);
|
|
if (memo[memoKey]) {
|
|
portsToCheck.push(memo[memoKey]);
|
|
}
|
|
let availablePort = await findPort(portsToCheck, options.host, options.verbose, false);
|
|
if (!availablePort) {
|
|
availablePort = await findPort(generateRange(...options.alternativePortRange), options.host, options.verbose);
|
|
if (options.verbose) {
|
|
log(`Unable to find an available port (tried ${portsToCheck.join(", ") || "-"}). Using alternative port:`, availablePort);
|
|
}
|
|
}
|
|
await setMemo({ [memoKey]: availablePort }, memoOptions);
|
|
return availablePort;
|
|
}
|
|
async function getRandomPort(host) {
|
|
const port = await checkPort(0, host);
|
|
if (port === false) {
|
|
throw new Error("Unable to obtain an available random port number!");
|
|
}
|
|
return port;
|
|
}
|
|
async function waitForPort(port, opts = {}) {
|
|
const delay = opts.delay || 500;
|
|
const retries = opts.retries || 4;
|
|
for (let i = retries; i > 0; i--) {
|
|
if (await checkPort(port, opts.host) === false) {
|
|
return;
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
}
|
|
throw new Error(`Timeout waiting for port ${port} after ${retries} retries with ${delay}ms interval.`);
|
|
}
|
|
async function checkPort(port, host = process.env.HOST, _verbose) {
|
|
if (!host) {
|
|
host = getLocalHosts([void 0, "0.0.0.0"]);
|
|
}
|
|
if (!Array.isArray(host)) {
|
|
return _checkPort(port, host);
|
|
}
|
|
for (const _host of host) {
|
|
const _port = await _checkPort(port, _host);
|
|
if (_port === false) {
|
|
if (port < 1024 && _verbose) {
|
|
log("Unable to listen to priviliged port:", `${_host}:${port}`);
|
|
}
|
|
return false;
|
|
}
|
|
if (port === 0 && _port !== 0) {
|
|
port = _port;
|
|
}
|
|
}
|
|
return port;
|
|
}
|
|
function generateRange(from, to) {
|
|
if (to < from) {
|
|
return [];
|
|
}
|
|
const r = [];
|
|
for (let i = from; i < to; i++) {
|
|
r.push(i);
|
|
}
|
|
return r;
|
|
}
|
|
function _checkPort(port, host) {
|
|
return new Promise((resolve) => {
|
|
const server = createServer();
|
|
server.unref();
|
|
server.on("error", (err) => {
|
|
if (err.code === "EINVAL" || err.code === "EADDRNOTAVAIL") {
|
|
resolve(port !== 0 && isSafePort(port) && port);
|
|
} else {
|
|
resolve(false);
|
|
}
|
|
});
|
|
server.listen({ port, host }, () => {
|
|
const { port: port2 } = server.address();
|
|
server.close(() => {
|
|
resolve(isSafePort(port2) && port2);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
function getLocalHosts(additional) {
|
|
const hosts = new Set(additional);
|
|
for (const _interface of Object.values(networkInterfaces())) {
|
|
for (const config of _interface) {
|
|
hosts.add(config.address);
|
|
}
|
|
}
|
|
return Array.from(hosts);
|
|
}
|
|
async function findPort(ports, host, _verbose = false, _random = true) {
|
|
for (const port of ports) {
|
|
const r = await checkPort(port, host, _verbose);
|
|
if (r) {
|
|
return r;
|
|
}
|
|
}
|
|
if (_random) {
|
|
const randomPort = await getRandomPort(host);
|
|
if (_verbose) {
|
|
log(`Unable to find an available port (tried ${ports.join(", ") || "-"}). Using random port:`, randomPort);
|
|
}
|
|
return randomPort;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
export { checkPort, getPort, getRandomPort, isSafePort, isUnsafePort, waitForPort };
|