126 lines
3.4 KiB
TypeScript
126 lines
3.4 KiB
TypeScript
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;
|
|
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;
|
|
const httpsServer = https.createServer(httpServerOptions, app);
|
|
const httpsPort = opts.port + 1;
|
|
const servers = [httpServer, 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 },
|
|
});
|
|
});
|
|
} 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);
|
|
logger.info(`Serving ${opts.folder} on ${httpsPort}: HTTP + TLS`);
|
|
httpsServer.listen(httpsPort);
|
|
}
|