aboutsummaryrefslogtreecommitdiff
path: root/packages/web-util/src/serve.ts
blob: f3a97e2e29e125b028bd017706356f569a3709d8 (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
import { Logger } from "@gnu-taler/taler-util";
import chokidar from "chokidar";
import express from "express";
import https from "https";
import { parse } from "url";
import WebSocket, { Server } 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",
  NOTIFY: "/notify",
  EXAMPLE: "/examples",
  APP: "/app",
};

export async function serve(opts: {
  folder: string;
  port: number;
  source?: string;
  development?: boolean;
  examplesLocationJs?: string;
  examplesLocationCss?: string;
  onUpdate?: () => Promise<void>;
}): Promise<void> {
  const app = express();

  app.use(PATHS.APP, express.static(opts.folder));
  const server = https.createServer(httpServerOptions, app);
  logger.info(`serving ${opts.folder} on ${opts.port}`);
  logger.info(`  ${PATHS.APP}: application`);
  logger.info(`  ${PATHS.EXAMPLE}: examples`);
  logger.info(`  ${PATHS.WS}: websocket`);
  logger.info(`  ${PATHS.NOTIFY}: broadcast`);

  if (opts.development) {
    const wss = new Server({ noServer: true });

    wss.on("connection", function connection(ws) {
      ws.send("welcome");
    });

    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 change`);

    chokidar.watch(watchingFolder).on("change", (path, stats) => {
      logger.info(`changed ${path}`);

      if (opts.onUpdate) {
        sendToAllClients({ type: "file-updated-start", data: { path } });
        opts
          .onUpdate()
          .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.onUpdate) opts.onUpdate();

    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`,
          ),
      );
    });

    app.get(PATHS.NOTIFY, function (req: any, res: any) {
      res.send("ok");
    });

    server.listen(opts.port);
  }
}