"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveHostAndAssignPorts = exports.waitForPortClosed = exports.checkListenable = void 0; const clc = require("colorette"); const tcpport = require("tcp-port-used"); const node_net_1 = require("node:net"); const error_1 = require("../error"); const utils = require("../utils"); const dns_1 = require("./dns"); const types_1 = require("./types"); const constants_1 = require("./constants"); const emulatorLogger_1 = require("./emulatorLogger"); const node_child_process_1 = require("node:child_process"); const RESTRICTED_PORTS = new Set([ 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 556, 563, 587, 601, 636, 993, 995, 2049, 3659, 4045, 6000, 6665, 6666, 6667, 6668, 6669, 6697, ]); function isRestricted(port) { return RESTRICTED_PORTS.has(port); } function suggestUnrestricted(port) { if (!isRestricted(port)) { return port; } let newPort = port; while (isRestricted(newPort)) { newPort++; } return newPort; } async function checkListenable(arg1, port) { const addr = port === undefined ? arg1 : listenSpec(arg1, port); return new Promise((resolve, reject) => { if (process.platform === "darwin") { try { (0, node_child_process_1.execSync)(`lsof -i :${addr.port} -sTCP:LISTEN`); return resolve(false); } catch (e) { } } const dummyServer = (0, node_net_1.createServer)(); dummyServer.once("error", (err) => { dummyServer.removeAllListeners(); const e = err; if (e.code === "EADDRINUSE" || e.code === "EACCES") { resolve(false); } else { reject(e); } }); dummyServer.once("listening", () => { dummyServer.removeAllListeners(); dummyServer.close((err) => { dummyServer.removeAllListeners(); if (err) { reject(err); } else { resolve(true); } }); }); dummyServer.listen({ host: addr.address, port: addr.port, ipv6Only: addr.family === "IPv6" }); }); } exports.checkListenable = checkListenable; async function waitForPortClosed(port, host) { const interval = 250; const timeout = 60000; try { await tcpport.waitUntilUsedOnHost(port, host, interval, timeout); } catch (e) { throw new error_1.FirebaseError(`TIMEOUT: Port ${port} on ${host} was not active within ${timeout}ms`); } } exports.waitForPortClosed = waitForPortClosed; const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = { database: true, firestore: true, "firestore.websocket": true, pubsub: true, hub: false, ui: false, auth: true, eventarc: true, extensions: true, functions: true, logging: true, storage: true, hosting: true, }; const MAX_PORT = 65535; async function resolveHostAndAssignPorts(listenConfig) { const lookupForHost = new Map(); const takenPorts = new Map(); const result = {}; const tasks = []; for (const name of Object.keys(listenConfig)) { const config = listenConfig[name]; if (!config) { continue; } else if (config instanceof Array) { result[name] = config; for (const { port } of config) { takenPorts.set(port, name); } continue; } const { host, port, portFixed } = config; let lookup = lookupForHost.get(host); if (!lookup) { lookup = dns_1.Resolver.DEFAULT.lookupAll(host); lookupForHost.set(host, lookup); } const findAddrs = lookup.then(async (addrs) => { const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket" ? types_1.Emulators.FIRESTORE : name); if (addrs.some((addr) => addr.address === dns_1.IPV6_UNSPECIFIED.address)) { if (!addrs.some((addr) => addr.address === dns_1.IPV4_UNSPECIFIED.address)) { emuLogger.logLabeled("DEBUG", name, `testing listening on IPv4 wildcard in addition to IPv6. To listen on IPv6 only, use "::0" instead.`); addrs.push(dns_1.IPV4_UNSPECIFIED); } } for (let p = port; p <= MAX_PORT; p++) { if (takenPorts.has(p)) { continue; } if (!portFixed && RESTRICTED_PORTS.has(p)) { emuLogger.logLabeled("DEBUG", name, `portUtils: skipping restricted port ${p}`); continue; } if (p === 5001 && /^hosting/i.exec(name)) { continue; } const available = []; const unavailable = []; let i; for (i = 0; i < addrs.length; i++) { const addr = addrs[i]; const listen = listenSpec(addr, p); let listenable; try { listenable = await checkListenable(listen); } catch (err) { emuLogger.logLabeled("WARN", name, `Error when trying to check port ${p} on ${addr.address}: ${err}`); unavailable.push(addr.address); continue; } if (listenable) { available.push(listen); } else { if (!portFixed) { if (i > 0) { emuLogger.logLabeled("DEBUG", name, `Port ${p} taken on secondary address ${addr.address}, will keep searching to find a better port.`); } break; } unavailable.push(addr.address); } } if (i === addrs.length) { if (unavailable.length > 0) { if (unavailable[0] === addrs[0].address) { return fixedPortNotAvailable(name, host, port, emuLogger, unavailable); } warnPartiallyAvailablePort(emuLogger, port, available, unavailable); } if (takenPorts.has(p)) { continue; } takenPorts.set(p, name); if (RESTRICTED_PORTS.has(p)) { const suggested = suggestUnrestricted(port); 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}.`); } if (p !== port && name !== "firestore.websocket") { emuLogger.logLabeled("WARN", `${portDescription(name)} unable to start on port ${port}, starting on ${p} instead.`); } if (available.length > 1 && EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY[name]) { emuLogger.logLabeled("DEBUG", name, `${portDescription(name)} only supports listening on one address (${available[0].address}). Not listening on ${addrs .slice(1) .map((s) => s.address) .join(",")}`); result[name] = [available[0]]; } else { result[name] = available; } return; } } return utils.reject(`Could not find any open port in ${port}-${MAX_PORT} for ${portDescription(name)}`, {}); }); tasks.push(findAddrs); } await Promise.all(tasks); return result; } exports.resolveHostAndAssignPorts = resolveHostAndAssignPorts; function portDescription(name) { return name === "firestore.websocket" ? `websocket server for ${types_1.Emulators.FIRESTORE}` : constants_1.Constants.description(name); } function warnPartiallyAvailablePort(emuLogger, port, available, unavailable) { emuLogger.logLabeled("WARN", `Port ${port} is available on ` + available.map((s) => s.address).join(",") + ` but not ${unavailable.join(",")}. This may cause issues with some clients.`); emuLogger.logLabeled("WARN", `If you encounter connectivity issues, consider switching to a different port or explicitly specifying ${clc.yellow('"host": ""')} instead of hostname in firebase.json`); } function fixedPortNotAvailable(name, host, port, emuLogger, unavailableAddrs) { if (unavailableAddrs.length !== 1 || unavailableAddrs[0] !== host) { host = `${host} (${unavailableAddrs.join(",")})`; } const description = portDescription(name); emuLogger.logLabeled("WARN", `Port ${port} is not open on ${host}, could not start ${description}.`); if (name === "firestore.websocket") { emuLogger.logLabeled("WARN", `To select a different port, specify that port in a firebase.json config file: { // ... "emulators": { "${types_1.Emulators.FIRESTORE}": { "host": "${clc.yellow("HOST")}", ... "websocketPort": "${clc.yellow("WEBSOCKET_PORT")}" } } }`); } else { emuLogger.logLabeled("WARN", `To select a different host/port, specify that host/port in a firebase.json config file: { // ... "emulators": { "${emuLogger.name}": { "host": "${clc.yellow("HOST")}", "port": "${clc.yellow("PORT")}" } } }`); } return utils.reject(`Could not start ${description}, port taken.`, {}); } function listenSpec(lookup, port) { if (lookup.family !== 4 && lookup.family !== 6) { throw new Error(`Unsupported address family "${lookup.family}" for address ${lookup.address}.`); } return { address: lookup.address, family: lookup.family === 4 ? "IPv4" : "IPv6", port: port, }; }