mirror of
https://github.com/embarklabs/embark.git
synced 2025-01-11 06:16:01 +00:00
refactor(@embark/api): catch-all route, fallback pages for Cockpit
Implement a `/*` server-side catch-all route for Cockpit that loads Cockpit's `index.html`. This change is necessary with intent to disable offline-first behavior in production builds of Cockpit. Cockpit's service worker effectively translates server-side route unavailability into client-side behaviors of connected-react-router. When the service worker is unregistered the same will be accomplished via the server-side catch-all route. Implement fallback pages for when embark is in the monorepo but Cockpit's Create React App development server isn't yet started or isn't yet responsive. Implement a fallback page for when the static build of Cockpit is missing. When embark is in the monorepo, give instructions for building Cockpit. Otherwise, report that the distribution is broken. Deprecate the environment variable `EMBARK_DEVELOPMENT` in favor of `EMBARK_UI_STATIC`. Unless the latter is truthy at runtime, when embark is in the monorepo the CRA dev server of Cockpit will be accessible at `localhost:55555` via proxied requests to `localhost:3000`. The deprecation is not a breaking change as `EMBARK_DEVELOPMENT` / `EMBARK_UI_STATIC` are not relevant to normal users, but only to developers working on embark itself. Bump `express-http-proxy` to the latest version.
This commit is contained in:
parent
e4d1e4ea87
commit
9829e925b4
@ -96,7 +96,7 @@
|
||||
"ethereumjs-util": "6.0.0",
|
||||
"ethereumjs-wallet": "0.6.0",
|
||||
"express": "4.16.3",
|
||||
"express-http-proxy": "1.5.0",
|
||||
"express-http-proxy": "1.5.1",
|
||||
"express-ws": "4.0.0",
|
||||
"file-loader": "2.0.0",
|
||||
"find-up": "2.1.0",
|
||||
@ -189,7 +189,7 @@
|
||||
"@types/body-parser": "1.17.0",
|
||||
"@types/cors": "2.8.4",
|
||||
"@types/express": "4.16.0",
|
||||
"@types/express-http-proxy": "1.5.0",
|
||||
"@types/express-http-proxy": "1.5.1",
|
||||
"@types/express-ws": "3.0.0",
|
||||
"@types/find-up": "2.1.0",
|
||||
"@types/globule": "1.1.3",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import bodyParser from "body-parser";
|
||||
import cors from "cors";
|
||||
import { Embark, Plugins } from "embark";
|
||||
import express, { NextFunction, Request, Response } from "express";
|
||||
import {Embark, Plugins} from "embark";
|
||||
import express, {NextFunction, Request, Response} from "express";
|
||||
import proxy from "express-http-proxy";
|
||||
import expressWs from "express-ws";
|
||||
import findUp from "find-up";
|
||||
@ -11,7 +11,7 @@ import {__} from "i18n";
|
||||
import * as path from "path";
|
||||
import * as ws from "ws";
|
||||
// @ts-ignore
|
||||
import { embarkPath } from "../../core/fs";
|
||||
import {embarkPath, existsSync} from "../../core/fs";
|
||||
|
||||
type Method = "get" | "post" | "ws" | "delete";
|
||||
|
||||
@ -22,8 +22,14 @@ interface CallDescription {
|
||||
}
|
||||
|
||||
export default class Server {
|
||||
private isLogging: boolean = false;
|
||||
private _isInsideMonorepo: boolean | null = null;
|
||||
private _monorepoRootDir: string = "";
|
||||
private embarkUiBuildDir: string = (
|
||||
findUp.sync("node_modules/embark-ui/build", {cwd: embarkPath()}) ||
|
||||
embarkPath("node_modules/embark-ui/build")
|
||||
);
|
||||
private expressInstance: expressWs.Instance;
|
||||
private isLogging: boolean = false;
|
||||
private server?: http.Server;
|
||||
|
||||
constructor(private embark: Embark, private port: number, private hostname: string, private plugins: Plugins) {
|
||||
@ -38,6 +44,22 @@ export default class Server {
|
||||
this.isLogging = false;
|
||||
}
|
||||
|
||||
private get isInsideMonorepo() {
|
||||
if (this._isInsideMonorepo === null) {
|
||||
this._isInsideMonorepo = existsSync(embarkPath("../../packages/embark")) &&
|
||||
existsSync(embarkPath("../../lerna.json")) &&
|
||||
path.resolve(embarkPath("../../packages/embark")) === embarkPath();
|
||||
}
|
||||
return this._isInsideMonorepo;
|
||||
}
|
||||
|
||||
private get monorepoRootDir() {
|
||||
if (!this._monorepoRootDir && this.isInsideMonorepo) {
|
||||
this._monorepoRootDir = path.resolve(embarkPath("../.."));
|
||||
}
|
||||
return this._monorepoRootDir;
|
||||
}
|
||||
|
||||
public start() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (this.server) {
|
||||
@ -65,9 +87,78 @@ export default class Server {
|
||||
});
|
||||
}
|
||||
|
||||
private makePage(reloadSeconds: number, body: string) {
|
||||
return (`
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
${this.isInsideMonorepo ? `
|
||||
<meta http-equiv="refresh" content="${reloadSeconds}">
|
||||
` : ""}
|
||||
<title>Embark API Server</title>
|
||||
<style type="text/css">
|
||||
code {
|
||||
background-color: rgba(220,220,220,.5);
|
||||
}
|
||||
${this.isInsideMonorepo ? `
|
||||
#timer {
|
||||
background-color: black;
|
||||
color: yellow;
|
||||
padding: 0.25em;
|
||||
}
|
||||
` : ""}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${body}
|
||||
${this.isInsideMonorepo ? `
|
||||
<p>this page will automatically reload
|
||||
in <span id="timer">${reloadSeconds}</span> seconds</p>
|
||||
<script>
|
||||
let timeLeft = ${reloadSeconds};
|
||||
const span = document.querySelector("#timer");
|
||||
setInterval(() => {
|
||||
if (timeLeft >= 1) { timeLeft -= 1; }
|
||||
span.innerText = \`\${timeLeft}\`;
|
||||
}, 1000);
|
||||
</script>
|
||||
` : ""}
|
||||
</body>
|
||||
</html>
|
||||
`.trim().split("\n").map((str) => str.trim()).filter((str) => str).join("\n"));
|
||||
}
|
||||
|
||||
private makePage404(reloadSeconds: number, envReport: string, inside: string, notice: string) {
|
||||
return this.makePage(reloadSeconds, `
|
||||
${envReport}
|
||||
<p>missing build for package <code>embark-ui</code> ${inside}</p>
|
||||
${notice}
|
||||
`);
|
||||
}
|
||||
|
||||
private makePageEConnError(reloadSeconds: number, waitingFor: string) {
|
||||
return this.makePage(reloadSeconds, `
|
||||
<p><code>lib/modules/api/server</code> inside the monorepo at
|
||||
<code>${path.join(this.monorepoRootDir, "packages/embark")}</code> is
|
||||
waiting for the Create React App development server of package
|
||||
<code>embark-ui</code> to ${waitingFor} at
|
||||
<code>localhost:55555</code></p>
|
||||
${waitingFor === "become available" ? `
|
||||
<p>please run either:</p>
|
||||
<p><code>cd ${this.monorepoRootDir} && yarn start</code><br />
|
||||
or<br />
|
||||
<code>cd ${path.join(this.monorepoRootDir, "packages/embark-ui")}
|
||||
&& yarn start</code></p>
|
||||
<p>to instead use a static build from the monorepo, restart embark with:
|
||||
<code>EMBARK_UI_STATIC=t embark run</code></p>
|
||||
` : ""}
|
||||
`);
|
||||
}
|
||||
|
||||
private initApp() {
|
||||
const instance = expressWs(express());
|
||||
instance.app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
instance.app.use((req: Request, _res, next: NextFunction) => {
|
||||
if (!this.isLogging) {
|
||||
return next();
|
||||
}
|
||||
@ -91,7 +182,7 @@ export default class Server {
|
||||
});
|
||||
|
||||
if (this.plugins) {
|
||||
instance.app.get("/embark-api/plugins", (req: Request, res: Response) => {
|
||||
instance.app.get("/embark-api/plugins", (_req, res: Response) => {
|
||||
res.send(JSON.stringify(this.plugins.plugins.map((plugin) => ({name: plugin.name}))));
|
||||
});
|
||||
|
||||
@ -101,18 +192,73 @@ export default class Server {
|
||||
|
||||
this.embark.events.on("plugins:register:api", (callDescription: CallDescription) => this.registerCallDescription(instance, callDescription));
|
||||
|
||||
let ui: express.RequestHandler;
|
||||
if (process.env.EMBARK_DEVELOPMENT) {
|
||||
ui = proxy("http://localhost:3000");
|
||||
if (!this.isInsideMonorepo || process.env.EMBARK_UI_STATIC) {
|
||||
if (existsSync(path.join(this.embarkUiBuildDir, "index.html"))) {
|
||||
instance.app.use("/", express.static(this.embarkUiBuildDir));
|
||||
instance.app.get("/*", (_req, res) => {
|
||||
res.sendFile(path.join(this.embarkUiBuildDir, "index.html"));
|
||||
});
|
||||
} else {
|
||||
let envReport = "";
|
||||
let inside = `
|
||||
in <code>${path.dirname(this.embarkUiBuildDir)}</code>
|
||||
`;
|
||||
let notice = `
|
||||
<p>this distribution of <code>embark-ui</code> appears to be broken</p>
|
||||
`;
|
||||
if (this.isInsideMonorepo) {
|
||||
envReport = `
|
||||
<p><code>process.env.EMBARK_UI_STATIC ===
|
||||
${JSON.stringify(process.env.EMBARK_UI_STATIC)}</code></p>
|
||||
`;
|
||||
inside = `
|
||||
inside the monorepo at <code>
|
||||
${path.join(this.monorepoRootDir, "packages/embark-ui")}</code>
|
||||
`;
|
||||
notice = `
|
||||
<p>to build <code>embark-ui</code> please run either:</p>
|
||||
<p><code>cd ${this.monorepoRootDir} && yarn build</code><br />
|
||||
or<br />
|
||||
<code>cd ${path.join(this.monorepoRootDir, "packages/embark-ui")}
|
||||
&& yarn build</code></p>
|
||||
<p>restart <code>embark run</code> after building
|
||||
<code>embark-ui</code></p>
|
||||
<p>to instead use a live development build from the monorepo, unset
|
||||
the environment variable <code>EMBARK_UI_STATIC</code> and restart
|
||||
embark</p>
|
||||
`;
|
||||
}
|
||||
const page404 = this.makePage404(3, envReport, inside, notice);
|
||||
const missingBuildHandler = (_req: Request, res: Response) => {
|
||||
res.status(404).send(page404);
|
||||
};
|
||||
instance.app.get("/", missingBuildHandler);
|
||||
instance.app.get("/*", missingBuildHandler);
|
||||
}
|
||||
} else {
|
||||
ui = express.static(
|
||||
findUp.sync("node_modules/embark-ui/build", {cwd: embarkPath()}) ||
|
||||
embarkPath("node_modules/embark-ui/build"),
|
||||
);
|
||||
const page503 = this.makePageEConnError(3, "become available");
|
||||
const page504 = this.makePageEConnError(3, "become responsive");
|
||||
instance.app.use("/", proxy("http://localhost:3000", {
|
||||
// @ts-ignore
|
||||
proxyErrorHandler: (err, res, next) => {
|
||||
switch (err && err.code) {
|
||||
case "ECONNREFUSED": {
|
||||
return res.status(503).send(page503);
|
||||
}
|
||||
case "ECONNRESET": {
|
||||
if (err.message === "socket hang up") {
|
||||
return res.status(504).send(page504);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
},
|
||||
timeout: 1000,
|
||||
}));
|
||||
}
|
||||
|
||||
instance.app.use("/", ui);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
17
yarn.lock
17
yarn.lock
@ -2716,12 +2716,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
|
||||
integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==
|
||||
|
||||
"@types/express-http-proxy@1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-http-proxy/-/express-http-proxy-1.5.0.tgz#0543348e5845c4c87f6e17a834f4962a75ea4579"
|
||||
integrity sha512-gPo4mt1//QMitFvyw0AXrjtmp1xrGQtxLNJdRcXD4QkTv4RvEFXU01/8lYlFTsNr8AL/10QyRdrBeC9Ow9WuSA==
|
||||
"@types/express-http-proxy@1.5.1":
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-http-proxy/-/express-http-proxy-1.5.1.tgz#0184017b1cfc8ab2a4954d35f90c9b4cc3d7ffcc"
|
||||
integrity sha512-9SOGqwVzbudT5nzF4TjKOu0cWE0HRaTVVivwxUxYMN/7mas6Wt/W5pz53dZIs7Y0fZBjAI3RTDDr+dXtXrv+hA==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
"@types/express-serve-static-core" "*"
|
||||
|
||||
"@types/express-serve-static-core@*":
|
||||
version "4.16.0"
|
||||
@ -8542,10 +8543,10 @@ expect@^23.6.0:
|
||||
jest-message-util "^23.4.0"
|
||||
jest-regex-util "^23.3.0"
|
||||
|
||||
express-http-proxy@1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/express-http-proxy/-/express-http-proxy-1.5.0.tgz#27aca3898e2f0cb9aff2e1d00c93ec27014254a2"
|
||||
integrity sha512-rYXjOj+ldSDZdmCxRDX/7o6Oxtz45sS9l4QTsvqm+ZFxqI5xTA4usMMP4FBrrKTpDPuQkI2YVda+0LvkJhPu7A==
|
||||
express-http-proxy@1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/express-http-proxy/-/express-http-proxy-1.5.1.tgz#cbf45695c759693c9c5f946117462d25b57e77a8"
|
||||
integrity sha512-k1RdysZWZ8wdPnsLa4iyrrYyUFih/sYKkn6WfkU/q5A8eUdh3l+oXhrRuQmEYEsZmiexVvpiOCkogl03jYfcbg==
|
||||
dependencies:
|
||||
debug "^3.0.1"
|
||||
es6-promise "^4.1.1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user