getPort.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. "use strict";
  2. /*
  3. * Based on the packages get-port https://www.npmjs.com/package/get-port
  4. * and portfinder https://www.npmjs.com/package/portfinder
  5. * The code structure is similar to get-port, but it searches
  6. * ports deterministically like portfinder
  7. */
  8. const net = require("node:net");
  9. const os = require("node:os");
  10. const minPort = 1024;
  11. const maxPort = 65_535;
  12. /**
  13. * @returns {Set<string | undefined>} local hosts
  14. */
  15. const getLocalHosts = () => {
  16. const interfaces = os.networkInterfaces();
  17. // Add undefined value for createServer function to use default host,
  18. // and default IPv4 host in case createServer defaults to IPv6.
  19. const results = new Set([undefined, "0.0.0.0"]);
  20. for (const _interface of Object.values(interfaces)) {
  21. if (_interface) {
  22. for (const config of _interface) {
  23. results.add(config.address);
  24. }
  25. }
  26. }
  27. return results;
  28. };
  29. /**
  30. * @param {number} basePort base port
  31. * @param {string | undefined} host host
  32. * @returns {Promise<number>} resolved port
  33. */
  34. const checkAvailablePort = (basePort, host) =>
  35. new Promise((resolve, reject) => {
  36. const server = net.createServer();
  37. server.unref();
  38. server.on("error", reject);
  39. server.listen(basePort, host, () => {
  40. // Next line should return AddressInfo because we're calling it after listen() and before close()
  41. const { port } = /** @type {import("net").AddressInfo} */ (
  42. server.address()
  43. );
  44. server.close(() => {
  45. resolve(port);
  46. });
  47. });
  48. });
  49. /**
  50. * @param {number} port port
  51. * @param {Set<string|undefined>} hosts hosts
  52. * @returns {Promise<number>} resolved port
  53. */
  54. const getAvailablePort = async (port, hosts) => {
  55. /**
  56. * Errors that mean that host is not available.
  57. * @type {Set<string | undefined>}
  58. */
  59. const nonExistentInterfaceErrors = new Set(["EADDRNOTAVAIL", "EINVAL"]);
  60. /* Check if the post is available on every local host name */
  61. for (const host of hosts) {
  62. try {
  63. await checkAvailablePort(port, host);
  64. } catch (error) {
  65. /* We throw an error only if the interface exists */
  66. if (
  67. !nonExistentInterfaceErrors.has(
  68. /** @type {NodeJS.ErrnoException} */ (error).code,
  69. )
  70. ) {
  71. throw error;
  72. }
  73. }
  74. }
  75. return port;
  76. };
  77. /**
  78. * @param {number} basePort base port
  79. * @param {string=} host host
  80. * @returns {Promise<number>} resolved port
  81. */
  82. async function getPorts(basePort, host) {
  83. if (basePort < minPort || basePort > maxPort) {
  84. throw new Error(`Port number must lie between ${minPort} and ${maxPort}`);
  85. }
  86. let port = basePort;
  87. const localhosts = getLocalHosts();
  88. const hosts =
  89. host && !localhosts.has(host)
  90. ? new Set([host])
  91. : /* If the host is equivalent to localhost
  92. we need to check every equivalent host
  93. else the port might falsely appear as available
  94. on some operating systems */
  95. localhosts;
  96. /** @type {Set<string | undefined>} */
  97. const portUnavailableErrors = new Set(["EADDRINUSE", "EACCES"]);
  98. while (port <= maxPort) {
  99. try {
  100. const availablePort = await getAvailablePort(port, hosts);
  101. return availablePort;
  102. } catch (error) {
  103. /* Try next port if port is busy; throw for any other error */
  104. if (
  105. !portUnavailableErrors.has(
  106. /** @type {NodeJS.ErrnoException} */ (error).code,
  107. )
  108. ) {
  109. throw error;
  110. }
  111. port += 1;
  112. }
  113. }
  114. throw new Error("No available ports found");
  115. }
  116. module.exports = getPorts;