209 lines
5.5 KiB
JavaScript
209 lines
5.5 KiB
JavaScript
var fs = require('fs');
|
|
var qs = require('qs');
|
|
var path = require('path');
|
|
var util = require('util');
|
|
var http = require('http');
|
|
var https = require('https');
|
|
var events = require('events');
|
|
var parse = require('url').parse;
|
|
var debug = require('debug')('tinylr:server');
|
|
var Client = require('./client');
|
|
var constants = require('constants');
|
|
|
|
var config = require('../package.json');
|
|
|
|
function Server(options) {
|
|
options = this.options = options || {};
|
|
events.EventEmitter.call(this);
|
|
|
|
options.livereload = options.livereload || path.join(__dirname, 'public/livereload.js');
|
|
options.port = parseInt(options.port || 35729, 10);
|
|
|
|
this.on('GET /', this.index.bind(this));
|
|
this.on('GET /changed', this.changed.bind(this));
|
|
this.on('POST /changed', this.changed.bind(this));
|
|
this.on('GET /livereload.js', this.livereload.bind(this));
|
|
this.on('GET /kill', this.close.bind(this));
|
|
|
|
this.clients = {};
|
|
|
|
this.configure(options.app);
|
|
}
|
|
|
|
module.exports = Server;
|
|
|
|
util.inherits(Server, events.EventEmitter);
|
|
|
|
Server.prototype.configure = function configure(app) {
|
|
debug('Configuring %s', app ? 'connect / express application' : 'HTTP server');
|
|
|
|
if(!app) {
|
|
if (this.options.key && this.options.cert) {
|
|
this.server = https.createServer(this.options, this.handler.bind(this));
|
|
}
|
|
else {
|
|
this.server = http.createServer(this.handler.bind(this));
|
|
}
|
|
this.server.on('upgrade', this.websocketify.bind(this));
|
|
this.server.on('error', this.error.bind(this));
|
|
return this;
|
|
}
|
|
|
|
|
|
var self = this;
|
|
this.app = app;
|
|
|
|
this.app.listen = function(port, done) {
|
|
done = done || function() {};
|
|
if(port !== self.options.port) {
|
|
debug('Warn: LiveReload port is not standard (%d). You are listening on %d', self.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 = self.server = http.createServer(app);
|
|
srv.on('upgrade', self.websocketify.bind(self));
|
|
srv.on('error', self.error.bind(self));
|
|
srv.on('close', self.close.bind(self));
|
|
return srv.listen(port, done);
|
|
};
|
|
|
|
return this;
|
|
};
|
|
|
|
Server.prototype.handler = function handler(req, res, next) {
|
|
var middleware = typeof next === 'function';
|
|
debug('LiveReload handler %s (middleware: %s)', req.url, middleware ? 'on' : 'off');
|
|
|
|
if(middleware) {
|
|
if(!req.params && req.query) req.params = req.query;
|
|
this.handle(req, res, next);
|
|
return this;
|
|
}
|
|
|
|
req
|
|
.on('end', this.handle.bind(this, req, res))
|
|
.on('data', function(chunk) {
|
|
req.data = req.data || '';
|
|
req.data += chunk;
|
|
});
|
|
|
|
return this;
|
|
};
|
|
|
|
Server.prototype.handle = function handle(req, res, next) {
|
|
var url = parse(req.url);
|
|
var middleware = typeof next === 'function';
|
|
|
|
req.body = {};
|
|
req.params = {};
|
|
|
|
try {
|
|
req.body = JSON.parse(req.data);
|
|
} catch(e) {}
|
|
|
|
if(url.query) req.params = qs.parse(url.query);
|
|
|
|
// todo: parse Accept header
|
|
res.setHeader('Content-Type', 'application/json');
|
|
|
|
// do the routing
|
|
var route = req.method + ' ' + url.pathname;
|
|
var respond = this.emit(route, req, res);
|
|
if(respond) return;
|
|
if(middleware) return next();
|
|
|
|
res.writeHead(404);
|
|
res.write(JSON.stringify({
|
|
error: 'not_found',
|
|
reason: 'no such route'
|
|
}));
|
|
res.end();
|
|
};
|
|
|
|
Server.prototype.websocketify = function websocketify(req, socket, head) {
|
|
var self = this;
|
|
var client = new Client(req, socket, head, this.options);
|
|
this.clients[client.id] = client;
|
|
|
|
debug('New LiveReload connection (id: %s)', client.id);
|
|
client.on('end', function() {
|
|
debug('Destroy client %s (url: %s)', client.id, client.url);
|
|
delete self.clients[client.id];
|
|
});
|
|
};
|
|
|
|
Server.prototype.listen = function listen(port, fn) {
|
|
this.port = port;
|
|
this.server.listen(port, fn);
|
|
};
|
|
|
|
Server.prototype.close = function close(req, res) {
|
|
if(res) res.end();
|
|
|
|
Object.keys(this.clients).forEach(function(id) {
|
|
this.clients[id].close();
|
|
}, this);
|
|
|
|
if(this.server._handle) this.server.close(this.emit.bind(this, 'close'));
|
|
};
|
|
|
|
Server.prototype.error = function error(e) {
|
|
console.error();
|
|
console.error('... Uhoh. Got error %s ...', e.message);
|
|
console.error(e.stack);
|
|
|
|
if(e.code !== constants.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
|
|
|
|
Server.prototype.livereload = function livereload(req, res) {
|
|
fs.createReadStream(this.options.livereload).pipe(res);
|
|
};
|
|
|
|
Server.prototype.changed = function changed(req, res) {
|
|
var files = [];
|
|
if(req && req.body && req.body.files) files = req.body.files;
|
|
if(req && req.params && req.params.files) files = req.params.files;
|
|
|
|
// normalize files array
|
|
files = Array.isArray(files) ? files :
|
|
typeof files === 'string' ? files.split(/[\s,]/) :
|
|
[];
|
|
|
|
|
|
debug('Changed event (Files: %s)', files.join(' '));
|
|
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);
|
|
|
|
if(!res) return;
|
|
|
|
res.write(JSON.stringify({
|
|
clients: clients,
|
|
files: files
|
|
}));
|
|
|
|
res.end();
|
|
};
|
|
|
|
Server.prototype.index = function index(req, res) {
|
|
res.write(JSON.stringify({
|
|
tinylr: 'Welcome',
|
|
version: config.version
|
|
}));
|
|
|
|
res.end();
|
|
};
|