diff options
Diffstat (limited to 'packages/web-util/src/serve.ts')
-rw-r--r-- | packages/web-util/src/serve.ts | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/packages/web-util/src/serve.ts b/packages/web-util/src/serve.ts new file mode 100644 index 000000000..1daea15bf --- /dev/null +++ b/packages/web-util/src/serve.ts @@ -0,0 +1,133 @@ +import { Logger } from "@gnu-taler/taler-util"; +import chokidar from "chokidar"; +import express from "express"; +import https from "https"; +import http from "http"; +import { parse } from "url"; +import WebSocket from "ws"; + +import locahostCrt from "./keys/localhost.crt"; +import locahostKey from "./keys/localhost.key"; +import storiesHtml from "./stories.html"; + +import path from "path"; + +const httpServerOptions = { + key: locahostKey, + cert: locahostCrt, +}; + +const logger = new Logger("serve.ts"); + +const PATHS = { + WS: "/ws", + EXAMPLE: "/examples", + APP: "/app", +}; + +export async function serve(opts: { + folder: string; + port: number; + source?: string; + tls?: boolean; + examplesLocationJs?: string; + examplesLocationCss?: string; + onSourceUpdate?: () => Promise<void>; +}): Promise<void> { + const app = express(); + + app.use(PATHS.APP, express.static(opts.folder)); + + const httpServer = http.createServer(app); + const httpPort = opts.port; + let httpsServer: typeof httpServer | undefined; + let httpsPort: number | undefined; + const servers = [httpServer]; + if (opts.tls) { + httpsServer = https.createServer(httpServerOptions, app); + httpsPort = opts.port + 1; + servers.push(httpsServer) + } + + logger.info(`Dev server. Endpoints:`); + logger.info(` ${PATHS.APP}: where root application can be tested`); + logger.info(` ${PATHS.EXAMPLE}: where examples can be found and browse`); + logger.info(` ${PATHS.WS}: websocket for live reloading`); + + const wss = new WebSocket.Server({ noServer: true }); + + wss.on("connection", function connection(ws) { + ws.send("welcome"); + }); + + servers.forEach(function addWSHandler(server) { + server.on("upgrade", function upgrade(request, socket, head) { + const { pathname } = parse(request.url || ""); + if (pathname === PATHS.WS) { + wss.handleUpgrade(request, socket, head, function done(ws) { + wss.emit("connection", ws, request); + }); + } else { + socket.destroy(); + } + }); + }); + + const sendToAllClients = function (data: object): void { + wss.clients.forEach(function each(client) { + if (client.readyState === WebSocket.OPEN) { + client.send(JSON.stringify(data)); + } + }); + }; + const watchingFolder = opts.source ?? opts.folder; + logger.info(`watching ${watchingFolder} for changes`); + + chokidar.watch(watchingFolder).on("change", (path, stats) => { + logger.info(`changed: ${path}`); + + if (opts.onSourceUpdate) { + sendToAllClients({ type: "file-updated-start", data: { path } }); + opts + .onSourceUpdate() + .then((result) => { + sendToAllClients({ + type: "file-updated-done", + data: { path, result }, + }); + }) + .catch((error) => { + sendToAllClients({ + type: "file-updated-failed", + data: { path, error: JSON.stringify(error) }, + }); + }); + } else { + sendToAllClients({ type: "file-change", data: { path } }); + } + }); + + if (opts.onSourceUpdate) opts.onSourceUpdate(); + + app.get(PATHS.EXAMPLE, function (req: any, res: any) { + res.set("Content-Type", "text/html"); + res.send( + storiesHtml + .replace( + "__EXAMPLES_JS_FILE_LOCATION__", + opts.examplesLocationJs ?? `.${PATHS.APP}/stories.js`, + ) + .replace( + "__EXAMPLES_CSS_FILE_LOCATION__", + opts.examplesLocationCss ?? `.${PATHS.APP}/stories.css`, + ), + ); + }); + + logger.info(`Serving ${opts.folder} on ${httpPort}: plain HTTP`); + httpServer.listen(httpPort); + if (httpsServer !== undefined) { + logger.info(`Serving ${opts.folder} on ${httpsPort}: HTTP + TLS`); + httpsServer.listen(httpsPort); + } +} |