server.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  6. var _fs = require('fs');
  7. var _fs2 = _interopRequireDefault(_fs);
  8. var _http = require('http');
  9. var _http2 = _interopRequireDefault(_http);
  10. var _https = require('https');
  11. var _https2 = _interopRequireDefault(_https);
  12. var _events = require('events');
  13. var _events2 = _interopRequireDefault(_events);
  14. var _url = require('url');
  15. var _client = require('./client');
  16. var _client2 = _interopRequireDefault(_client);
  17. var _package = require('../package.json');
  18. var _package2 = _interopRequireDefault(_package);
  19. var _any = require('body/any');
  20. var _any2 = _interopRequireDefault(_any);
  21. var _qs = require('qs');
  22. var _qs2 = _interopRequireDefault(_qs);
  23. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  24. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  25. function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  26. function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
  27. var debug = require('debug')('tinylr:server');
  28. var CONTENT_TYPE = 'content-type';
  29. var FORM_TYPE = 'application/x-www-form-urlencoded';
  30. function buildRootPath() {
  31. var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '/';
  32. var rootUrl = prefix;
  33. // Add trailing slash
  34. if (prefix[prefix.length - 1] !== '/') {
  35. rootUrl = rootUrl + '/';
  36. }
  37. // Add leading slash
  38. if (prefix[0] !== '/') {
  39. rootUrl = '/' + rootUrl;
  40. }
  41. return rootUrl;
  42. }
  43. var Server = function (_events$EventEmitter) {
  44. _inherits(Server, _events$EventEmitter);
  45. function Server() {
  46. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  47. _classCallCheck(this, Server);
  48. var _this = _possibleConstructorReturn(this, (Server.__proto__ || Object.getPrototypeOf(Server)).call(this));
  49. _this.options = options;
  50. options.livereload = options.livereload || require.resolve('livereload-js/dist/livereload.js');
  51. // todo: change falsy check to allow 0 for random port
  52. options.port = parseInt(options.port || 35729, 10);
  53. if (options.errorListener) {
  54. _this.errorListener = options.errorListener;
  55. }
  56. _this.rootPath = buildRootPath(options.prefix);
  57. _this.clients = {};
  58. _this.configure(options.app);
  59. _this.routes(options.app);
  60. return _this;
  61. }
  62. _createClass(Server, [{
  63. key: 'routes',
  64. value: function routes() {
  65. if (!this.options.dashboard) {
  66. this.on('GET ' + this.rootPath, this.index.bind(this));
  67. }
  68. this.on('GET ' + this.rootPath + 'changed', this.changed.bind(this));
  69. this.on('POST ' + this.rootPath + 'changed', this.changed.bind(this));
  70. this.on('POST ' + this.rootPath + 'alert', this.alert.bind(this));
  71. this.on('GET ' + this.rootPath + 'livereload.js', this.livereload.bind(this));
  72. this.on('GET ' + this.rootPath + 'kill', this.close.bind(this));
  73. }
  74. }, {
  75. key: 'configure',
  76. value: function configure(app) {
  77. var _this2 = this;
  78. debug('Configuring %s', app ? 'connect / express application' : 'HTTP server');
  79. var handler = this.options.handler || this.handler;
  80. if (!app) {
  81. if (this.options.key && this.options.cert || this.options.pfx) {
  82. this.server = _https2.default.createServer(this.options, handler.bind(this));
  83. } else {
  84. this.server = _http2.default.createServer(handler.bind(this));
  85. }
  86. this.server.on('upgrade', this.websocketify.bind(this));
  87. this.server.on('error', this.error.bind(this));
  88. return this;
  89. }
  90. this.app = app;
  91. this.app.listen = function (port, done) {
  92. done = done || function () {};
  93. if (port !== _this2.options.port) {
  94. debug('Warn: LiveReload port is not standard (%d). You are listening on %d', _this2.options.port, port);
  95. debug('You\'ll need to rely on the LiveReload snippet');
  96. debug('> http://feedback.livereload.com/knowledgebase/articles/86180-how-do-i-add-the-script-tag-manually-');
  97. }
  98. var srv = _this2.server = _http2.default.createServer(app);
  99. srv.on('upgrade', _this2.websocketify.bind(_this2));
  100. srv.on('error', _this2.error.bind(_this2));
  101. srv.on('close', _this2.close.bind(_this2));
  102. return srv.listen(port, done);
  103. };
  104. return this;
  105. }
  106. }, {
  107. key: 'handler',
  108. value: function handler(req, res, next) {
  109. var _this3 = this;
  110. var middleware = typeof next === 'function';
  111. debug('LiveReload handler %s (middleware: %s)', req.url, middleware ? 'on' : 'off');
  112. next = next || this.defaultHandler.bind(this, res);
  113. req.headers[CONTENT_TYPE] = req.headers[CONTENT_TYPE] || FORM_TYPE;
  114. return (0, _any2.default)(req, res, function (err, body) {
  115. if (err) return next(err);
  116. req.body = body;
  117. if (!req.query) {
  118. req.query = req.url.indexOf('?') !== -1 ? _qs2.default.parse((0, _url.parse)(req.url).query) : {};
  119. }
  120. return _this3.handle(req, res, next);
  121. });
  122. }
  123. }, {
  124. key: 'index',
  125. value: function index(req, res) {
  126. res.setHeader('Content-Type', 'application/json');
  127. res.write(JSON.stringify({
  128. tinylr: 'Welcome',
  129. version: _package2.default.version
  130. }));
  131. res.end();
  132. }
  133. }, {
  134. key: 'handle',
  135. value: function handle(req, res, next) {
  136. var url = (0, _url.parse)(req.url);
  137. debug('Request:', req.method, url.href);
  138. var middleware = typeof next === 'function';
  139. // do the routing
  140. var route = req.method + ' ' + url.pathname;
  141. var respond = this.emit(route, req, res);
  142. if (respond) return;
  143. if (middleware) return next();
  144. // Only apply content-type on non middleware setup #70
  145. return this.notFound(res);
  146. }
  147. }, {
  148. key: 'defaultHandler',
  149. value: function defaultHandler(res, err) {
  150. if (!err) return this.notFound(res);
  151. this.error(err);
  152. res.setHeader('Content-Type', 'text/plain');
  153. res.statusCode = 500;
  154. res.end('Error: ' + err.stack);
  155. }
  156. }, {
  157. key: 'notFound',
  158. value: function notFound(res) {
  159. res.setHeader('Content-Type', 'application/json');
  160. res.writeHead(404);
  161. res.write(JSON.stringify({
  162. error: 'not_found',
  163. reason: 'no such route'
  164. }));
  165. res.end();
  166. }
  167. }, {
  168. key: 'websocketify',
  169. value: function websocketify(req, socket, head) {
  170. var _this4 = this;
  171. var client = new _client2.default(req, socket, head, this.options);
  172. this.clients[client.id] = client;
  173. // handle socket error to prevent possible app crash, such as ECONNRESET
  174. socket.on('error', function (e) {
  175. // ignore frequent ECONNRESET error (seems inevitable when refresh)
  176. if (e.code === 'ECONNRESET') return;
  177. _this4.error(e);
  178. });
  179. client.once('info', function (data) {
  180. debug('Create client %s (url: %s)', data.id, data.url);
  181. _this4.emit('MSG /create', data.id, data.url);
  182. });
  183. client.once('end', function () {
  184. debug('Destroy client %s (url: %s)', client.id, client.url);
  185. _this4.emit('MSG /destroy', client.id, client.url);
  186. delete _this4.clients[client.id];
  187. });
  188. }
  189. }, {
  190. key: 'listen',
  191. value: function listen(port, host, fn) {
  192. port = port || this.options.port;
  193. // Last used port for error display
  194. this.port = port;
  195. if (typeof host === 'function') {
  196. fn = host;
  197. host = undefined;
  198. }
  199. this.server.listen(port, host, fn);
  200. }
  201. }, {
  202. key: 'close',
  203. value: function close(req, res) {
  204. Object.keys(this.clients).forEach(function (id) {
  205. this.clients[id].close();
  206. }, this);
  207. if (this.server._handle) this.server.close(this.emit.bind(this, 'close'));
  208. if (res) res.end();
  209. }
  210. }, {
  211. key: 'error',
  212. value: function error(e) {
  213. if (this.errorListener) {
  214. this.errorListener(e);
  215. return;
  216. }
  217. console.error();
  218. if (typeof e === 'undefined') {
  219. console.error('... Uhoh. Got error %s ...', e);
  220. } else {
  221. console.error('... Uhoh. Got error %s ...', e.message);
  222. console.error(e.stack);
  223. if (e.code !== 'EADDRINUSE') return;
  224. console.error();
  225. console.error('You already have a server listening on %s', this.port);
  226. console.error('You should stop it and try again.');
  227. console.error();
  228. }
  229. }
  230. // Routes
  231. }, {
  232. key: 'livereload',
  233. value: function livereload(req, res) {
  234. res.setHeader('Content-Type', 'application/javascript');
  235. _fs2.default.createReadStream(this.options.livereload).pipe(res);
  236. }
  237. }, {
  238. key: 'changed',
  239. value: function changed(req, res) {
  240. var files = this.param('files', req);
  241. debug('Changed event (Files: %s)', files.join(' '));
  242. var clients = this.notifyClients(files);
  243. if (!res) return;
  244. res.setHeader('Content-Type', 'application/json');
  245. res.write(JSON.stringify({
  246. clients: clients,
  247. files: files
  248. }));
  249. res.end();
  250. }
  251. }, {
  252. key: 'alert',
  253. value: function alert(req, res) {
  254. var message = this.param('message', req);
  255. debug('Alert event (Message: %s)', message);
  256. var clients = this.alertClients(message);
  257. if (!res) return;
  258. res.setHeader('Content-Type', 'application/json');
  259. res.write(JSON.stringify({
  260. clients: clients,
  261. message: message
  262. }));
  263. res.end();
  264. }
  265. }, {
  266. key: 'notifyClients',
  267. value: function notifyClients(files) {
  268. var clients = Object.keys(this.clients).map(function (id) {
  269. var client = this.clients[id];
  270. debug('Reloading client %s (url: %s)', client.id, client.url);
  271. client.reload(files);
  272. return {
  273. id: client.id,
  274. url: client.url
  275. };
  276. }, this);
  277. return clients;
  278. }
  279. }, {
  280. key: 'alertClients',
  281. value: function alertClients(message) {
  282. var clients = Object.keys(this.clients).map(function (id) {
  283. var client = this.clients[id];
  284. debug('Alert client %s (url: %s)', client.id, client.url);
  285. client.alert(message);
  286. return {
  287. id: client.id,
  288. url: client.url
  289. };
  290. }, this);
  291. return clients;
  292. }
  293. // Lookup param from body / params / query.
  294. }, {
  295. key: 'param',
  296. value: function param(name, req) {
  297. var param = void 0;
  298. if (req.body && req.body[name]) param = req.body[name];else if (req.params && req.params[name]) param = req.params[name];else if (req.query && req.query[name]) param = req.query[name];
  299. // normalize files array
  300. if (name === 'files') {
  301. param = Array.isArray(param) ? param : typeof param === 'string' ? param.split(/[\s,]/) : [];
  302. }
  303. return param;
  304. }
  305. }]);
  306. return Server;
  307. }(_events2.default.EventEmitter);
  308. exports.default = Server;
  309. module.exports = exports['default'];