summaryrefslogtreecommitdiff
path: root/packages/web-util/src/serve.ts
blob: 1daea15bf541f1e51d33c3ea223c8360677903c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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);
  }
}