mirror of https://github.com/embarklabs/embark.git
refactor(@embark/api): in dev use cockpit redirect instead of proxy
Embark API server's development proxy from port 55555 to 3000 was attempting to inappropriately forward an `/embark-api/` endpoint for the blockchain process logs to Create React App's development server. Why it was only happening for the one endpoint is not known but probably has to do with timing around registration of the API server's express routes. The problem can be fixed with a one-line `filter:` function in the options for `express-http-proxy`. However, it was realized that to fix an unrelated problem, whereby the proxy doesn't forward websockets to CRA such that hot reload doesn't work when accessing `embark-ui` in development on port 55555, a switch to `http-proxy-middleware` would be required. That was quickly attempted (easy switch) but there are outstanding [difficulties][bug] with `webpack-dev-server` and `node-http-proxy` that cause CRA to crash. Switch strategies and refactor the API module to serve a page on port 55555 (in development only) that alerts the developer `embark-ui` should be accessed on port 3000. The page redirects (client-side) after 10 seconds, with URL query params and/or hash preserved. A future version could instead do client-side polling of port 3000 with `fetch` and then redirect only once it's available. The reason for not redirecting immediately is that the intermediate page makes it more obvious what needs to be done, e.g. CRA dev server may need to be started with `yarn start`. [bug]: https://github.com/webpack/webpack-dev-server/issues/1642
This commit is contained in:
parent
3988fb4c8a
commit
a39c2c82d7
|
@ -191,7 +191,6 @@
|
||||||
"@types/body-parser": "1.17.0",
|
"@types/body-parser": "1.17.0",
|
||||||
"@types/cors": "2.8.4",
|
"@types/cors": "2.8.4",
|
||||||
"@types/express": "4.16.0",
|
"@types/express": "4.16.0",
|
||||||
"@types/express-http-proxy": "1.5.1",
|
|
||||||
"@types/express-ws": "3.0.0",
|
"@types/express-ws": "3.0.0",
|
||||||
"@types/find-up": "2.1.0",
|
"@types/find-up": "2.1.0",
|
||||||
"@types/globule": "1.1.3",
|
"@types/globule": "1.1.3",
|
||||||
|
|
|
@ -2,7 +2,6 @@ import bodyParser from "body-parser";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import {Embark, Plugins} from "embark";
|
import {Embark, Plugins} from "embark";
|
||||||
import express, {NextFunction, Request, Response} from "express";
|
import express, {NextFunction, Request, Response} from "express";
|
||||||
import proxy from "express-http-proxy";
|
|
||||||
import expressWs from "express-ws";
|
import expressWs from "express-ws";
|
||||||
import findUp from "find-up";
|
import findUp from "find-up";
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
|
@ -87,15 +86,12 @@ export default class Server {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private makePage(reloadSeconds: number, body: string) {
|
private makePage(body: string) {
|
||||||
return (`
|
return (`
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
${this.isInsideMonorepo ? `
|
|
||||||
<meta http-equiv="refresh" content="${reloadSeconds}">
|
|
||||||
` : ""}
|
|
||||||
<title>Embark API Server</title>
|
<title>Embark API Server</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
code {
|
code {
|
||||||
|
@ -112,47 +108,78 @@ export default class Server {
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
${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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`.trim().split("\n").map((str) => str.trim()).filter((str) => str).join("\n"));
|
`.trim().split("\n").map((str) => str.trim()).filter((str) => str).join("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private makePage404(reloadSeconds: number, envReport: string, inside: string, notice: string) {
|
private makePage404(reloadSeconds: number, envReport: string, inside: string, notice: string) {
|
||||||
return this.makePage(reloadSeconds, `
|
return this.makePage(`
|
||||||
${envReport}
|
${envReport}
|
||||||
<p>missing build for package <code>embark-ui</code> ${inside}</p>
|
<p>missing build for package <code>embark-ui</code> ${inside}</p>
|
||||||
${notice}
|
${notice}
|
||||||
|
${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");
|
||||||
|
const timer = window.setInterval(() => {
|
||||||
|
if (timeLeft >= 1) {
|
||||||
|
timeLeft -= 1;
|
||||||
|
span.innerText = \`\${timeLeft}\`;
|
||||||
|
}
|
||||||
|
if (!timeLeft) {
|
||||||
|
window.clearInterval(timer);
|
||||||
|
window.location.reload(true);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
</script>
|
||||||
|
` : ""}
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private makePageEConnError(reloadSeconds: number, waitingFor: string) {
|
private makePage503(redirectSeconds: number) {
|
||||||
return this.makePage(reloadSeconds, `
|
return this.makePage(`
|
||||||
<p><code>lib/modules/api/server</code> inside the monorepo at
|
<p><code>lib/modules/api/server</code> is inside the monorepo at
|
||||||
<code>${path.join(this.monorepoRootDir, "packages/embark")}</code> is
|
<code>${path.join(this.monorepoRootDir, "packages/embark")}</code></p>
|
||||||
waiting for the Create React App development server of package
|
<p>to access <code>embark-ui</code> in development use port
|
||||||
<code>embark-ui</code> to ${waitingFor} at
|
<code>3000</code></p>
|
||||||
<code>localhost:55555</code></p>
|
<p>if you haven't already, please run either:</p>
|
||||||
${waitingFor === "become available" ? `
|
|
||||||
<p>please run either:</p>
|
|
||||||
<p><code>cd ${this.monorepoRootDir} && yarn start</code><br />
|
<p><code>cd ${this.monorepoRootDir} && yarn start</code><br />
|
||||||
or<br />
|
or<br />
|
||||||
<code>cd ${path.join(this.monorepoRootDir, "packages/embark-ui")}
|
<code>cd ${path.join(this.monorepoRootDir, "packages/embark-ui")} &&
|
||||||
&& yarn start</code></p>
|
yarn start</code></p>
|
||||||
<p>to instead use a static build from the monorepo, restart embark with:
|
<p>to instead use a static build from the monorepo, restart embark with:
|
||||||
<code>EMBARK_UI_STATIC=t embark run</code></p>
|
<code>EMBARK_UI_STATIC=t embark run</code></p>
|
||||||
` : ""}
|
<p>this page will automatically redirect to <a id="redirect" href=""></a>
|
||||||
|
in <span id="timer">${redirectSeconds}</span> seconds</p>
|
||||||
|
<script>
|
||||||
|
window.embarkApiRedirect = window.location.href.replace(
|
||||||
|
\`http://\${window.location.hostname}:55555\`,
|
||||||
|
\`http://\${window.location.hostname}:3000\`
|
||||||
|
);
|
||||||
|
document.querySelector("#redirect").href = window.embarkApiRedirect;
|
||||||
|
let displayLink = window.embarkApiRedirect.slice(7);
|
||||||
|
if (displayLink.endsWith(\`\${window.location.hostname}:3000/\`)) {
|
||||||
|
displayLink = displayLink.slice(0, -1);
|
||||||
|
}
|
||||||
|
document.querySelector("#redirect").innerText = displayLink;
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
let timeLeft = ${redirectSeconds};
|
||||||
|
const span = document.querySelector("#timer");
|
||||||
|
const timer = window.setInterval(() => {
|
||||||
|
if (timeLeft >= 1) {
|
||||||
|
timeLeft -= 1;
|
||||||
|
span.innerText = \`\${timeLeft}\`;
|
||||||
|
}
|
||||||
|
if (!timeLeft) {
|
||||||
|
window.clearInterval(timer);
|
||||||
|
window.location.href = window.embarkApiRedirect;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
</script>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +222,7 @@ export default class Server {
|
||||||
if (!this.isInsideMonorepo || process.env.EMBARK_UI_STATIC) {
|
if (!this.isInsideMonorepo || process.env.EMBARK_UI_STATIC) {
|
||||||
if (existsSync(path.join(this.embarkUiBuildDir, "index.html"))) {
|
if (existsSync(path.join(this.embarkUiBuildDir, "index.html"))) {
|
||||||
instance.app.use("/", express.static(this.embarkUiBuildDir));
|
instance.app.use("/", express.static(this.embarkUiBuildDir));
|
||||||
instance.app.get("/*", (_req, res) => {
|
instance.app.get(/^\/(?!embark-api).*$/, (_req, res) => {
|
||||||
res.sendFile(path.join(this.embarkUiBuildDir, "index.html"));
|
res.sendFile(path.join(this.embarkUiBuildDir, "index.html"));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -204,7 +231,9 @@ export default class Server {
|
||||||
in <code>${path.dirname(this.embarkUiBuildDir)}</code>
|
in <code>${path.dirname(this.embarkUiBuildDir)}</code>
|
||||||
`;
|
`;
|
||||||
let notice = `
|
let notice = `
|
||||||
<p>this distribution of <code>embark-ui</code> appears to be broken</p>
|
<p>this distribution of <code>embark-ui</code> appears to be broken,
|
||||||
|
please <a href="https://github.com/embark-framework/embark/issues">
|
||||||
|
file an issue</a></p>
|
||||||
`;
|
`;
|
||||||
if (this.isInsideMonorepo) {
|
if (this.isInsideMonorepo) {
|
||||||
envReport = `
|
envReport = `
|
||||||
|
@ -223,40 +252,24 @@ export default class Server {
|
||||||
&& yarn build</code></p>
|
&& yarn build</code></p>
|
||||||
<p>restart <code>embark run</code> after building
|
<p>restart <code>embark run</code> after building
|
||||||
<code>embark-ui</code></p>
|
<code>embark-ui</code></p>
|
||||||
<p>to instead use a live development build from the monorepo, unset
|
<p>to instead use a live development build from the monorepo: unset
|
||||||
the environment variable <code>EMBARK_UI_STATIC</code> and restart
|
the environment variable <code>EMBARK_UI_STATIC</code>, restart
|
||||||
embark</p>
|
embark, and visit
|
||||||
|
<a href="http://localhost:3000">http://localhost:3000</a></p>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
const page404 = this.makePage404(3, envReport, inside, notice);
|
const page404 = this.makePage404(10, envReport, inside, notice);
|
||||||
const missingBuildHandler = (_req: Request, res: Response) => {
|
const missingBuildHandler = (_req: Request, res: Response) => {
|
||||||
res.status(404).send(page404);
|
res.status(404).send(page404);
|
||||||
};
|
};
|
||||||
instance.app.get("/", missingBuildHandler);
|
instance.app.get(/^\/(?!embark-api).*$/, missingBuildHandler);
|
||||||
instance.app.get("/*", missingBuildHandler);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const page503 = this.makePageEConnError(3, "become available");
|
const page503 = this.makePage503(10);
|
||||||
const page504 = this.makePageEConnError(3, "become responsive");
|
const unavailableBuildHandler = (_req: Request, res: Response) => {
|
||||||
instance.app.use("/", proxy("http://localhost:3000", {
|
res.status(503).send(page503);
|
||||||
// @ts-ignore
|
};
|
||||||
proxyErrorHandler: (err, res, next) => {
|
instance.app.get(/^\/(?!embark-api).*$/, unavailableBuildHandler);
|
||||||
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,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
|
|
|
@ -2723,14 +2723,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
|
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
|
||||||
integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==
|
integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==
|
||||||
|
|
||||||
"@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@*":
|
"@types/express-serve-static-core@*":
|
||||||
version "4.16.0"
|
version "4.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz#fdfe777594ddc1fe8eb8eccce52e261b496e43e7"
|
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz#fdfe777594ddc1fe8eb8eccce52e261b496e43e7"
|
||||||
|
|
Loading…
Reference in New Issue