No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

portUtils.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.resolveHostAndAssignPorts = exports.waitForPortClosed = exports.checkListenable = void 0;
  4. const clc = require("colorette");
  5. const tcpport = require("tcp-port-used");
  6. const node_net_1 = require("node:net");
  7. const error_1 = require("../error");
  8. const utils = require("../utils");
  9. const dns_1 = require("./dns");
  10. const types_1 = require("./types");
  11. const constants_1 = require("./constants");
  12. const emulatorLogger_1 = require("./emulatorLogger");
  13. const node_child_process_1 = require("node:child_process");
  14. const RESTRICTED_PORTS = new Set([
  15. 1,
  16. 7,
  17. 9,
  18. 11,
  19. 13,
  20. 15,
  21. 17,
  22. 19,
  23. 20,
  24. 21,
  25. 22,
  26. 23,
  27. 25,
  28. 37,
  29. 42,
  30. 43,
  31. 53,
  32. 77,
  33. 79,
  34. 87,
  35. 95,
  36. 101,
  37. 102,
  38. 103,
  39. 104,
  40. 109,
  41. 110,
  42. 111,
  43. 113,
  44. 115,
  45. 117,
  46. 119,
  47. 123,
  48. 135,
  49. 139,
  50. 143,
  51. 179,
  52. 389,
  53. 427,
  54. 465,
  55. 512,
  56. 513,
  57. 514,
  58. 515,
  59. 526,
  60. 530,
  61. 531,
  62. 532,
  63. 540,
  64. 548,
  65. 556,
  66. 563,
  67. 587,
  68. 601,
  69. 636,
  70. 993,
  71. 995,
  72. 2049,
  73. 3659,
  74. 4045,
  75. 6000,
  76. 6665,
  77. 6666,
  78. 6667,
  79. 6668,
  80. 6669,
  81. 6697,
  82. ]);
  83. function isRestricted(port) {
  84. return RESTRICTED_PORTS.has(port);
  85. }
  86. function suggestUnrestricted(port) {
  87. if (!isRestricted(port)) {
  88. return port;
  89. }
  90. let newPort = port;
  91. while (isRestricted(newPort)) {
  92. newPort++;
  93. }
  94. return newPort;
  95. }
  96. async function checkListenable(arg1, port) {
  97. const addr = port === undefined ? arg1 : listenSpec(arg1, port);
  98. return new Promise((resolve, reject) => {
  99. if (process.platform === "darwin") {
  100. try {
  101. (0, node_child_process_1.execSync)(`lsof -i :${addr.port} -sTCP:LISTEN`);
  102. return resolve(false);
  103. }
  104. catch (e) {
  105. }
  106. }
  107. const dummyServer = (0, node_net_1.createServer)();
  108. dummyServer.once("error", (err) => {
  109. dummyServer.removeAllListeners();
  110. const e = err;
  111. if (e.code === "EADDRINUSE" || e.code === "EACCES") {
  112. resolve(false);
  113. }
  114. else {
  115. reject(e);
  116. }
  117. });
  118. dummyServer.once("listening", () => {
  119. dummyServer.removeAllListeners();
  120. dummyServer.close((err) => {
  121. dummyServer.removeAllListeners();
  122. if (err) {
  123. reject(err);
  124. }
  125. else {
  126. resolve(true);
  127. }
  128. });
  129. });
  130. dummyServer.listen({ host: addr.address, port: addr.port, ipv6Only: addr.family === "IPv6" });
  131. });
  132. }
  133. exports.checkListenable = checkListenable;
  134. async function waitForPortClosed(port, host) {
  135. const interval = 250;
  136. const timeout = 60000;
  137. try {
  138. await tcpport.waitUntilUsedOnHost(port, host, interval, timeout);
  139. }
  140. catch (e) {
  141. throw new error_1.FirebaseError(`TIMEOUT: Port ${port} on ${host} was not active within ${timeout}ms`);
  142. }
  143. }
  144. exports.waitForPortClosed = waitForPortClosed;
  145. const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = {
  146. database: true,
  147. firestore: true,
  148. "firestore.websocket": true,
  149. pubsub: true,
  150. hub: false,
  151. ui: false,
  152. auth: true,
  153. eventarc: true,
  154. extensions: true,
  155. functions: true,
  156. logging: true,
  157. storage: true,
  158. hosting: true,
  159. };
  160. const MAX_PORT = 65535;
  161. async function resolveHostAndAssignPorts(listenConfig) {
  162. const lookupForHost = new Map();
  163. const takenPorts = new Map();
  164. const result = {};
  165. const tasks = [];
  166. for (const name of Object.keys(listenConfig)) {
  167. const config = listenConfig[name];
  168. if (!config) {
  169. continue;
  170. }
  171. else if (config instanceof Array) {
  172. result[name] = config;
  173. for (const { port } of config) {
  174. takenPorts.set(port, name);
  175. }
  176. continue;
  177. }
  178. const { host, port, portFixed } = config;
  179. let lookup = lookupForHost.get(host);
  180. if (!lookup) {
  181. lookup = dns_1.Resolver.DEFAULT.lookupAll(host);
  182. lookupForHost.set(host, lookup);
  183. }
  184. const findAddrs = lookup.then(async (addrs) => {
  185. const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket" ? types_1.Emulators.FIRESTORE : name);
  186. if (addrs.some((addr) => addr.address === dns_1.IPV6_UNSPECIFIED.address)) {
  187. if (!addrs.some((addr) => addr.address === dns_1.IPV4_UNSPECIFIED.address)) {
  188. emuLogger.logLabeled("DEBUG", name, `testing listening on IPv4 wildcard in addition to IPv6. To listen on IPv6 only, use "::0" instead.`);
  189. addrs.push(dns_1.IPV4_UNSPECIFIED);
  190. }
  191. }
  192. for (let p = port; p <= MAX_PORT; p++) {
  193. if (takenPorts.has(p)) {
  194. continue;
  195. }
  196. if (!portFixed && RESTRICTED_PORTS.has(p)) {
  197. emuLogger.logLabeled("DEBUG", name, `portUtils: skipping restricted port ${p}`);
  198. continue;
  199. }
  200. if (p === 5001 && /^hosting/i.exec(name)) {
  201. continue;
  202. }
  203. const available = [];
  204. const unavailable = [];
  205. let i;
  206. for (i = 0; i < addrs.length; i++) {
  207. const addr = addrs[i];
  208. const listen = listenSpec(addr, p);
  209. let listenable;
  210. try {
  211. listenable = await checkListenable(listen);
  212. }
  213. catch (err) {
  214. emuLogger.logLabeled("WARN", name, `Error when trying to check port ${p} on ${addr.address}: ${err}`);
  215. unavailable.push(addr.address);
  216. continue;
  217. }
  218. if (listenable) {
  219. available.push(listen);
  220. }
  221. else {
  222. if (!portFixed) {
  223. if (i > 0) {
  224. emuLogger.logLabeled("DEBUG", name, `Port ${p} taken on secondary address ${addr.address}, will keep searching to find a better port.`);
  225. }
  226. break;
  227. }
  228. unavailable.push(addr.address);
  229. }
  230. }
  231. if (i === addrs.length) {
  232. if (unavailable.length > 0) {
  233. if (unavailable[0] === addrs[0].address) {
  234. return fixedPortNotAvailable(name, host, port, emuLogger, unavailable);
  235. }
  236. warnPartiallyAvailablePort(emuLogger, port, available, unavailable);
  237. }
  238. if (takenPorts.has(p)) {
  239. continue;
  240. }
  241. takenPorts.set(p, name);
  242. if (RESTRICTED_PORTS.has(p)) {
  243. const suggested = suggestUnrestricted(port);
  244. emuLogger.logLabeled("WARN", name, `Port ${port} is restricted by some web browsers, including Chrome. You may want to choose a different port such as ${suggested}.`);
  245. }
  246. if (p !== port && name !== "firestore.websocket") {
  247. emuLogger.logLabeled("WARN", `${portDescription(name)} unable to start on port ${port}, starting on ${p} instead.`);
  248. }
  249. if (available.length > 1 && EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY[name]) {
  250. emuLogger.logLabeled("DEBUG", name, `${portDescription(name)} only supports listening on one address (${available[0].address}). Not listening on ${addrs
  251. .slice(1)
  252. .map((s) => s.address)
  253. .join(",")}`);
  254. result[name] = [available[0]];
  255. }
  256. else {
  257. result[name] = available;
  258. }
  259. return;
  260. }
  261. }
  262. return utils.reject(`Could not find any open port in ${port}-${MAX_PORT} for ${portDescription(name)}`, {});
  263. });
  264. tasks.push(findAddrs);
  265. }
  266. await Promise.all(tasks);
  267. return result;
  268. }
  269. exports.resolveHostAndAssignPorts = resolveHostAndAssignPorts;
  270. function portDescription(name) {
  271. return name === "firestore.websocket"
  272. ? `websocket server for ${types_1.Emulators.FIRESTORE}`
  273. : constants_1.Constants.description(name);
  274. }
  275. function warnPartiallyAvailablePort(emuLogger, port, available, unavailable) {
  276. emuLogger.logLabeled("WARN", `Port ${port} is available on ` +
  277. available.map((s) => s.address).join(",") +
  278. ` but not ${unavailable.join(",")}. This may cause issues with some clients.`);
  279. emuLogger.logLabeled("WARN", `If you encounter connectivity issues, consider switching to a different port or explicitly specifying ${clc.yellow('"host": "<ip address>"')} instead of hostname in firebase.json`);
  280. }
  281. function fixedPortNotAvailable(name, host, port, emuLogger, unavailableAddrs) {
  282. if (unavailableAddrs.length !== 1 || unavailableAddrs[0] !== host) {
  283. host = `${host} (${unavailableAddrs.join(",")})`;
  284. }
  285. const description = portDescription(name);
  286. emuLogger.logLabeled("WARN", `Port ${port} is not open on ${host}, could not start ${description}.`);
  287. if (name === "firestore.websocket") {
  288. emuLogger.logLabeled("WARN", `To select a different port, specify that port in a firebase.json config file:
  289. {
  290. // ...
  291. "emulators": {
  292. "${types_1.Emulators.FIRESTORE}": {
  293. "host": "${clc.yellow("HOST")}",
  294. ...
  295. "websocketPort": "${clc.yellow("WEBSOCKET_PORT")}"
  296. }
  297. }
  298. }`);
  299. }
  300. else {
  301. emuLogger.logLabeled("WARN", `To select a different host/port, specify that host/port in a firebase.json config file:
  302. {
  303. // ...
  304. "emulators": {
  305. "${emuLogger.name}": {
  306. "host": "${clc.yellow("HOST")}",
  307. "port": "${clc.yellow("PORT")}"
  308. }
  309. }
  310. }`);
  311. }
  312. return utils.reject(`Could not start ${description}, port taken.`, {});
  313. }
  314. function listenSpec(lookup, port) {
  315. if (lookup.family !== 4 && lookup.family !== 6) {
  316. throw new Error(`Unsupported address family "${lookup.family}" for address ${lookup.address}.`);
  317. }
  318. return {
  319. address: lookup.address,
  320. family: lookup.family === 4 ? "IPv4" : "IPv6",
  321. port: port,
  322. };
  323. }