getPort.js 3.5 KB

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