Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support UI listening on mutliple addresses. #5088

Merged
merged 5 commits into from Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
@@ -1,2 +1,4 @@
- Enable single project mode for the database emulator (#5068).
- Ravamp emulator networking to assign ports early and explictly listen on IP addresses (#5083).
- Emulator UI and hub now listen on both IPv4 and IPv6 address by default (if possible) (#5088).
- Fix Firestore emulator excessive logs about discovery endpoint not found (#5088).
1 change: 1 addition & 0 deletions src/commands/emulators-start.ts
Expand Up @@ -101,6 +101,7 @@ function printEmulatorOverview(options: any): void {
if (uiRunning) {
row.push("");
}
return row;
}
let uiLink = "n/a";
if (isSupportedByUi && uiRunning) {
Expand Down
28 changes: 17 additions & 11 deletions src/emulator/ExpressBasedEmulator.ts
Expand Up @@ -62,6 +62,22 @@ export abstract class ExpressBasedEmulator implements EmulatorInstance {
const promises = [];
const specs = this.options.listen;

for (const opt of ExpressBasedEmulator.listenOptionsFromSpecs(specs)) {
promises.push(
new Promise((resolve, reject) => {
const server = createServer(app).listen(opt);
server.once("listening", resolve);
server.once("error", reject);
this.destroyers.add(utils.createDestroyer(server));
})
);
}
}

/**
* Translate addresses and ports to low-level net/http server options.
*/
static listenOptionsFromSpecs(specs: ListenSpec[]): ListenOptions[] {
const listenOptions: ListenOptions[] = [];

const dualStackPorts = new Set();
Expand Down Expand Up @@ -89,17 +105,7 @@ export abstract class ExpressBasedEmulator implements EmulatorInstance {
});
}
}

for (const opt of listenOptions) {
promises.push(
new Promise((resolve, reject) => {
const server = createServer(app).listen(opt);
server.once("listening", resolve);
server.once("error", reject);
this.destroyers.add(utils.createDestroyer(server));
})
);
}
return listenOptions;
}

async connect(): Promise<void> {
Expand Down
9 changes: 6 additions & 3 deletions src/emulator/controller.ts
Expand Up @@ -444,7 +444,11 @@ export async function startAll(
}

if (listenForEmulator.hub) {
const hub = new EmulatorHub({ projectId, listen: listenForEmulator[Emulators.HUB] });
const hub = new EmulatorHub({
projectId,
listen: listenForEmulator[Emulators.HUB],
listenForEmulator,
});

// Log the command for analytics, we only report this for "hub"
// since we originally mistakenly reported emulators:start events
Expand Down Expand Up @@ -820,11 +824,10 @@ export async function startAll(
}

if (listenForEmulator.ui) {
const uiAddr = legacyGetFirstAddr(Emulators.UI);
const ui = new EmulatorUI({
projectId: projectId,
auto_download: true,
...uiAddr,
listen: listenForEmulator[Emulators.UI],
});
await startEmulator(ui);
}
Expand Down
14 changes: 7 additions & 7 deletions src/emulator/downloadableEmulators.ts
Expand Up @@ -80,15 +80,15 @@ export const DownloadDetails: { [s in DownloadableEmulators]: EmulatorDownloadDe
},
}
: {
version: "1.10.0",
downloadPath: path.join(CACHE_DIR, "ui-v1.10.0.zip"),
unzipDir: path.join(CACHE_DIR, "ui-v1.10.0"),
binaryPath: path.join(CACHE_DIR, "ui-v1.10.0", "server", "server.js"),
version: "1.11.0",
downloadPath: path.join(CACHE_DIR, "ui-v1.11.0.zip"),
unzipDir: path.join(CACHE_DIR, "ui-v1.11.0"),
binaryPath: path.join(CACHE_DIR, "ui-v1.11.0", "server", "server.js"),
opts: {
cacheDir: CACHE_DIR,
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.10.0.zip",
expectedSize: 3062540,
expectedChecksum: "7dec1e82acccc196efc4d364e2664288",
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.11.0.zip",
expectedSize: 3061915,
expectedChecksum: "94679756dc270754e9a4dc9d1c6fc4e1",
namePrefix: "ui",
},
},
Expand Down
7 changes: 6 additions & 1 deletion src/emulator/hub.ts
Expand Up @@ -10,6 +10,7 @@ import { HubExport } from "./hubExport";
import { EmulatorRegistry } from "./registry";
import { FunctionsEmulator } from "./functionsEmulator";
import { ExpressBasedEmulator } from "./ExpressBasedEmulator";
import { PortName } from "./portUtils";

// We use the CLI version from package.json
const pkg = require("../../package.json");
Expand All @@ -23,6 +24,7 @@ export interface Locator {
export interface EmulatorHubArgs {
projectId: string;
listen: ListenSpec[];
listenForEmulator: Record<PortName, ListenSpec[]>;
}

export type GetEmulatorsResponse = Record<string, EmulatorInfo>;
Expand Down Expand Up @@ -87,7 +89,10 @@ export class EmulatorHub extends ExpressBasedEmulator {
app.get(EmulatorHub.PATH_EMULATORS, (req, res) => {
const body: GetEmulatorsResponse = {};
for (const info of EmulatorRegistry.listRunningWithInfo()) {
body[info.name] = info;
body[info.name] = {
listen: this.args.listenForEmulator[info.name],
...info,
};
}
res.json(body);
});
Expand Down
8 changes: 4 additions & 4 deletions src/emulator/portUtils.ts
Expand Up @@ -174,11 +174,11 @@ const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY: Record<PortName, boolean> = {
// Listening on multiple addresses to maximize the chance of discovery.
hub: false,

// TODO: Modify the following emulators to listen on multiple addresses.
// Separate Node.js process that supports multi-listen. For consistency, we
// resolve the addresses in the CLI and pass the result to the UI.
ui: false,

// Separate Node.js process that requires a separate update.
// For consistency, we can resolve in the CLI and pass in the results.
ui: true,
// TODO: Modify the following emulators to listen on multiple addresses.

// Express-based servers, can be reused for multiple listen sockets.
auth: true,
Expand Down
15 changes: 7 additions & 8 deletions src/emulator/ui.ts
@@ -1,13 +1,13 @@
import { EmulatorInstance, EmulatorInfo, Emulators } from "./types";
import { EmulatorInstance, EmulatorInfo, Emulators, ListenSpec } from "./types";
import * as downloadableEmulators from "./downloadableEmulators";
import { EmulatorRegistry } from "./registry";
import { FirebaseError } from "../error";
import { Constants } from "./constants";
import { emulatorSession } from "../track";
import { ExpressBasedEmulator } from "./ExpressBasedEmulator";

export interface EmulatorUIOptions {
port: number;
host: string;
listen: ListenSpec[];
projectId: string;
auto_download?: boolean;
}
Expand All @@ -23,10 +23,9 @@ export class EmulatorUI implements EmulatorInstance {
)}!`
);
}
const { auto_download: autoDownload, host, port, projectId } = this.args;
const { auto_download: autoDownload, projectId } = this.args;
const env: Partial<NodeJS.ProcessEnv> = {
HOST: host.toString(),
PORT: port.toString(),
LISTEN: JSON.stringify(ExpressBasedEmulator.listenOptionsFromSpecs(this.args.listen)),
GCLOUD_PROJECT: projectId,
[Constants.FIREBASE_EMULATOR_HUB]: EmulatorRegistry.url(Emulators.HUB).host,
};
Expand All @@ -50,8 +49,8 @@ export class EmulatorUI implements EmulatorInstance {
getInfo(): EmulatorInfo {
return {
name: this.getName(),
host: this.args.host,
port: this.args.port,
host: this.args.listen[0].address,
port: this.args.listen[0].port,
pid: downloadableEmulators.getPID(Emulators.UI),
};
}
Expand Down