/ lib / modules / webserver / server.js
server.js
  1  const async = require('async');
  2  let serveStatic = require('serve-static');
  3  const {canonicalHost, defaultHost, dockerHostSwap} = require('../../utils/host');
  4  const expressWebSocket = require('express-ws');
  5  const express = require('express');
  6  const fs = require('../../core/fs');
  7  require('http-shutdown').extend();
  8  var cors = require('cors');
  9  let path = require('path');
 10  var bodyParser = require('body-parser');
 11  const helmet = require('helmet');
 12  
 13  class Server {
 14    constructor(options) {
 15      this.logger = options.logger;
 16      this.buildDir = options.buildDir;
 17      this.events = options.events;
 18      this.port = options.port || 8000;
 19      this.dist = options.dist || 'dist/';
 20      this.hostname = dockerHostSwap(options.host) || defaultHost;
 21      this.isFirstStart = true;
 22      this.opened = false;
 23      this.openBrowser = options.openBrowser;
 24      this.logging = false;
 25      this.plugins = options.plugins;
 26      this.enableCatchAll = options.enableCatchAll;
 27  
 28      this.events.once('outputDone', () => {
 29        this.logger.info(this._getMessage());
 30      });
 31    }
 32  
 33    enableLogging(callback) {
 34      this.logging = true;
 35      return callback(null, __("Enabled Webserver Logs"));
 36    }
 37  
 38    disableLogging(callback) {
 39      this.logging = false;
 40      return callback(null, __("Disabled Webserver Logs"));
 41    }
 42  
 43    start(callback) {
 44      callback = callback || function() {};
 45      const self = this;
 46      if (this.server && this.server.listening) {
 47        let message = __("a webserver is already running at") + " " +
 48          ("http://" + canonicalHost(this.hostname) +
 49            ":" + this.port).bold.underline.green;
 50        return callback(null, message);
 51      }
 52  
 53      const coverage = serveStatic(fs.dappPath('coverage/__root__/'), {'index': ['index.html', 'index.htm']});
 54      const coverageStyle = serveStatic(fs.dappPath('coverage/'));
 55      const main = serveStatic(this.buildDir, {'index': ['index.html', 'index.htm']});
 56  
 57      this.app = express();
 58      const expressWs = expressWebSocket(this.app);
 59      // Assign Logging Function
 60      this.app.use(function(req, res, next) {
 61        if (self.logging) {
 62          if (!req.headers.upgrade) {
 63            console.log('Webserver> ' + req.method + " " + req.originalUrl);
 64          }
 65        }
 66        next();
 67      });
 68  
 69      this.app.use(helmet.noCache());
 70      this.app.use(cors());
 71      this.app.use(main);
 72      this.app.use('/coverage', coverage);
 73      this.app.use(coverageStyle);
 74  
 75      this.app.use(express.static(path.join(fs.dappPath(this.dist)), {'index': ['index.html', 'index.htm']}));
 76      this.app.use('/embark', express.static(path.join(__dirname, '../../../embark-ui/build')));
 77  
 78      this.app.use(bodyParser.json()); // support json encoded bodies
 79      this.app.use(bodyParser.urlencoded({extended: true})); // support encoded bodies
 80  
 81      this.app.ws('/logs', function(ws, _req) {
 82        self.events.on("log", function(logLevel, logMsg) {
 83          ws.send(JSON.stringify({msg: logMsg, msg_clear: logMsg.stripColors, logLevel: logLevel}), () => {});
 84        });
 85      });
 86  
 87      if (self.plugins) {
 88        let apiCalls = self.plugins.getPluginsProperty("apiCalls", "apiCalls");
 89        this.app.get('/embark-api/plugins', function(req, res) {
 90          res.send(JSON.stringify(self.plugins.plugins.map((plugin) => {
 91            return {name: plugin.name};
 92          })));
 93        });
 94  
 95        for (let apiCall of apiCalls) {
 96          this.app[apiCall.method].apply(this.app, [apiCall.endpoint, this.applyAPIFunction.bind(this, apiCall.cb)]);
 97        }
 98      }
 99      const wss = expressWs.getWss('/');
100  
101      self.events.on('outputDone', () => {
102        wss.clients.forEach(function(client) {
103          client.send('outputDone');
104        });
105      });
106  
107      self.events.on('outputError', () => {
108        wss.clients.forEach(function(client) {
109          client.send('outputError');
110        });
111      });
112  
113      this.events.on('plugins:register:api', (apiCall) => {
114        self.app[apiCall.method].apply(self.app, [apiCall.endpoint, this.applyAPIFunction.bind(this, apiCall.cb)]);
115      });
116  
117      this.app.get('/embark/*', function(req, res) {
118        self.logger.trace('webserver> GET ' + req.path);
119        res.sendFile(path.join(__dirname, '../../../embark-ui/build', 'index.html'));
120      });
121  
122      if (this.enableCatchAll === true) {
123        this.app.get('/*', function(req, res) {
124          self.logger.trace('webserver> GET ' + req.path);
125          res.sendFile(path.join(fs.dappPath(self.dist, 'index.html')));
126        });
127      }
128  
129      async.waterfall([
130        function createPlaceholderPage(next) {
131          if (!self.isFirstStart) {
132            return next();
133          }
134          self.isFirstStart = false;
135          self.events.request('build-placeholder', next);
136        },
137        function listen(next) {
138          self.server = self.app.listen(self.port, self.hostname, () => {
139            self.port = self.server.address().port;
140            next();
141          });
142        },
143        function openBrowser(next) {
144          if (!self.openBrowser || self.opened) {
145            return next();
146          }
147          self.opened = true;
148          self.events.request('open-browser', next);
149        }
150      ], function(err) {
151        if (err) {
152          return callback(err);
153        }
154  
155        callback(null, self._getMessage(), self.port);
156      });
157    }
158  
159    _getMessage() {
160      return __('webserver available at') + ' ' +
161      ('http://' + canonicalHost(this.hostname) + ':' + this.port).bold.underline.green;
162    }
163  
164    applyAPIFunction(cb, req, res) {
165      this.events.request('authenticator:authorize', req, res, (err) => {
166        if (err) {
167          const send = res.send ? res.send.bind(res) : req.send.bind(req); // WS only has the first params
168          return send(err);
169        }
170        cb(req, res);
171      });
172    }
173  
174    stop(callback) {
175      callback = callback || function () {};
176      if (!this.server || !this.server.listening) {
177        return callback(null, __("no webserver is currently running"));
178      }
179      this.server.close(function() {
180        callback(null, __("Webserver stopped"));
181      });
182    }
183  }
184  
185  module.exports = Server;