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;