summaryrefslogtreecommitdiff
path: root/packages/web-util/src/serve.ts
blob: 11cc6db39ea72277271f76bb954d4ceabb045eeb (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
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)
  server.listen(opts.port);
  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.trace(`changed ${path}`)

      sendToAllClients({ type: 'file-updated-start', data: { path } })
      if (opts.onUpdate) {
        opts.onUpdate().then(result => {
          sendToAllClients({ type: 'file-updated-done', data: { path, result } })
        })
      } else {
        sendToAllClients({ type: 'file-change-done', data: { path } })
      }
    })

    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')
    })

  }
}