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

Moving the emulator UI server to firebase-tools. #7897

Merged
merged 16 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions src/emulator/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
import * as downloadableEmulators from "./downloadableEmulators";
import { EmulatorRegistry } from "./registry";
import { FirebaseError } from "../error";
import { EmulatorLogger } from "./emulatorLogger";
import { Constants } from "./constants";
import { emulatorSession } from "../track";
import { ExpressBasedEmulator } from "./ExpressBasedEmulator";
import { createApp } from "./ui/server";
import { ALL_EXPERIMENTS, ExperimentName, isEnabled } from "../experiments";

export interface EmulatorUIOptions {
Expand All @@ -16,7 +18,7 @@
export class EmulatorUI implements EmulatorInstance {
constructor(private args: EmulatorUIOptions) {}

start(): Promise<void> {
async start(): Promise<void> {
if (!EmulatorRegistry.isRunning(Emulators.HUB)) {
throw new FirebaseError(
`Cannot start ${Constants.description(Emulators.UI)} without ${Constants.description(
Expand All @@ -24,7 +26,7 @@
)}!`,
);
}
const { auto_download: autoDownload, projectId } = this.args;

Check failure on line 29 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

'autoDownload' is assigned a value but never used

Check failure on line 29 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

'autoDownload' is assigned a value but never used

Check failure on line 29 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

'autoDownload' is assigned a value but never used
const env: Partial<NodeJS.ProcessEnv> = {
LISTEN: JSON.stringify(ExpressBasedEmulator.listenOptionsFromSpecs(this.args.listen)),
GCLOUD_PROJECT: projectId,
Expand All @@ -39,17 +41,51 @@
const enabledExperiments = (Object.keys(ALL_EXPERIMENTS) as Array<ExperimentName>).filter(
(experimentName) => isEnabled(experimentName),
);
env[Constants.FIREBASE_ENABLED_EXPERIMENTS] = JSON.stringify(enabledExperiments);
env[Constants.FIREBASE_ENABLED_EXPERIMENTS] = JSON.stringify(enabledExperiments); // FIXME pass in GA info

return downloadableEmulators.start(Emulators.UI, { auto_download: autoDownload }, env);
// return downloadableEmulators.start(Emulators.UI, { auto_download: autoDownload }, env);
const downloadDetails = downloadableEmulators.DownloadDetails[Emulators.UI];

Check failure on line 47 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

'downloadDetails' is assigned a value but never used

Check failure on line 47 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

'downloadDetails' is assigned a value but never used

Check failure on line 47 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

'downloadDetails' is assigned a value but never used
const logger = EmulatorLogger.forEmulator(Emulators.UI);

Check failure on line 48 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`

Check failure on line 48 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

'logger' is assigned a value but never used

Check failure on line 48 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Insert `··`

Check failure on line 48 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

'logger' is assigned a value but never used

Check failure on line 48 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`

Check failure on line 48 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

'logger' is assigned a value but never used
await downloadableEmulators.downloadIfNecessary(Emulators.UI);

Check failure on line 49 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`

Check failure on line 49 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Insert `··`

Check failure on line 49 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`

// FIXME check CI

Check failure on line 51 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `···`

Check failure on line 51 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Insert `···`

Check failure on line 51 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `···`
// const hasEmulator = fs.existsSync(getExecPath(Emulators.UI));

Check failure on line 52 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`

Check failure on line 52 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Insert `··`

Check failure on line 52 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`
// if (!hasEmulator || downloadDetails.opts.skipCache) {

Check failure on line 53 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`

Check failure on line 53 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Insert `··`

Check failure on line 53 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`
// if (args.auto_download) {

Check failure on line 54 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`

Check failure on line 54 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Insert `··`

Check failure on line 54 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`
// if (process.env.CI) {

Check failure on line 55 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`

Check failure on line 55 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Insert `··`

Check failure on line 55 in src/emulator/ui.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `··`
// utils.logWarning(
// `It appears you are running in a CI environment. You can avoid downloading the ${Constants.description(
// targetName,
// )} repeatedly by caching the ${downloadDetails.opts.cacheDir} directory.`,
// );
// }

// await downloadEmulator(targetName);
// } else {
// utils.logWarning("Setup required, please run: firebase setup:emulators:" + targetName);
// throw new FirebaseError("emulator not found");
// }
// }

// const command = _getCommand(targetName, args);
// logger.log(
// "DEBUG",
// `Starting ${Constants.description(targetName)} with command ${JSON.stringify(command)}`,
// );
// return _runBinary(emulator, command, extraEnv);
// FIXME do some logging probs

// FIXME consider the host may be different? Should take it from the config methinks
//export function createApp(zipDirPath: string, env : ("DEV" | "PROD"), projectId : string, host : string | undefined, port: string | undefined, hubHost: string ) : Promise<express.Express> {
await createApp("path", "PROD", projectId, "127.0.0.1", Constants.getDefaultPort(Emulators.UI), EmulatorRegistry.url(Emulators.HUB).host);
}

connect(): Promise<void> {
return Promise.resolve();
}

stop(): Promise<void> {
return downloadableEmulators.stop(Emulators.UI);
return downloadableEmulators.stop(Emulators.UI); // FIXME consider stop
}

getInfo(): EmulatorInfo {
Expand Down
120 changes: 120 additions & 0 deletions src/emulator/ui/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { createServer } from 'http';
import type { ListenOptions } from 'net';
import * as path from 'path';

import express from 'express';
import fetch from 'node-fetch';

/*
This file defines Node.js server-side logic for the Emulator UI.

During development, the express app is loaded into the Vite dev server
(configured via ./vite.config.ts) and exposes the /api/* endpoints below.

For production, this file serves as an entry point and runs additional logic
(see `import.meta.env.PROD` below) to start the server on a port which serves
static assets in addition to APIs.

This file may NOT import any front-end code or types from src/.
*/
// FIXME note to self zipdirpath is - check server.ts in firebase-tools-ui
export function createApp(zipDirPath: string, env : ("DEV" | "PROD"), projectId : string, host : string | undefined, port: number, hubHost: string ) : Promise<express.Express> {

Check warning on line 37 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
const app = express();

// Exposes the host and port of various emulators to facilitate accessing
// them using client SDKs. For features that involve multiple emulators or
// hard to accomplish using client SDKs, consider adding an API below.
app.get(
'/api/config',
jsonHandler(async () => {
const hubDiscoveryUrl = new URL(`http://${hubHost}/emulators`);
const emulatorsRes = await fetch(hubDiscoveryUrl.toString());
Fixed Show fixed Hide fixed
const emulators = (await emulatorsRes.json()) as any;

Check warning on line 48 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value

Check warning on line 48 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

This assertion is unnecessary since it does not change the type of the expression

Check warning on line 48 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type

const json = { projectId, experiments: [], ...emulators };

Check warning on line 50 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value

// Googlers: see go/firebase-emulator-ui-usage-collection-design?pli=1#heading=h.jwz7lj6r67z8
// for more detail
if (process.env.FIREBASE_GA_SESSION) {
json.analytics = JSON.parse(process.env.FIREBASE_GA_SESSION);

Check warning on line 55 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value

Check warning on line 55 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .analytics on an `any` value
}

// pick up any experiments enabled with `firebase experiment:enable`
if (process.env.FIREBASE_ENABLED_EXPERIMENTS) {
json.experiments = JSON.parse(process.env.FIREBASE_ENABLED_EXPERIMENTS);

Check warning on line 60 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value

Check warning on line 60 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .experiments on an `any` value
}

return json;

Check warning on line 63 in src/emulator/ui/server.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe return of an `any` typed value
})
);

if (env == "PROD") {
const webDir = path.join(path.dirname(zipDirPath), '..', 'client');
app.use(express.static(webDir));
// Required for the router to work properly.
app.get('*', function (_, res) {
res.sendFile(path.join(webDir, 'index.html'));
});
Fixed Show fixed Hide fixed

let listen: ListenOptions[];
if (process.env.LISTEN) { // FIXME what is this
listen = JSON.parse(process.env.LISTEN);
} else {
// Mainly used when starting in dev mode (without CLI).
host = host || '127.0.0.1';
const portValue = Number(port) || 5173;
listen = [{ host, port: portValue }];
}
for (const opts of listen) {
const server = createServer(app).listen(opts);
server.once('listening', () => {
console.log(`Web / API server started at ${opts.host}:${opts.port}`);
});
server.once('error', (err) => {
console.error(`Failed to start server at ${opts.host}:${opts.port}`);
console.error(err);
if (opts === listen[0]) {
// If we failed to listen on the primary address, surface the error.
process.exit(1);
}
});
}
}

return Promise.resolve(app)
}
function jsonHandler(
handler: (req: express.Request) => Promise<object>
): express.Handler {
return (req, res) => {
handler(req).then(
(body) => {
res.status(200).json(body);
},
(err) => {
console.error(err);
res.status(500).json({
message: err.message,
stack: err.stack,
raw: err,
});
}
);
};
}
Loading