396 lines
No EOL
12 KiB
JavaScript
396 lines
No EOL
12 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
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; }; }();
|
|
|
|
var _fs = require('fs');
|
|
|
|
var _fs2 = _interopRequireDefault(_fs);
|
|
|
|
var _http = require('http');
|
|
|
|
var _http2 = _interopRequireDefault(_http);
|
|
|
|
var _https = require('https');
|
|
|
|
var _https2 = _interopRequireDefault(_https);
|
|
|
|
var _events = require('events');
|
|
|
|
var _events2 = _interopRequireDefault(_events);
|
|
|
|
var _url = require('url');
|
|
|
|
var _client = require('./client');
|
|
|
|
var _client2 = _interopRequireDefault(_client);
|
|
|
|
var _package = require('../package.json');
|
|
|
|
var _package2 = _interopRequireDefault(_package);
|
|
|
|
var _any = require('body/any');
|
|
|
|
var _any2 = _interopRequireDefault(_any);
|
|
|
|
var _qs = require('qs');
|
|
|
|
var _qs2 = _interopRequireDefault(_qs);
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
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; }
|
|
|
|
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; }
|
|
|
|
var debug = require('debug')('tinylr:server');
|
|
|
|
var CONTENT_TYPE = 'content-type';
|
|
var FORM_TYPE = 'application/x-www-form-urlencoded';
|
|
|
|
function buildRootPath() {
|
|
var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '/';
|
|
|
|
var rootUrl = prefix;
|
|
|
|
// Add trailing slash
|
|
if (prefix[prefix.length - 1] !== '/') {
|
|
rootUrl = rootUrl + '/';
|
|
}
|
|
|
|
// Add leading slash
|
|
if (prefix[0] !== '/') {
|
|
rootUrl = '/' + rootUrl;
|
|
}
|
|
|
|
return rootUrl;
|
|
}
|
|
|
|
var Server = function (_events$EventEmitter) {
|
|
_inherits(Server, _events$EventEmitter);
|
|
|
|
function Server() {
|
|
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
|
|
_classCallCheck(this, Server);
|
|
|
|
var _this = _possibleConstructorReturn(this, (Server.__proto__ || Object.getPrototypeOf(Server)).call(this));
|
|
|
|
_this.options = options;
|
|
|
|
options.livereload = options.livereload || require.resolve('livereload-js/dist/livereload.js');
|
|
|
|
// todo: change falsy check to allow 0 for random port
|
|
options.port = parseInt(options.port || 35729, 10);
|
|
|
|
if (options.errorListener) {
|
|
_this.errorListener = options.errorListener;
|
|
}
|
|
|
|
_this.rootPath = buildRootPath(options.prefix);
|
|
|
|
_this.clients = {};
|
|
_this.configure(options.app);
|
|
_this.routes(options.app);
|
|
return _this;
|
|
}
|
|
|
|
_createClass(Server, [{
|
|
key: 'routes',
|
|
value: function routes() {
|
|
if (!this.options.dashboard) {
|
|
this.on('GET ' + this.rootPath, this.index.bind(this));
|
|
}
|
|
|
|
this.on('GET ' + this.rootPath + 'changed', this.changed.bind(this));
|
|
this.on('POST ' + this.rootPath + 'changed', this.changed.bind(this));
|
|
this.on('POST ' + this.rootPath + 'alert', this.alert.bind(this));
|
|
this.on('GET ' + this.rootPath + 'livereload.js', this.livereload.bind(this));
|
|
this.on('GET ' + this.rootPath + 'kill', this.close.bind(this));
|
|
}
|
|
}, {
|
|
key: 'configure',
|
|
value: function configure(app) {
|
|
var _this2 = this;
|
|
|
|
debug('Configuring %s', app ? 'connect / express application' : 'HTTP server');
|
|
|
|
var handler = this.options.handler || this.handler;
|
|
|
|
if (!app) {
|
|
if (this.options.key && this.options.cert || this.options.pfx) {
|
|
this.server = _https2.default.createServer(this.options, handler.bind(this));
|
|
} else {
|
|
this.server = _http2.default.createServer(handler.bind(this));
|
|
}
|
|
|
|
this.server.on('upgrade', this.websocketify.bind(this));
|
|
this.server.on('error', this.error.bind(this));
|
|
return this;
|
|
}
|
|
|
|
this.app = app;
|
|
this.app.listen = function (port, done) {
|
|
done = done || function () {};
|
|
if (port !== _this2.options.port) {
|
|
debug('Warn: LiveReload port is not standard (%d). You are listening on %d', _this2.options.port, port);
|
|
debug('You\'ll need to rely on the LiveReload snippet');
|
|
debug('> http://feedback.livereload.com/knowledgebase/articles/86180-how-do-i-add-the-script-tag-manually-');
|
|
}
|
|
|
|
var srv = _this2.server = _http2.default.createServer(app);
|
|
srv.on('upgrade', _this2.websocketify.bind(_this2));
|
|
srv.on('error', _this2.error.bind(_this2));
|
|
srv.on('close', _this2.close.bind(_this2));
|
|
return srv.listen(port, done);
|
|
};
|
|
|
|
return this;
|
|
}
|
|
}, {
|
|
key: 'handler',
|
|
value: function handler(req, res, next) {
|
|
var _this3 = this;
|
|
|
|
var middleware = typeof next === 'function';
|
|
debug('LiveReload handler %s (middleware: %s)', req.url, middleware ? 'on' : 'off');
|
|
|
|
next = next || this.defaultHandler.bind(this, res);
|
|
req.headers[CONTENT_TYPE] = req.headers[CONTENT_TYPE] || FORM_TYPE;
|
|
return (0, _any2.default)(req, res, function (err, body) {
|
|
if (err) return next(err);
|
|
req.body = body;
|
|
|
|
if (!req.query) {
|
|
req.query = req.url.indexOf('?') !== -1 ? _qs2.default.parse((0, _url.parse)(req.url).query) : {};
|
|
}
|
|
|
|
return _this3.handle(req, res, next);
|
|
});
|
|
}
|
|
}, {
|
|
key: 'index',
|
|
value: function index(req, res) {
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.write(JSON.stringify({
|
|
tinylr: 'Welcome',
|
|
version: _package2.default.version
|
|
}));
|
|
|
|
res.end();
|
|
}
|
|
}, {
|
|
key: 'handle',
|
|
value: function handle(req, res, next) {
|
|
var url = (0, _url.parse)(req.url);
|
|
debug('Request:', req.method, url.href);
|
|
var middleware = typeof next === 'function';
|
|
|
|
// do the routing
|
|
var route = req.method + ' ' + url.pathname;
|
|
var respond = this.emit(route, req, res);
|
|
if (respond) return;
|
|
|
|
if (middleware) return next();
|
|
|
|
// Only apply content-type on non middleware setup #70
|
|
return this.notFound(res);
|
|
}
|
|
}, {
|
|
key: 'defaultHandler',
|
|
value: function defaultHandler(res, err) {
|
|
if (!err) return this.notFound(res);
|
|
|
|
this.error(err);
|
|
res.setHeader('Content-Type', 'text/plain');
|
|
res.statusCode = 500;
|
|
res.end('Error: ' + err.stack);
|
|
}
|
|
}, {
|
|
key: 'notFound',
|
|
value: function notFound(res) {
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.writeHead(404);
|
|
res.write(JSON.stringify({
|
|
error: 'not_found',
|
|
reason: 'no such route'
|
|
}));
|
|
res.end();
|
|
}
|
|
}, {
|
|
key: 'websocketify',
|
|
value: function websocketify(req, socket, head) {
|
|
var _this4 = this;
|
|
|
|
var client = new _client2.default(req, socket, head, this.options);
|
|
this.clients[client.id] = client;
|
|
|
|
// handle socket error to prevent possible app crash, such as ECONNRESET
|
|
socket.on('error', function (e) {
|
|
// ignore frequent ECONNRESET error (seems inevitable when refresh)
|
|
if (e.code === 'ECONNRESET') return;
|
|
_this4.error(e);
|
|
});
|
|
|
|
client.once('info', function (data) {
|
|
debug('Create client %s (url: %s)', data.id, data.url);
|
|
_this4.emit('MSG /create', data.id, data.url);
|
|
});
|
|
|
|
client.once('end', function () {
|
|
debug('Destroy client %s (url: %s)', client.id, client.url);
|
|
_this4.emit('MSG /destroy', client.id, client.url);
|
|
delete _this4.clients[client.id];
|
|
});
|
|
}
|
|
}, {
|
|
key: 'listen',
|
|
value: function listen(port, host, fn) {
|
|
port = port || this.options.port;
|
|
|
|
// Last used port for error display
|
|
this.port = port;
|
|
|
|
if (typeof host === 'function') {
|
|
fn = host;
|
|
host = undefined;
|
|
}
|
|
|
|
this.server.listen(port, host, fn);
|
|
}
|
|
}, {
|
|
key: 'close',
|
|
value: function close(req, res) {
|
|
Object.keys(this.clients).forEach(function (id) {
|
|
this.clients[id].close();
|
|
}, this);
|
|
|
|
if (this.server._handle) this.server.close(this.emit.bind(this, 'close'));
|
|
|
|
if (res) res.end();
|
|
}
|
|
}, {
|
|
key: 'error',
|
|
value: function error(e) {
|
|
if (this.errorListener) {
|
|
this.errorListener(e);
|
|
return;
|
|
}
|
|
|
|
console.error();
|
|
if (typeof e === 'undefined') {
|
|
console.error('... Uhoh. Got error %s ...', e);
|
|
} else {
|
|
console.error('... Uhoh. Got error %s ...', e.message);
|
|
console.error(e.stack);
|
|
|
|
if (e.code !== 'EADDRINUSE') return;
|
|
console.error();
|
|
console.error('You already have a server listening on %s', this.port);
|
|
console.error('You should stop it and try again.');
|
|
console.error();
|
|
}
|
|
}
|
|
|
|
// Routes
|
|
|
|
}, {
|
|
key: 'livereload',
|
|
value: function livereload(req, res) {
|
|
res.setHeader('Content-Type', 'application/javascript');
|
|
_fs2.default.createReadStream(this.options.livereload).pipe(res);
|
|
}
|
|
}, {
|
|
key: 'changed',
|
|
value: function changed(req, res) {
|
|
var files = this.param('files', req);
|
|
|
|
debug('Changed event (Files: %s)', files.join(' '));
|
|
var clients = this.notifyClients(files);
|
|
|
|
if (!res) return;
|
|
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.write(JSON.stringify({
|
|
clients: clients,
|
|
files: files
|
|
}));
|
|
|
|
res.end();
|
|
}
|
|
}, {
|
|
key: 'alert',
|
|
value: function alert(req, res) {
|
|
var message = this.param('message', req);
|
|
|
|
debug('Alert event (Message: %s)', message);
|
|
var clients = this.alertClients(message);
|
|
|
|
if (!res) return;
|
|
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.write(JSON.stringify({
|
|
clients: clients,
|
|
message: message
|
|
}));
|
|
|
|
res.end();
|
|
}
|
|
}, {
|
|
key: 'notifyClients',
|
|
value: function notifyClients(files) {
|
|
var clients = Object.keys(this.clients).map(function (id) {
|
|
var client = this.clients[id];
|
|
debug('Reloading client %s (url: %s)', client.id, client.url);
|
|
client.reload(files);
|
|
return {
|
|
id: client.id,
|
|
url: client.url
|
|
};
|
|
}, this);
|
|
|
|
return clients;
|
|
}
|
|
}, {
|
|
key: 'alertClients',
|
|
value: function alertClients(message) {
|
|
var clients = Object.keys(this.clients).map(function (id) {
|
|
var client = this.clients[id];
|
|
debug('Alert client %s (url: %s)', client.id, client.url);
|
|
client.alert(message);
|
|
return {
|
|
id: client.id,
|
|
url: client.url
|
|
};
|
|
}, this);
|
|
|
|
return clients;
|
|
}
|
|
|
|
// Lookup param from body / params / query.
|
|
|
|
}, {
|
|
key: 'param',
|
|
value: function param(name, req) {
|
|
var param = void 0;
|
|
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];
|
|
|
|
// normalize files array
|
|
if (name === 'files') {
|
|
param = Array.isArray(param) ? param : typeof param === 'string' ? param.split(/[\s,]/) : [];
|
|
}
|
|
|
|
return param;
|
|
}
|
|
}]);
|
|
|
|
return Server;
|
|
}(_events2.default.EventEmitter);
|
|
|
|
exports.default = Server;
|
|
module.exports = exports['default']; |