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