Mercurial > hg > nodescore
comparison node_modules/socket.io/lib/manager.js @ 69:333afcfd3f3a
added node_modules to project and fixed path to chronometer
also added deps to installer script
author | tzara <rc-web@kiben.net> |
---|---|
date | Sat, 26 Oct 2013 14:12:50 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
68:b076cd17638c | 69:333afcfd3f3a |
---|---|
1 /*! | |
2 * socket.io-node | |
3 * Copyright(c) 2011 LearnBoost <dev@learnboost.com> | |
4 * MIT Licensed | |
5 */ | |
6 | |
7 /** | |
8 * Module dependencies. | |
9 */ | |
10 | |
11 var fs = require('fs') | |
12 , url = require('url') | |
13 , tty = require('tty') | |
14 , crypto = require('crypto') | |
15 , util = require('./util') | |
16 , store = require('./store') | |
17 , client = require('socket.io-client') | |
18 , transports = require('./transports') | |
19 , Logger = require('./logger') | |
20 , Socket = require('./socket') | |
21 , MemoryStore = require('./stores/memory') | |
22 , SocketNamespace = require('./namespace') | |
23 , Static = require('./static') | |
24 , EventEmitter = process.EventEmitter; | |
25 | |
26 /** | |
27 * Export the constructor. | |
28 */ | |
29 | |
30 exports = module.exports = Manager; | |
31 | |
32 /** | |
33 * Default transports. | |
34 */ | |
35 | |
36 var defaultTransports = exports.defaultTransports = [ | |
37 'websocket' | |
38 , 'htmlfile' | |
39 , 'xhr-polling' | |
40 , 'jsonp-polling' | |
41 ]; | |
42 | |
43 /** | |
44 * Inherited defaults. | |
45 */ | |
46 | |
47 var parent = module.parent.exports | |
48 , protocol = parent.protocol | |
49 , jsonpolling_re = /^\d+$/; | |
50 | |
51 /** | |
52 * Manager constructor. | |
53 * | |
54 * @param {HTTPServer} server | |
55 * @param {Object} options, optional | |
56 * @api public | |
57 */ | |
58 | |
59 function Manager (server, options) { | |
60 this.server = server; | |
61 this.namespaces = {}; | |
62 this.sockets = this.of(''); | |
63 this.settings = { | |
64 origins: '*:*' | |
65 , log: true | |
66 , store: new MemoryStore | |
67 , logger: new Logger | |
68 , static: new Static(this) | |
69 , heartbeats: true | |
70 , resource: '/socket.io' | |
71 , transports: defaultTransports | |
72 , authorization: false | |
73 , blacklist: ['disconnect'] | |
74 , 'log level': 3 | |
75 , 'log colors': tty.isatty(process.stdout.fd) | |
76 , 'close timeout': 60 | |
77 , 'heartbeat interval': 25 | |
78 , 'heartbeat timeout': 60 | |
79 , 'polling duration': 20 | |
80 , 'flash policy server': true | |
81 , 'flash policy port': 10843 | |
82 , 'destroy upgrade': true | |
83 , 'destroy buffer size': 10E7 | |
84 , 'browser client': true | |
85 , 'browser client cache': true | |
86 , 'browser client minification': false | |
87 , 'browser client etag': false | |
88 , 'browser client expires': 315360000 | |
89 , 'browser client gzip': false | |
90 , 'browser client handler': false | |
91 , 'client store expiration': 15 | |
92 , 'match origin protocol': false | |
93 }; | |
94 | |
95 for (var i in options) { | |
96 if (options.hasOwnProperty(i)) { | |
97 this.settings[i] = options[i]; | |
98 } | |
99 } | |
100 | |
101 var self = this; | |
102 | |
103 // default error handler | |
104 server.on('error', function(err) { | |
105 self.log.warn('error raised: ' + err); | |
106 }); | |
107 | |
108 this.initStore(); | |
109 | |
110 this.on('set:store', function() { | |
111 self.initStore(); | |
112 }); | |
113 | |
114 // reset listeners | |
115 this.oldListeners = server.listeners('request').splice(0); | |
116 server.removeAllListeners('request'); | |
117 | |
118 server.on('request', function (req, res) { | |
119 self.handleRequest(req, res); | |
120 }); | |
121 | |
122 server.on('upgrade', function (req, socket, head) { | |
123 self.handleUpgrade(req, socket, head); | |
124 }); | |
125 | |
126 server.on('close', function () { | |
127 clearInterval(self.gc); | |
128 }); | |
129 | |
130 server.once('listening', function () { | |
131 self.gc = setInterval(self.garbageCollection.bind(self), 10000); | |
132 }); | |
133 | |
134 for (var i in transports) { | |
135 if (transports.hasOwnProperty(i)) { | |
136 if (transports[i].init) { | |
137 transports[i].init(this); | |
138 } | |
139 } | |
140 } | |
141 | |
142 // forward-compatibility with 1.0 | |
143 var self = this; | |
144 this.sockets.on('connection', function (conn) { | |
145 self.emit('connection', conn); | |
146 }); | |
147 | |
148 this.sequenceNumber = Date.now() | 0; | |
149 | |
150 this.log.info('socket.io started'); | |
151 }; | |
152 | |
153 Manager.prototype.__proto__ = EventEmitter.prototype | |
154 | |
155 /** | |
156 * Store accessor shortcut. | |
157 * | |
158 * @api public | |
159 */ | |
160 | |
161 Manager.prototype.__defineGetter__('store', function () { | |
162 var store = this.get('store'); | |
163 store.manager = this; | |
164 return store; | |
165 }); | |
166 | |
167 /** | |
168 * Logger accessor. | |
169 * | |
170 * @api public | |
171 */ | |
172 | |
173 Manager.prototype.__defineGetter__('log', function () { | |
174 var logger = this.get('logger'); | |
175 | |
176 logger.level = this.get('log level') || -1; | |
177 logger.colors = this.get('log colors'); | |
178 logger.enabled = this.enabled('log'); | |
179 | |
180 return logger; | |
181 }); | |
182 | |
183 /** | |
184 * Static accessor. | |
185 * | |
186 * @api public | |
187 */ | |
188 | |
189 Manager.prototype.__defineGetter__('static', function () { | |
190 return this.get('static'); | |
191 }); | |
192 | |
193 /** | |
194 * Get settings. | |
195 * | |
196 * @api public | |
197 */ | |
198 | |
199 Manager.prototype.get = function (key) { | |
200 return this.settings[key]; | |
201 }; | |
202 | |
203 /** | |
204 * Set settings | |
205 * | |
206 * @api public | |
207 */ | |
208 | |
209 Manager.prototype.set = function (key, value) { | |
210 if (arguments.length == 1) return this.get(key); | |
211 this.settings[key] = value; | |
212 this.emit('set:' + key, this.settings[key], key); | |
213 return this; | |
214 }; | |
215 | |
216 /** | |
217 * Enable a setting | |
218 * | |
219 * @api public | |
220 */ | |
221 | |
222 Manager.prototype.enable = function (key) { | |
223 this.settings[key] = true; | |
224 this.emit('set:' + key, this.settings[key], key); | |
225 return this; | |
226 }; | |
227 | |
228 /** | |
229 * Disable a setting | |
230 * | |
231 * @api public | |
232 */ | |
233 | |
234 Manager.prototype.disable = function (key) { | |
235 this.settings[key] = false; | |
236 this.emit('set:' + key, this.settings[key], key); | |
237 return this; | |
238 }; | |
239 | |
240 /** | |
241 * Checks if a setting is enabled | |
242 * | |
243 * @api public | |
244 */ | |
245 | |
246 Manager.prototype.enabled = function (key) { | |
247 return !!this.settings[key]; | |
248 }; | |
249 | |
250 /** | |
251 * Checks if a setting is disabled | |
252 * | |
253 * @api public | |
254 */ | |
255 | |
256 Manager.prototype.disabled = function (key) { | |
257 return !this.settings[key]; | |
258 }; | |
259 | |
260 /** | |
261 * Configure callbacks. | |
262 * | |
263 * @api public | |
264 */ | |
265 | |
266 Manager.prototype.configure = function (env, fn) { | |
267 if ('function' == typeof env) { | |
268 env.call(this); | |
269 } else if (env == (process.env.NODE_ENV || 'development')) { | |
270 fn.call(this); | |
271 } | |
272 | |
273 return this; | |
274 }; | |
275 | |
276 /** | |
277 * Initializes everything related to the message dispatcher. | |
278 * | |
279 * @api private | |
280 */ | |
281 | |
282 Manager.prototype.initStore = function () { | |
283 this.handshaken = {}; | |
284 this.connected = {}; | |
285 this.open = {}; | |
286 this.closed = {}; | |
287 this.rooms = {}; | |
288 this.roomClients = {}; | |
289 | |
290 var self = this; | |
291 | |
292 this.store.subscribe('handshake', function (id, data) { | |
293 self.onHandshake(id, data); | |
294 }); | |
295 | |
296 this.store.subscribe('connect', function (id) { | |
297 self.onConnect(id); | |
298 }); | |
299 | |
300 this.store.subscribe('open', function (id) { | |
301 self.onOpen(id); | |
302 }); | |
303 | |
304 this.store.subscribe('join', function (id, room) { | |
305 self.onJoin(id, room); | |
306 }); | |
307 | |
308 this.store.subscribe('leave', function (id, room) { | |
309 self.onLeave(id, room); | |
310 }); | |
311 | |
312 this.store.subscribe('close', function (id) { | |
313 self.onClose(id); | |
314 }); | |
315 | |
316 this.store.subscribe('dispatch', function (room, packet, volatile, exceptions) { | |
317 self.onDispatch(room, packet, volatile, exceptions); | |
318 }); | |
319 | |
320 this.store.subscribe('disconnect', function (id) { | |
321 self.onDisconnect(id); | |
322 }); | |
323 }; | |
324 | |
325 /** | |
326 * Called when a client handshakes. | |
327 * | |
328 * @param text | |
329 */ | |
330 | |
331 Manager.prototype.onHandshake = function (id, data) { | |
332 this.handshaken[id] = data; | |
333 }; | |
334 | |
335 /** | |
336 * Called when a client connects (ie: transport first opens) | |
337 * | |
338 * @api private | |
339 */ | |
340 | |
341 Manager.prototype.onConnect = function (id) { | |
342 this.connected[id] = true; | |
343 }; | |
344 | |
345 /** | |
346 * Called when a client opens a request in a different node. | |
347 * | |
348 * @api private | |
349 */ | |
350 | |
351 Manager.prototype.onOpen = function (id) { | |
352 this.open[id] = true; | |
353 | |
354 if (this.closed[id]) { | |
355 var self = this; | |
356 | |
357 this.store.unsubscribe('dispatch:' + id, function () { | |
358 var transport = self.transports[id]; | |
359 if (self.closed[id] && self.closed[id].length && transport) { | |
360 | |
361 // if we have buffered messages that accumulate between calling | |
362 // onOpen an this async callback, send them if the transport is | |
363 // still open, otherwise leave them buffered | |
364 if (transport.open) { | |
365 transport.payload(self.closed[id]); | |
366 self.closed[id] = []; | |
367 } | |
368 } | |
369 }); | |
370 } | |
371 | |
372 // clear the current transport | |
373 if (this.transports[id]) { | |
374 this.transports[id].discard(); | |
375 this.transports[id] = null; | |
376 } | |
377 }; | |
378 | |
379 /** | |
380 * Called when a message is sent to a namespace and/or room. | |
381 * | |
382 * @api private | |
383 */ | |
384 | |
385 Manager.prototype.onDispatch = function (room, packet, volatile, exceptions) { | |
386 if (this.rooms[room]) { | |
387 for (var i = 0, l = this.rooms[room].length; i < l; i++) { | |
388 var id = this.rooms[room][i]; | |
389 | |
390 if (!~exceptions.indexOf(id)) { | |
391 if (this.transports[id] && this.transports[id].open) { | |
392 this.transports[id].onDispatch(packet, volatile); | |
393 } else if (!volatile) { | |
394 this.onClientDispatch(id, packet); | |
395 } | |
396 } | |
397 } | |
398 } | |
399 }; | |
400 | |
401 /** | |
402 * Called when a client joins a nsp / room. | |
403 * | |
404 * @api private | |
405 */ | |
406 | |
407 Manager.prototype.onJoin = function (id, name) { | |
408 if (!this.roomClients[id]) { | |
409 this.roomClients[id] = {}; | |
410 } | |
411 | |
412 if (!this.rooms[name]) { | |
413 this.rooms[name] = []; | |
414 } | |
415 | |
416 if (!~this.rooms[name].indexOf(id)) { | |
417 this.rooms[name].push(id); | |
418 this.roomClients[id][name] = true; | |
419 } | |
420 }; | |
421 | |
422 /** | |
423 * Called when a client leaves a nsp / room. | |
424 * | |
425 * @param private | |
426 */ | |
427 | |
428 Manager.prototype.onLeave = function (id, room) { | |
429 if (this.rooms[room]) { | |
430 var index = this.rooms[room].indexOf(id); | |
431 | |
432 if (index >= 0) { | |
433 this.rooms[room].splice(index, 1); | |
434 } | |
435 | |
436 if (!this.rooms[room].length) { | |
437 delete this.rooms[room]; | |
438 } | |
439 | |
440 if (this.roomClients[id]) { | |
441 delete this.roomClients[id][room]; | |
442 } | |
443 } | |
444 }; | |
445 | |
446 /** | |
447 * Called when a client closes a request in different node. | |
448 * | |
449 * @api private | |
450 */ | |
451 | |
452 Manager.prototype.onClose = function (id) { | |
453 if (this.open[id]) { | |
454 delete this.open[id]; | |
455 } | |
456 | |
457 this.closed[id] = []; | |
458 | |
459 var self = this; | |
460 | |
461 this.store.subscribe('dispatch:' + id, function (packet, volatile) { | |
462 if (!volatile) { | |
463 self.onClientDispatch(id, packet); | |
464 } | |
465 }); | |
466 }; | |
467 | |
468 /** | |
469 * Dispatches a message for a closed client. | |
470 * | |
471 * @api private | |
472 */ | |
473 | |
474 Manager.prototype.onClientDispatch = function (id, packet) { | |
475 if (this.closed[id]) { | |
476 this.closed[id].push(packet); | |
477 } | |
478 }; | |
479 | |
480 /** | |
481 * Receives a message for a client. | |
482 * | |
483 * @api private | |
484 */ | |
485 | |
486 Manager.prototype.onClientMessage = function (id, packet) { | |
487 if (this.namespaces[packet.endpoint]) { | |
488 this.namespaces[packet.endpoint].handlePacket(id, packet); | |
489 } | |
490 }; | |
491 | |
492 /** | |
493 * Fired when a client disconnects (not triggered). | |
494 * | |
495 * @api private | |
496 */ | |
497 | |
498 Manager.prototype.onClientDisconnect = function (id, reason) { | |
499 for (var name in this.namespaces) { | |
500 if (this.namespaces.hasOwnProperty(name)) { | |
501 this.namespaces[name].handleDisconnect(id, reason, typeof this.roomClients[id] !== 'undefined' && | |
502 typeof this.roomClients[id][name] !== 'undefined'); | |
503 } | |
504 } | |
505 | |
506 this.onDisconnect(id); | |
507 }; | |
508 | |
509 /** | |
510 * Called when a client disconnects. | |
511 * | |
512 * @param text | |
513 */ | |
514 | |
515 Manager.prototype.onDisconnect = function (id, local) { | |
516 delete this.handshaken[id]; | |
517 | |
518 if (this.open[id]) { | |
519 delete this.open[id]; | |
520 } | |
521 | |
522 if (this.connected[id]) { | |
523 delete this.connected[id]; | |
524 } | |
525 | |
526 if (this.transports[id]) { | |
527 this.transports[id].discard(); | |
528 delete this.transports[id]; | |
529 } | |
530 | |
531 if (this.closed[id]) { | |
532 delete this.closed[id]; | |
533 } | |
534 | |
535 if (this.roomClients[id]) { | |
536 for (var room in this.roomClients[id]) { | |
537 if (this.roomClients[id].hasOwnProperty(room)) { | |
538 this.onLeave(id, room); | |
539 } | |
540 } | |
541 delete this.roomClients[id] | |
542 } | |
543 | |
544 this.store.destroyClient(id, this.get('client store expiration')); | |
545 | |
546 this.store.unsubscribe('dispatch:' + id); | |
547 | |
548 if (local) { | |
549 this.store.unsubscribe('message:' + id); | |
550 this.store.unsubscribe('disconnect:' + id); | |
551 } | |
552 }; | |
553 | |
554 /** | |
555 * Handles an HTTP request. | |
556 * | |
557 * @api private | |
558 */ | |
559 | |
560 Manager.prototype.handleRequest = function (req, res) { | |
561 var data = this.checkRequest(req); | |
562 | |
563 if (!data) { | |
564 for (var i = 0, l = this.oldListeners.length; i < l; i++) { | |
565 this.oldListeners[i].call(this.server, req, res); | |
566 } | |
567 | |
568 return; | |
569 } | |
570 | |
571 if (data.static || !data.transport && !data.protocol) { | |
572 if (data.static && this.enabled('browser client')) { | |
573 this.static.write(data.path, req, res); | |
574 } else { | |
575 res.writeHead(200); | |
576 res.end('Welcome to socket.io.'); | |
577 | |
578 this.log.info('unhandled socket.io url'); | |
579 } | |
580 | |
581 return; | |
582 } | |
583 | |
584 if (data.protocol != protocol) { | |
585 res.writeHead(500); | |
586 res.end('Protocol version not supported.'); | |
587 | |
588 this.log.info('client protocol version unsupported'); | |
589 } else { | |
590 if (data.id) { | |
591 this.handleHTTPRequest(data, req, res); | |
592 } else { | |
593 this.handleHandshake(data, req, res); | |
594 } | |
595 } | |
596 }; | |
597 | |
598 /** | |
599 * Handles an HTTP Upgrade. | |
600 * | |
601 * @api private | |
602 */ | |
603 | |
604 Manager.prototype.handleUpgrade = function (req, socket, head) { | |
605 var data = this.checkRequest(req) | |
606 , self = this; | |
607 | |
608 if (!data) { | |
609 if (this.enabled('destroy upgrade')) { | |
610 socket.end(); | |
611 this.log.debug('destroying non-socket.io upgrade'); | |
612 } | |
613 | |
614 return; | |
615 } | |
616 | |
617 req.head = head; | |
618 this.handleClient(data, req); | |
619 req.head = null; | |
620 }; | |
621 | |
622 /** | |
623 * Handles a normal handshaken HTTP request (eg: long-polling) | |
624 * | |
625 * @api private | |
626 */ | |
627 | |
628 Manager.prototype.handleHTTPRequest = function (data, req, res) { | |
629 req.res = res; | |
630 this.handleClient(data, req); | |
631 }; | |
632 | |
633 /** | |
634 * Intantiantes a new client. | |
635 * | |
636 * @api private | |
637 */ | |
638 | |
639 Manager.prototype.handleClient = function (data, req) { | |
640 var socket = req.socket | |
641 , store = this.store | |
642 , self = this; | |
643 | |
644 // handle sync disconnect xhrs | |
645 if (undefined != data.query.disconnect) { | |
646 if (this.transports[data.id] && this.transports[data.id].open) { | |
647 this.transports[data.id].onForcedDisconnect(); | |
648 } else { | |
649 this.store.publish('disconnect-force:' + data.id); | |
650 } | |
651 req.res.writeHead(200); | |
652 req.res.end(); | |
653 return; | |
654 } | |
655 | |
656 if (!~this.get('transports').indexOf(data.transport)) { | |
657 this.log.warn('unknown transport: "' + data.transport + '"'); | |
658 req.connection.end(); | |
659 return; | |
660 } | |
661 | |
662 var transport = new transports[data.transport](this, data, req) | |
663 , handshaken = this.handshaken[data.id]; | |
664 | |
665 if (transport.disconnected) { | |
666 // failed during transport setup | |
667 req.connection.end(); | |
668 return; | |
669 } | |
670 if (handshaken) { | |
671 if (transport.open) { | |
672 if (this.closed[data.id] && this.closed[data.id].length) { | |
673 transport.payload(this.closed[data.id]); | |
674 this.closed[data.id] = []; | |
675 } | |
676 | |
677 this.onOpen(data.id); | |
678 this.store.publish('open', data.id); | |
679 this.transports[data.id] = transport; | |
680 } | |
681 | |
682 if (!this.connected[data.id]) { | |
683 this.onConnect(data.id); | |
684 this.store.publish('connect', data.id); | |
685 | |
686 // flag as used | |
687 delete handshaken.issued; | |
688 this.onHandshake(data.id, handshaken); | |
689 this.store.publish('handshake', data.id, handshaken); | |
690 | |
691 // initialize the socket for all namespaces | |
692 for (var i in this.namespaces) { | |
693 if (this.namespaces.hasOwnProperty(i)) { | |
694 var socket = this.namespaces[i].socket(data.id, true); | |
695 | |
696 // echo back connect packet and fire connection event | |
697 if (i === '') { | |
698 this.namespaces[i].handlePacket(data.id, { type: 'connect' }); | |
699 } | |
700 } | |
701 } | |
702 | |
703 this.store.subscribe('message:' + data.id, function (packet) { | |
704 self.onClientMessage(data.id, packet); | |
705 }); | |
706 | |
707 this.store.subscribe('disconnect:' + data.id, function (reason) { | |
708 self.onClientDisconnect(data.id, reason); | |
709 }); | |
710 } | |
711 } else { | |
712 if (transport.open) { | |
713 transport.error('client not handshaken', 'reconnect'); | |
714 } | |
715 | |
716 transport.discard(); | |
717 } | |
718 }; | |
719 | |
720 /** | |
721 * Generates a session id. | |
722 * | |
723 * @api private | |
724 */ | |
725 | |
726 Manager.prototype.generateId = function () { | |
727 var rand = new Buffer(15); // multiple of 3 for base64 | |
728 if (!rand.writeInt32BE) { | |
729 return Math.abs(Math.random() * Math.random() * Date.now() | 0).toString() | |
730 + Math.abs(Math.random() * Math.random() * Date.now() | 0).toString(); | |
731 } | |
732 this.sequenceNumber = (this.sequenceNumber + 1) | 0; | |
733 rand.writeInt32BE(this.sequenceNumber, 11); | |
734 if (crypto.randomBytes) { | |
735 crypto.randomBytes(12).copy(rand); | |
736 } else { | |
737 // not secure for node 0.4 | |
738 [0, 4, 8].forEach(function(i) { | |
739 rand.writeInt32BE(Math.random() * Math.pow(2, 32) | 0, i); | |
740 }); | |
741 } | |
742 return rand.toString('base64').replace(/\//g, '_').replace(/\+/g, '-'); | |
743 }; | |
744 | |
745 /** | |
746 * Handles a handshake request. | |
747 * | |
748 * @api private | |
749 */ | |
750 | |
751 Manager.prototype.handleHandshake = function (data, req, res) { | |
752 var self = this | |
753 , origin = req.headers.origin | |
754 , headers = { | |
755 'Content-Type': 'text/plain' | |
756 }; | |
757 | |
758 function writeErr (status, message) { | |
759 if (data.query.jsonp && jsonpolling_re.test(data.query.jsonp)) { | |
760 res.writeHead(200, { 'Content-Type': 'application/javascript' }); | |
761 res.end('io.j[' + data.query.jsonp + '](new Error("' + message + '"));'); | |
762 } else { | |
763 res.writeHead(status, headers); | |
764 res.end(message); | |
765 } | |
766 }; | |
767 | |
768 function error (err) { | |
769 writeErr(500, 'handshake error'); | |
770 self.log.warn('handshake error ' + err); | |
771 }; | |
772 | |
773 if (!this.verifyOrigin(req)) { | |
774 writeErr(403, 'handshake bad origin'); | |
775 return; | |
776 } | |
777 | |
778 var handshakeData = this.handshakeData(data); | |
779 | |
780 if (origin) { | |
781 // https://developer.mozilla.org/En/HTTP_Access_Control | |
782 headers['Access-Control-Allow-Origin'] = origin; | |
783 headers['Access-Control-Allow-Credentials'] = 'true'; | |
784 } | |
785 | |
786 this.authorize(handshakeData, function (err, authorized, newData) { | |
787 if (err) return error(err); | |
788 | |
789 if (authorized) { | |
790 var id = self.generateId() | |
791 , hs = [ | |
792 id | |
793 , self.enabled('heartbeats') ? self.get('heartbeat timeout') || '' : '' | |
794 , self.get('close timeout') || '' | |
795 , self.transports(data).join(',') | |
796 ].join(':'); | |
797 | |
798 if (data.query.jsonp && jsonpolling_re.test(data.query.jsonp)) { | |
799 hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');'; | |
800 res.writeHead(200, { 'Content-Type': 'application/javascript' }); | |
801 } else { | |
802 res.writeHead(200, headers); | |
803 } | |
804 | |
805 res.end(hs); | |
806 | |
807 self.onHandshake(id, newData || handshakeData); | |
808 self.store.publish('handshake', id, newData || handshakeData); | |
809 | |
810 self.log.info('handshake authorized', id); | |
811 } else { | |
812 writeErr(403, 'handshake unauthorized'); | |
813 self.log.info('handshake unauthorized'); | |
814 } | |
815 }) | |
816 }; | |
817 | |
818 /** | |
819 * Gets normalized handshake data | |
820 * | |
821 * @api private | |
822 */ | |
823 | |
824 Manager.prototype.handshakeData = function (data) { | |
825 var connection = data.request.connection | |
826 , connectionAddress | |
827 , date = new Date; | |
828 | |
829 if (connection.remoteAddress) { | |
830 connectionAddress = { | |
831 address: connection.remoteAddress | |
832 , port: connection.remotePort | |
833 }; | |
834 } else if (connection.socket && connection.socket.remoteAddress) { | |
835 connectionAddress = { | |
836 address: connection.socket.remoteAddress | |
837 , port: connection.socket.remotePort | |
838 }; | |
839 } | |
840 | |
841 return { | |
842 headers: data.headers | |
843 , address: connectionAddress | |
844 , time: date.toString() | |
845 , query: data.query | |
846 , url: data.request.url | |
847 , xdomain: !!data.request.headers.origin | |
848 , secure: data.request.connection.secure | |
849 , issued: +date | |
850 }; | |
851 }; | |
852 | |
853 /** | |
854 * Verifies the origin of a request. | |
855 * | |
856 * @api private | |
857 */ | |
858 | |
859 Manager.prototype.verifyOrigin = function (request) { | |
860 var origin = request.headers.origin || request.headers.referer | |
861 , origins = this.get('origins'); | |
862 | |
863 if (origin === 'null') origin = '*'; | |
864 | |
865 if (origins.indexOf('*:*') !== -1) { | |
866 return true; | |
867 } | |
868 | |
869 if (origin) { | |
870 try { | |
871 var parts = url.parse(origin); | |
872 parts.port = parts.port || 80; | |
873 var ok = | |
874 ~origins.indexOf(parts.hostname + ':' + parts.port) || | |
875 ~origins.indexOf(parts.hostname + ':*') || | |
876 ~origins.indexOf('*:' + parts.port); | |
877 if (!ok) this.log.warn('illegal origin: ' + origin); | |
878 return ok; | |
879 } catch (ex) { | |
880 this.log.warn('error parsing origin'); | |
881 } | |
882 } | |
883 else { | |
884 this.log.warn('origin missing from handshake, yet required by config'); | |
885 } | |
886 return false; | |
887 }; | |
888 | |
889 /** | |
890 * Handles an incoming packet. | |
891 * | |
892 * @api private | |
893 */ | |
894 | |
895 Manager.prototype.handlePacket = function (sessid, packet) { | |
896 this.of(packet.endpoint || '').handlePacket(sessid, packet); | |
897 }; | |
898 | |
899 /** | |
900 * Performs authentication. | |
901 * | |
902 * @param Object client request data | |
903 * @api private | |
904 */ | |
905 | |
906 Manager.prototype.authorize = function (data, fn) { | |
907 if (this.get('authorization')) { | |
908 var self = this; | |
909 | |
910 this.get('authorization').call(this, data, function (err, authorized) { | |
911 self.log.debug('client ' + authorized ? 'authorized' : 'unauthorized'); | |
912 fn(err, authorized); | |
913 }); | |
914 } else { | |
915 this.log.debug('client authorized'); | |
916 fn(null, true); | |
917 } | |
918 | |
919 return this; | |
920 }; | |
921 | |
922 /** | |
923 * Retrieves the transports adviced to the user. | |
924 * | |
925 * @api private | |
926 */ | |
927 | |
928 Manager.prototype.transports = function (data) { | |
929 var transp = this.get('transports') | |
930 , ret = []; | |
931 | |
932 for (var i = 0, l = transp.length; i < l; i++) { | |
933 var transport = transp[i]; | |
934 | |
935 if (transport) { | |
936 if (!transport.checkClient || transport.checkClient(data)) { | |
937 ret.push(transport); | |
938 } | |
939 } | |
940 } | |
941 | |
942 return ret; | |
943 }; | |
944 | |
945 /** | |
946 * Checks whether a request is a socket.io one. | |
947 * | |
948 * @return {Object} a client request data object or `false` | |
949 * @api private | |
950 */ | |
951 | |
952 var regexp = /^\/([^\/]+)\/?([^\/]+)?\/?([^\/]+)?\/?$/ | |
953 | |
954 Manager.prototype.checkRequest = function (req) { | |
955 var resource = this.get('resource'); | |
956 | |
957 var match; | |
958 if (typeof resource === 'string') { | |
959 match = req.url.substr(0, resource.length); | |
960 if (match !== resource) match = null; | |
961 } else { | |
962 match = resource.exec(req.url); | |
963 if (match) match = match[0]; | |
964 } | |
965 | |
966 if (match) { | |
967 var uri = url.parse(req.url.substr(match.length), true) | |
968 , path = uri.pathname || '' | |
969 , pieces = path.match(regexp); | |
970 | |
971 // client request data | |
972 var data = { | |
973 query: uri.query || {} | |
974 , headers: req.headers | |
975 , request: req | |
976 , path: path | |
977 }; | |
978 | |
979 if (pieces) { | |
980 data.protocol = Number(pieces[1]); | |
981 data.transport = pieces[2]; | |
982 data.id = pieces[3]; | |
983 data.static = !!this.static.has(path); | |
984 }; | |
985 | |
986 return data; | |
987 } | |
988 | |
989 return false; | |
990 }; | |
991 | |
992 /** | |
993 * Declares a socket namespace | |
994 * | |
995 * @api public | |
996 */ | |
997 | |
998 Manager.prototype.of = function (nsp) { | |
999 if (this.namespaces[nsp]) { | |
1000 return this.namespaces[nsp]; | |
1001 } | |
1002 | |
1003 return this.namespaces[nsp] = new SocketNamespace(this, nsp); | |
1004 }; | |
1005 | |
1006 /** | |
1007 * Perform garbage collection on long living objects and properties that cannot | |
1008 * be removed automatically. | |
1009 * | |
1010 * @api private | |
1011 */ | |
1012 | |
1013 Manager.prototype.garbageCollection = function () { | |
1014 // clean up unused handshakes | |
1015 var ids = Object.keys(this.handshaken) | |
1016 , i = ids.length | |
1017 , now = Date.now() | |
1018 , handshake; | |
1019 | |
1020 while (i--) { | |
1021 handshake = this.handshaken[ids[i]]; | |
1022 | |
1023 if ('issued' in handshake && (now - handshake.issued) >= 3E4) { | |
1024 this.onDisconnect(ids[i]); | |
1025 } | |
1026 } | |
1027 }; |