Initial commit
[yaffs-website] / node_modules / tiny-lr / lib / server.js
1 var fs          = require('fs');
2 var qs          = require('qs');
3 var path        = require('path');
4 var util        = require('util');
5 var http        = require('http');
6 var https       = require('https');
7 var events      = require('events');
8 var parse       = require('url').parse;
9 var debug       = require('debug')('tinylr:server');
10 var Client      = require('./client');
11
12 // Middleware fallbacks
13 var bodyParser  = require('body-parser').json()
14 var queryParser = require('./middleware/query')();
15
16 var config = require('../package.json');
17
18 function Server(options) {
19   options = this.options = options || {};
20   events.EventEmitter.call(this);
21
22   options.livereload = options.livereload || require.resolve('livereload-js/dist/livereload.js');
23   options.port = parseInt(options.port || 35729, 10);
24
25   this.on('GET /', this.index.bind(this));
26   this.on('GET /changed', this.changed.bind(this));
27   this.on('POST /changed', this.changed.bind(this));
28   this.on('GET /livereload.js', this.livereload.bind(this));
29   this.on('GET /kill', this.close.bind(this));
30
31   if(options.errorListener) {
32     this.errorListener = options.errorListener;
33   }
34
35   this.clients = {};
36   this.configure(options.app);
37 }
38
39 module.exports = Server;
40
41 util.inherits(Server, events.EventEmitter);
42
43 Server.prototype.configure = function configure(app) {
44   var self = this;
45   debug('Configuring %s', app ? 'connect / express application' : 'HTTP server');
46
47   if (!app) {
48     if ((this.options.key && this.options.cert) || this.options.pfx) {
49       this.server = https.createServer(this.options, this.handler.bind(this));
50     } else {
51       this.server = http.createServer(this.handler.bind(this));
52     }
53     this.server.on('upgrade', this.websocketify.bind(this));
54     this.server.on('error', function() {
55       self.error.apply(self, arguments);
56     });
57     return this;
58   }
59
60   this.app = app;
61
62   this.app.listen = function(port, done) {
63     done = done || function() {};
64     if (port !== self.options.port) {
65       debug('Warn: LiveReload port is not standard (%d). You are listening on %d', self.options.port, port);
66       debug('You\'ll need to rely on the LiveReload snippet');
67       debug('> http://feedback.livereload.com/knowledgebase/articles/86180-how-do-i-add-the-script-tag-manually-');
68     }
69
70     var srv = self.server = http.createServer(app);
71     srv.on('upgrade', self.websocketify.bind(self));
72     srv.on('error', function() {
73       self.error.apply(self, arguments);
74     });
75     srv.on('close', self.close.bind(self));
76     return srv.listen(port, done);
77   };
78
79   return this;
80 };
81
82 Server.prototype.handler = function handler(req, res, next) {
83   var self = this;
84   var middleware = typeof next === 'function';
85   debug('LiveReload handler %s (middleware: %s)', req.url, middleware ? 'on' : 'off');
86
87   this.parse(req, res, function(err) {
88     debug('query parsed', req.body, err);
89     if (err) return next(err);
90     self.handle(req, res, next);
91   });
92
93   // req
94   //   .on('end', this.handle.bind(this, req, res))
95   //   .on('data', function(chunk) {
96   //     req.data = req.data || '';
97   //     req.data += chunk;
98   //   });
99
100   return this;
101 };
102
103 // Ensure body / query are defined, useful as a fallback when the
104 // Server is used without express / connect, and shouldn't hurt
105 // otherwise
106 Server.prototype.parse = function(req, res, next) {
107   debug('Parse', req.body, req.query);
108   bodyParser(req, res, function(err) {
109     debug('Body parsed', req.body);
110     if (err) return next(err);
111
112     queryParser(req, res, next);
113   });
114 };
115
116 Server.prototype.handle = function handle(req, res, next) {
117   var url = parse(req.url);
118   debug('Request:', req.method, url.href);
119   var middleware = typeof next === 'function';
120
121   // do the routing
122   var route = req.method + ' '  + url.pathname;
123   var respond = this.emit(route, req, res);
124   if (respond) return;
125   if (!middleware) {
126     // Only apply content-type on non middleware setup #70
127     res.setHeader('Content-Type', 'application/json');
128   } else {
129     // Middleware ==> next()ing
130     return next();
131   }
132
133   res.writeHead(404);
134   res.write(JSON.stringify({
135     error: 'not_found',
136     reason: 'no such route'
137   }));
138   res.end();
139 };
140
141 Server.prototype.websocketify = function websocketify(req, socket, head) {
142   var self = this;
143   var client = new Client(req, socket, head, this.options);
144   this.clients[client.id] = client;
145
146   debug('New LiveReload connection (id: %s)', client.id);
147   client.on('end', function() {
148     debug('Destroy client %s (url: %s)', client.id, client.url);
149     delete self.clients[client.id];
150   });
151 };
152
153 Server.prototype.listen = function listen(port, host, fn) {
154   port = port || this.options.port;
155   
156   //Last used port for error display
157   this.port = port;
158
159   if (typeof host === 'function') {
160     fn = host;
161     host = undefined;
162   }
163
164   this.server.listen(port, host, fn);
165 };
166
167 Server.prototype.close = function close(req, res) {
168   Object.keys(this.clients).forEach(function(id) {
169     this.clients[id].close();
170   }, this);
171
172
173   if (this.server._handle) this.server.close(this.emit.bind(this, 'close'));
174
175   if (res) res.end();
176 };
177
178 Server.prototype.error = function error(e) {
179   if(this.errorListener) {
180     this.errorListener(e);
181     return
182   }
183
184   console.error();
185   console.error('... Uhoh. Got error %s ...', e.message);
186   console.error(e.stack);
187
188   if (e.code !== 'EADDRINUSE') return;
189   console.error();
190   console.error('You already have a server listening on %s', this.port);
191   console.error('You should stop it and try again.');
192   console.error();
193 };
194
195 // Routes
196
197 Server.prototype.livereload = function livereload(req, res) {
198   res.setHeader('Content-Type', 'application/javascript');
199   fs.createReadStream(this.options.livereload).pipe(res);
200 };
201
202 Server.prototype.changed = function changed(req, res) {
203   var files = this.param('files', req);
204
205   debug('Changed event (Files: %s)', files.join(' '));
206   var clients = this.notifyClients(files);
207
208   if (!res) return;
209
210   res.setHeader('Content-Type', 'application/json');
211   res.write(JSON.stringify({
212     clients: clients,
213     files: files
214   }));
215
216   res.end();
217 };
218
219 Server.prototype.notifyClients = function notifyClients(files) {
220   var clients = Object.keys(this.clients).map(function(id) {
221     var client = this.clients[id];
222     debug('Reloading client %s (url: %s)', client.id, client.url);
223     client.reload(files);
224     return {
225       id: client.id,
226       url: client.url
227     };
228   }, this);
229
230   return clients;
231 };
232
233 // Lookup param from body / params / query.
234 Server.prototype.param = function _param(name, req) {
235   var param;
236   if (req.body && req.body[name]) param = req.body.files;
237   else if (req.params && req.params[name]) param = req.params.files;
238   else if (req.query && req.query[name]) param= req.query.files;
239
240   // normalize files array
241   param = Array.isArray(param) ? param :
242     typeof param === 'string' ? param.split(/[\s,]/) :
243     [];
244
245   debug('param %s', name, req.body, req.params, req.query, param);
246   return param;
247 };
248
249 Server.prototype.index = function index(req, res) {
250   res.setHeader('Content-Type', 'application/json');
251   res.write(JSON.stringify({
252     tinylr: 'Welcome',
253     version: config.version
254   }));
255
256   res.end();
257 };