[Service] Integrate DeviceHome and SignalingServer
[platform/framework/web/wrtjs.git] / device_home / node_modules / socket.io / lib / index.js
1 'use strict';
2
3 /**
4  * Module dependencies.
5  */
6
7 var http = require('http');
8 var read = require('fs').readFileSync;
9 var path = require('path');
10 var exists = require('fs').existsSync;
11 var engine = require('engine.io');
12 var clientVersion = require('socket.io-client/package.json').version;
13 var Client = require('./client');
14 var Emitter = require('events').EventEmitter;
15 var Namespace = require('./namespace');
16 var ParentNamespace = require('./parent-namespace');
17 var Adapter = require('socket.io-adapter');
18 var parser = require('socket.io-parser');
19 var debug = require('debug')('socket.io:server');
20 var url = require('url');
21
22 /**
23  * Module exports.
24  */
25
26 module.exports = Server;
27
28 /**
29  * Socket.IO client source.
30  */
31
32 var clientSource = undefined;
33 var clientSourceMap = undefined;
34
35 /**
36  * Server constructor.
37  *
38  * @param {http.Server|Number|Object} srv http server, port or options
39  * @param {Object} [opts]
40  * @api public
41  */
42
43 function Server(srv, opts){
44   if (!(this instanceof Server)) return new Server(srv, opts);
45   if ('object' == typeof srv && srv instanceof Object && !srv.listen) {
46     opts = srv;
47     srv = null;
48   }
49   opts = opts || {};
50   this.nsps = {};
51   this.parentNsps = new Map();
52   this.path(opts.path || '/socket.io');
53   this.serveClient(false !== opts.serveClient);
54   this.parser = opts.parser || parser;
55   this.encoder = new this.parser.Encoder();
56   this.adapter(opts.adapter || Adapter);
57   this.origins(opts.origins || '*:*');
58   this.sockets = this.of('/');
59   if (srv) this.attach(srv, opts);
60 }
61
62 /**
63  * Server request verification function, that checks for allowed origins
64  *
65  * @param {http.IncomingMessage} req request
66  * @param {Function} fn callback to be called with the result: `fn(err, success)`
67  */
68
69 Server.prototype.checkRequest = function(req, fn) {
70   var origin = req.headers.origin || req.headers.referer;
71
72   // file:// URLs produce a null Origin which can't be authorized via echo-back
73   if ('null' == origin || null == origin) origin = '*';
74
75   if (!!origin && typeof(this._origins) == 'function') return this._origins(origin, fn);
76   if (this._origins.indexOf('*:*') !== -1) return fn(null, true);
77   if (origin) {
78     try {
79       var parts = url.parse(origin);
80       var defaultPort = 'https:' == parts.protocol ? 443 : 80;
81       parts.port = parts.port != null
82         ? parts.port
83         : defaultPort;
84       var ok =
85         ~this._origins.indexOf(parts.protocol + '//' + parts.hostname + ':' + parts.port) ||
86         ~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
87         ~this._origins.indexOf(parts.hostname + ':*') ||
88         ~this._origins.indexOf('*:' + parts.port);
89       debug('origin %s is %svalid', origin, !!ok ? '' : 'not ');
90       return fn(null, !!ok);
91     } catch (ex) {
92     }
93   }
94   fn(null, false);
95 };
96
97 /**
98  * Sets/gets whether client code is being served.
99  *
100  * @param {Boolean} v whether to serve client code
101  * @return {Server|Boolean} self when setting or value when getting
102  * @api public
103  */
104
105 Server.prototype.serveClient = function(v){
106   if (!arguments.length) return this._serveClient;
107   this._serveClient = v;
108   var resolvePath = function(file){
109     var filepath = path.resolve(__dirname, './../../', file);
110     if (exists(filepath)) {
111       return filepath;
112     }
113     return require.resolve(file);
114   };
115   if (v && !clientSource) {
116     clientSource = read(resolvePath( 'socket.io-client/dist/socket.io.js'), 'utf-8');
117     try {
118       clientSourceMap = read(resolvePath( 'socket.io-client/dist/socket.io.js.map'), 'utf-8');
119     } catch(err) {
120       debug('could not load sourcemap file');
121     }
122   }
123   return this;
124 };
125
126 /**
127  * Old settings for backwards compatibility
128  */
129
130 var oldSettings = {
131   "transports": "transports",
132   "heartbeat timeout": "pingTimeout",
133   "heartbeat interval": "pingInterval",
134   "destroy buffer size": "maxHttpBufferSize"
135 };
136
137 /**
138  * Backwards compatibility.
139  *
140  * @api public
141  */
142
143 Server.prototype.set = function(key, val){
144   if ('authorization' == key && val) {
145     this.use(function(socket, next) {
146       val(socket.request, function(err, authorized) {
147         if (err) return next(new Error(err));
148         if (!authorized) return next(new Error('Not authorized'));
149         next();
150       });
151     });
152   } else if ('origins' == key && val) {
153     this.origins(val);
154   } else if ('resource' == key) {
155     this.path(val);
156   } else if (oldSettings[key] && this.eio[oldSettings[key]]) {
157     this.eio[oldSettings[key]] = val;
158   } else {
159     console.error('Option %s is not valid. Please refer to the README.', key);
160   }
161
162   return this;
163 };
164
165 /**
166  * Executes the middleware for an incoming namespace not already created on the server.
167  *
168  * @param {String} name name of incoming namespace
169  * @param {Object} query the query parameters
170  * @param {Function} fn callback
171  * @api private
172  */
173
174 Server.prototype.checkNamespace = function(name, query, fn){
175   if (this.parentNsps.size === 0) return fn(false);
176
177   const keysIterator = this.parentNsps.keys();
178
179   const run = () => {
180     let nextFn = keysIterator.next();
181     if (nextFn.done) {
182       return fn(false);
183     }
184     nextFn.value(name, query, (err, allow) => {
185       if (err || !allow) {
186         run();
187       } else {
188         fn(this.parentNsps.get(nextFn.value).createChild(name));
189       }
190     });
191   };
192
193   run();
194 };
195
196 /**
197  * Sets the client serving path.
198  *
199  * @param {String} v pathname
200  * @return {Server|String} self when setting or value when getting
201  * @api public
202  */
203
204 Server.prototype.path = function(v){
205   if (!arguments.length) return this._path;
206   this._path = v.replace(/\/$/, '');
207   return this;
208 };
209
210 /**
211  * Sets the adapter for rooms.
212  *
213  * @param {Adapter} v pathname
214  * @return {Server|Adapter} self when setting or value when getting
215  * @api public
216  */
217
218 Server.prototype.adapter = function(v){
219   if (!arguments.length) return this._adapter;
220   this._adapter = v;
221   for (var i in this.nsps) {
222     if (this.nsps.hasOwnProperty(i)) {
223       this.nsps[i].initAdapter();
224     }
225   }
226   return this;
227 };
228
229 /**
230  * Sets the allowed origins for requests.
231  *
232  * @param {String|String[]} v origins
233  * @return {Server|Adapter} self when setting or value when getting
234  * @api public
235  */
236
237 Server.prototype.origins = function(v){
238   if (!arguments.length) return this._origins;
239
240   this._origins = v;
241   return this;
242 };
243
244 /**
245  * Attaches socket.io to a server or port.
246  *
247  * @param {http.Server|Number} server or port
248  * @param {Object} options passed to engine.io
249  * @return {Server} self
250  * @api public
251  */
252
253 Server.prototype.listen =
254 Server.prototype.attach = function(srv, opts){
255   if ('function' == typeof srv) {
256     var msg = 'You are trying to attach socket.io to an express ' +
257     'request handler function. Please pass a http.Server instance.';
258     throw new Error(msg);
259   }
260
261   // handle a port as a string
262   if (Number(srv) == srv) {
263     srv = Number(srv);
264   }
265
266   if ('number' == typeof srv) {
267     debug('creating http server and binding to %d', srv);
268     var port = srv;
269     srv = http.Server(function(req, res){
270       res.writeHead(404);
271       res.end();
272     });
273     srv.listen(port);
274
275   }
276
277   // set engine.io path to `/socket.io`
278   opts = opts || {};
279   opts.path = opts.path || this.path();
280   // set origins verification
281   opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this);
282
283   if (this.sockets.fns.length > 0) {
284     this.initEngine(srv, opts);
285     return this;
286   }
287
288   var self = this;
289   var connectPacket = { type: parser.CONNECT, nsp: '/' };
290   this.encoder.encode(connectPacket, function (encodedPacket){
291     // the CONNECT packet will be merged with Engine.IO handshake,
292     // to reduce the number of round trips
293     opts.initialPacket = encodedPacket;
294
295     self.initEngine(srv, opts);
296   });
297   return this;
298 };
299
300 /**
301  * Initialize engine
302  *
303  * @param {Object} options passed to engine.io
304  * @api private
305  */
306
307 Server.prototype.initEngine = function(srv, opts){
308   // initialize engine
309   debug('creating engine.io instance with opts %j', opts);
310   this.eio = engine.attach(srv, opts);
311
312   // attach static file serving
313   if (this._serveClient) this.attachServe(srv);
314
315   // Export http server
316   this.httpServer = srv;
317
318   // bind to engine events
319   this.bind(this.eio);
320 };
321
322 /**
323  * Attaches the static file serving.
324  *
325  * @param {Function|http.Server} srv http server
326  * @api private
327  */
328
329 Server.prototype.attachServe = function(srv){
330   debug('attaching client serving req handler');
331   var url = this._path + '/socket.io.js';
332   var urlMap = this._path + '/socket.io.js.map';
333   var evs = srv.listeners('request').slice(0);
334   var self = this;
335   srv.removeAllListeners('request');
336   srv.on('request', function(req, res) {
337     if (0 === req.url.indexOf(urlMap)) {
338       self.serveMap(req, res);
339     } else if (0 === req.url.indexOf(url)) {
340       self.serve(req, res);
341     } else {
342       for (var i = 0; i < evs.length; i++) {
343         evs[i].call(srv, req, res);
344       }
345     }
346   });
347 };
348
349 /**
350  * Handles a request serving `/socket.io.js`
351  *
352  * @param {http.Request} req
353  * @param {http.Response} res
354  * @api private
355  */
356
357 Server.prototype.serve = function(req, res){
358   // Per the standard, ETags must be quoted:
359   // https://tools.ietf.org/html/rfc7232#section-2.3
360   var expectedEtag = '"' + clientVersion + '"';
361
362   var etag = req.headers['if-none-match'];
363   if (etag) {
364     if (expectedEtag == etag) {
365       debug('serve client 304');
366       res.writeHead(304);
367       res.end();
368       return;
369     }
370   }
371
372   debug('serve client source');
373   res.setHeader("Cache-Control", "public, max-age=0");
374   res.setHeader('Content-Type', 'application/javascript');
375   res.setHeader('ETag', expectedEtag);
376   res.writeHead(200);
377   res.end(clientSource);
378 };
379
380 /**
381  * Handles a request serving `/socket.io.js.map`
382  *
383  * @param {http.Request} req
384  * @param {http.Response} res
385  * @api private
386  */
387
388 Server.prototype.serveMap = function(req, res){
389   // Per the standard, ETags must be quoted:
390   // https://tools.ietf.org/html/rfc7232#section-2.3
391   var expectedEtag = '"' + clientVersion + '"';
392
393   var etag = req.headers['if-none-match'];
394   if (etag) {
395     if (expectedEtag == etag) {
396       debug('serve client 304');
397       res.writeHead(304);
398       res.end();
399       return;
400     }
401   }
402
403   debug('serve client sourcemap');
404   res.setHeader('Content-Type', 'application/json');
405   res.setHeader('ETag', expectedEtag);
406   res.writeHead(200);
407   res.end(clientSourceMap);
408 };
409
410 /**
411  * Binds socket.io to an engine.io instance.
412  *
413  * @param {engine.Server} engine engine.io (or compatible) server
414  * @return {Server} self
415  * @api public
416  */
417
418 Server.prototype.bind = function(engine){
419   this.engine = engine;
420   this.engine.on('connection', this.onconnection.bind(this));
421   return this;
422 };
423
424 /**
425  * Called with each incoming transport connection.
426  *
427  * @param {engine.Socket} conn
428  * @return {Server} self
429  * @api public
430  */
431
432 Server.prototype.onconnection = function(conn){
433   debug('incoming connection with id %s', conn.id);
434   var client = new Client(this, conn);
435   client.connect('/');
436   return this;
437 };
438
439 /**
440  * Looks up a namespace.
441  *
442  * @param {String|RegExp|Function} name nsp name
443  * @param {Function} [fn] optional, nsp `connection` ev handler
444  * @api public
445  */
446
447 Server.prototype.of = function(name, fn){
448   if (typeof name === 'function' || name instanceof RegExp) {
449     const parentNsp = new ParentNamespace(this);
450     debug('initializing parent namespace %s', parentNsp.name);
451     if (typeof name === 'function') {
452       this.parentNsps.set(name, parentNsp);
453     } else {
454       this.parentNsps.set((nsp, conn, next) => next(null, name.test(nsp)), parentNsp);
455     }
456     if (fn) parentNsp.on('connect', fn);
457     return parentNsp;
458   }
459
460   if (String(name)[0] !== '/') name = '/' + name;
461
462   var nsp = this.nsps[name];
463   if (!nsp) {
464     debug('initializing namespace %s', name);
465     nsp = new Namespace(this, name);
466     this.nsps[name] = nsp;
467   }
468   if (fn) nsp.on('connect', fn);
469   return nsp;
470 };
471
472 /**
473  * Closes server connection
474  *
475  * @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed
476  * @api public
477  */
478
479 Server.prototype.close = function(fn){
480   for (var id in this.nsps['/'].sockets) {
481     if (this.nsps['/'].sockets.hasOwnProperty(id)) {
482       this.nsps['/'].sockets[id].onclose();
483     }
484   }
485
486   this.engine.close();
487
488   if (this.httpServer) {
489     this.httpServer.close(fn);
490   } else {
491     fn && fn();
492   }
493 };
494
495 /**
496  * Expose main namespace (/).
497  */
498
499 var emitterMethods = Object.keys(Emitter.prototype).filter(function(key){
500   return typeof Emitter.prototype[key] === 'function';
501 });
502
503 emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress', 'binary']).forEach(function(fn){
504   Server.prototype[fn] = function(){
505     return this.sockets[fn].apply(this.sockets, arguments);
506   };
507 });
508
509 Namespace.flags.forEach(function(flag){
510   Object.defineProperty(Server.prototype, flag, {
511     get: function() {
512       this.sockets.flags = this.sockets.flags || {};
513       this.sockets.flags[flag] = true;
514       return this;
515     }
516   });
517 });
518
519 /**
520  * BC with `io.listen`
521  */
522
523 Server.listen = Server;