aboutsummaryrefslogtreecommitdiff
path: root/packages/web-util/src/serve.ts
blob: f37ef90ce5de61a6c619374b9c2a44081628fb2f (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
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",
  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 servers = [
    http.createServer(app),
    https.createServer(httpServerOptions, app),
  ];
  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 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 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");
    });

    servers.forEach(function startServer(server, index) {
      logger.info(`serving ${opts.folder} on ${opts.port + index}`);
      server.listen(opts.port + index);
    });
  }
}