mirror of
https://github.com/Hopiu/postal.js.git
synced 2026-03-17 22:50:22 +00:00
961 lines
20 KiB
JavaScript
961 lines
20 KiB
JavaScript
/*!
|
|
* socket.io-node
|
|
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
|
* MIT Licensed
|
|
*/
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var fs = require( 'fs' )
|
|
, url = require( 'url' )
|
|
, tty = require( 'tty' )
|
|
, util = require( './util' )
|
|
, store = require( './store' )
|
|
, client = require( 'socket.io-client' )
|
|
, transports = require( './transports' )
|
|
, Logger = require( './logger' )
|
|
, Socket = require( './socket' )
|
|
, MemoryStore = require( './stores/memory' )
|
|
, SocketNamespace = require( './namespace' )
|
|
, Static = require( './static' )
|
|
, EventEmitter = process.EventEmitter;
|
|
|
|
/**
|
|
* Export the constructor.
|
|
*/
|
|
|
|
exports = module.exports = Manager;
|
|
|
|
/**
|
|
* Default transports.
|
|
*/
|
|
|
|
var defaultTransports = exports.defaultTransports = [
|
|
'websocket'
|
|
, 'htmlfile'
|
|
, 'xhr-polling'
|
|
, 'jsonp-polling'
|
|
];
|
|
|
|
/**
|
|
* Inherited defaults.
|
|
*/
|
|
|
|
var parent = module.parent.exports
|
|
, protocol = parent.protocol;
|
|
|
|
/**
|
|
* Manager constructor.
|
|
*
|
|
* @param {HTTPServer} server
|
|
* @param {Object} options, optional
|
|
* @api public
|
|
*/
|
|
|
|
function Manager( server, options ) {
|
|
this.server = server;
|
|
this.namespaces = {};
|
|
this.sockets = this.of( '' );
|
|
this.settings = {
|
|
origins : '*:*', log : true, store : new MemoryStore, logger : new Logger, static : new Static( this ), heartbeats : true, resource : '/socket.io', transports : defaultTransports, authorization : false, blacklist : ['disconnect'], 'log level' : 3, 'log colors' : tty.isatty( process.stdout.fd ), 'close timeout' : 60, 'heartbeat interval' : 25, 'heartbeat timeout' : 60, 'polling duration' : 20, 'flash policy server' : true, 'flash policy port' : 10843, 'destroy upgrade' : true, 'destroy buffer size' : 10E7, 'browser client' : true, 'browser client cache' : true, 'browser client minification' : false, 'browser client etag' : false, 'browser client expires' : 315360000, 'browser client gzip' : false, 'browser client handler' : false, 'client store expiration' : 15, 'match origin protocol' : false
|
|
};
|
|
|
|
for ( var i in options ) {
|
|
this.settings[i] = options[i];
|
|
}
|
|
|
|
var self = this;
|
|
|
|
// default error handler
|
|
server.on( 'error', function ( err ) {
|
|
self.log.warn( 'error raised: ' + err );
|
|
} );
|
|
|
|
this.initStore();
|
|
|
|
this.on( 'set:store', function () {
|
|
self.initStore();
|
|
} );
|
|
|
|
// reset listeners
|
|
this.oldListeners = server.listeners( 'request' );
|
|
server.removeAllListeners( 'request' );
|
|
|
|
server.on( 'request', function ( req, res ) {
|
|
self.handleRequest( req, res );
|
|
} );
|
|
|
|
server.on( 'upgrade', function ( req, socket, head ) {
|
|
self.handleUpgrade( req, socket, head );
|
|
} );
|
|
|
|
server.on( 'close', function () {
|
|
clearInterval( self.gc );
|
|
} );
|
|
|
|
server.once( 'listening', function () {
|
|
self.gc = setInterval( self.garbageCollection.bind( self ), 10000 );
|
|
} );
|
|
|
|
for ( var i in transports ) {
|
|
if ( transports[i].init ) {
|
|
transports[i].init( this );
|
|
}
|
|
}
|
|
|
|
// forward-compatibility with 1.0
|
|
var self = this;
|
|
this.sockets.on( 'connection', function ( conn ) {
|
|
self.emit( 'connection', conn );
|
|
} );
|
|
|
|
this.log.info( 'socket.io started' );
|
|
}
|
|
;
|
|
|
|
Manager.prototype.__proto__ = EventEmitter.prototype
|
|
|
|
/**
|
|
* Store accessor shortcut.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.__defineGetter__( 'store', function () {
|
|
var store = this.get( 'store' );
|
|
store.manager = this;
|
|
return store;
|
|
} );
|
|
|
|
/**
|
|
* Logger accessor.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.__defineGetter__( 'log', function () {
|
|
var logger = this.get( 'logger' );
|
|
|
|
logger.level = this.get( 'log level' ) || -1;
|
|
logger.colors = this.get( 'log colors' );
|
|
logger.enabled = this.enabled( 'log' );
|
|
|
|
return logger;
|
|
} );
|
|
|
|
/**
|
|
* Static accessor.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.__defineGetter__( 'static', function () {
|
|
return this.get( 'static' );
|
|
} );
|
|
|
|
/**
|
|
* Get settings.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.get = function ( key ) {
|
|
return this.settings[key];
|
|
};
|
|
|
|
/**
|
|
* Set settings
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.set = function ( key, value ) {
|
|
if ( arguments.length == 1 ) {
|
|
return this.get( key );
|
|
}
|
|
this.settings[key] = value;
|
|
this.emit( 'set:' + key, this.settings[key], key );
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Enable a setting
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.enable = function ( key ) {
|
|
this.settings[key] = true;
|
|
this.emit( 'set:' + key, this.settings[key], key );
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Disable a setting
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.disable = function ( key ) {
|
|
this.settings[key] = false;
|
|
this.emit( 'set:' + key, this.settings[key], key );
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Checks if a setting is enabled
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.enabled = function ( key ) {
|
|
return !!this.settings[key];
|
|
};
|
|
|
|
/**
|
|
* Checks if a setting is disabled
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.disabled = function ( key ) {
|
|
return !this.settings[key];
|
|
};
|
|
|
|
/**
|
|
* Configure callbacks.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.configure = function ( env, fn ) {
|
|
if ( 'function' == typeof env ) {
|
|
env.call( this );
|
|
} else if ( env == (process.env.NODE_ENV || 'development') ) {
|
|
fn.call( this );
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Initializes everything related to the message dispatcher.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.initStore = function () {
|
|
this.handshaken = {};
|
|
this.connected = {};
|
|
this.open = {};
|
|
this.closed = {};
|
|
this.rooms = {};
|
|
this.roomClients = {};
|
|
|
|
var self = this;
|
|
|
|
this.store.subscribe( 'handshake', function ( id, data ) {
|
|
self.onHandshake( id, data );
|
|
} );
|
|
|
|
this.store.subscribe( 'connect', function ( id ) {
|
|
self.onConnect( id );
|
|
} );
|
|
|
|
this.store.subscribe( 'open', function ( id ) {
|
|
self.onOpen( id );
|
|
} );
|
|
|
|
this.store.subscribe( 'join', function ( id, room ) {
|
|
self.onJoin( id, room );
|
|
} );
|
|
|
|
this.store.subscribe( 'leave', function ( id, room ) {
|
|
self.onLeave( id, room );
|
|
} );
|
|
|
|
this.store.subscribe( 'close', function ( id ) {
|
|
self.onClose( id );
|
|
} );
|
|
|
|
this.store.subscribe( 'dispatch', function ( room, packet, volatile, exceptions ) {
|
|
self.onDispatch( room, packet, volatile, exceptions );
|
|
} );
|
|
|
|
this.store.subscribe( 'disconnect', function ( id ) {
|
|
self.onDisconnect( id );
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Called when a client handshakes.
|
|
*
|
|
* @param text
|
|
*/
|
|
|
|
Manager.prototype.onHandshake = function ( id, data ) {
|
|
this.handshaken[id] = data;
|
|
};
|
|
|
|
/**
|
|
* Called when a client connects (ie: transport first opens)
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.onConnect = function ( id ) {
|
|
this.connected[id] = true;
|
|
};
|
|
|
|
/**
|
|
* Called when a client opens a request in a different node.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.onOpen = function ( id ) {
|
|
this.open[id] = true;
|
|
|
|
// if we were buffering messages for the client, clear them
|
|
if ( this.closed[id] ) {
|
|
var self = this;
|
|
|
|
this.store.unsubscribe( 'dispatch:' + id, function () {
|
|
delete self.closed[id];
|
|
} );
|
|
}
|
|
|
|
// clear the current transport
|
|
if ( this.transports[id] ) {
|
|
this.transports[id].discard();
|
|
this.transports[id] = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called when a message is sent to a namespace and/or room.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.onDispatch = function ( room, packet, volatile, exceptions ) {
|
|
if ( this.rooms[room] ) {
|
|
for ( var i = 0, l = this.rooms[room].length; i < l; i++ ) {
|
|
var id = this.rooms[room][i];
|
|
|
|
if ( !~exceptions.indexOf( id ) ) {
|
|
if ( this.transports[id] && this.transports[id].open ) {
|
|
this.transports[id].onDispatch( packet, volatile );
|
|
} else if ( !volatile ) {
|
|
this.onClientDispatch( id, packet );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called when a client joins a nsp / room.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.onJoin = function ( id, name ) {
|
|
if ( !this.roomClients[id] ) {
|
|
this.roomClients[id] = {};
|
|
}
|
|
|
|
if ( !this.rooms[name] ) {
|
|
this.rooms[name] = [];
|
|
}
|
|
|
|
if ( !~this.rooms[name].indexOf( id ) ) {
|
|
this.rooms[name].push( id );
|
|
this.roomClients[id][name] = true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called when a client leaves a nsp / room.
|
|
*
|
|
* @param private
|
|
*/
|
|
|
|
Manager.prototype.onLeave = function ( id, room ) {
|
|
if ( this.rooms[room] ) {
|
|
var index = this.rooms[room].indexOf( id );
|
|
|
|
if ( index >= 0 ) {
|
|
this.rooms[room].splice( index, 1 );
|
|
}
|
|
|
|
if ( !this.rooms[room].length ) {
|
|
delete this.rooms[room];
|
|
}
|
|
delete this.roomClients[id][room];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Called when a client closes a request in different node.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.onClose = function ( id ) {
|
|
if ( this.open[id] ) {
|
|
delete this.open[id];
|
|
}
|
|
|
|
this.closed[id] = [];
|
|
|
|
var self = this;
|
|
|
|
this.store.subscribe( 'dispatch:' + id, function ( packet, volatile ) {
|
|
if ( !volatile ) {
|
|
self.onClientDispatch( id, packet );
|
|
}
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Dispatches a message for a closed client.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.onClientDispatch = function ( id, packet ) {
|
|
if ( this.closed[id] ) {
|
|
this.closed[id].push( packet );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Receives a message for a client.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.onClientMessage = function ( id, packet ) {
|
|
if ( this.namespaces[packet.endpoint] ) {
|
|
this.namespaces[packet.endpoint].handlePacket( id, packet );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Fired when a client disconnects (not triggered).
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.onClientDisconnect = function ( id, reason ) {
|
|
for ( var name in this.namespaces ) {
|
|
this.namespaces[name].handleDisconnect( id, reason, typeof this.roomClients[id] !== 'undefined' &&
|
|
typeof this.roomClients[id][name] !== 'undefined' );
|
|
}
|
|
|
|
this.onDisconnect( id );
|
|
};
|
|
|
|
/**
|
|
* Called when a client disconnects.
|
|
*
|
|
* @param text
|
|
*/
|
|
|
|
Manager.prototype.onDisconnect = function ( id, local ) {
|
|
delete this.handshaken[id];
|
|
|
|
if ( this.open[id] ) {
|
|
delete this.open[id];
|
|
}
|
|
|
|
if ( this.connected[id] ) {
|
|
delete this.connected[id];
|
|
}
|
|
|
|
if ( this.transports[id] ) {
|
|
this.transports[id].discard();
|
|
delete this.transports[id];
|
|
}
|
|
|
|
if ( this.closed[id] ) {
|
|
delete this.closed[id];
|
|
}
|
|
|
|
if ( this.roomClients[id] ) {
|
|
for ( var room in this.roomClients[id] ) {
|
|
this.onLeave( id, room );
|
|
}
|
|
delete this.roomClients[id]
|
|
}
|
|
|
|
this.store.destroyClient( id, this.get( 'client store expiration' ) );
|
|
|
|
this.store.unsubscribe( 'dispatch:' + id );
|
|
|
|
if ( local ) {
|
|
this.store.unsubscribe( 'message:' + id );
|
|
this.store.unsubscribe( 'disconnect:' + id );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handles an HTTP request.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.handleRequest = function ( req, res ) {
|
|
var data = this.checkRequest( req );
|
|
|
|
if ( !data ) {
|
|
for ( var i = 0, l = this.oldListeners.length; i < l; i++ ) {
|
|
this.oldListeners[i].call( this.server, req, res );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( data.static || !data.transport && !data.protocol ) {
|
|
if ( data.static && this.enabled( 'browser client' ) ) {
|
|
this.static.write( data.path, req, res );
|
|
} else {
|
|
res.writeHead( 200 );
|
|
res.end( 'Welcome to socket.io.' );
|
|
|
|
this.log.info( 'unhandled socket.io url' );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( data.protocol != protocol ) {
|
|
res.writeHead( 500 );
|
|
res.end( 'Protocol version not supported.' );
|
|
|
|
this.log.info( 'client protocol version unsupported' );
|
|
} else {
|
|
if ( data.id ) {
|
|
this.handleHTTPRequest( data, req, res );
|
|
} else {
|
|
this.handleHandshake( data, req, res );
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handles an HTTP Upgrade.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.handleUpgrade = function ( req, socket, head ) {
|
|
var data = this.checkRequest( req )
|
|
, self = this;
|
|
|
|
if ( !data ) {
|
|
if ( this.enabled( 'destroy upgrade' ) ) {
|
|
socket.end();
|
|
this.log.debug( 'destroying non-socket.io upgrade' );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
req.head = head;
|
|
this.handleClient( data, req );
|
|
};
|
|
|
|
/**
|
|
* Handles a normal handshaken HTTP request (eg: long-polling)
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.handleHTTPRequest = function ( data, req, res ) {
|
|
req.res = res;
|
|
this.handleClient( data, req );
|
|
};
|
|
|
|
/**
|
|
* Intantiantes a new client.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.handleClient = function ( data, req ) {
|
|
var socket = req.socket
|
|
, store = this.store
|
|
, self = this;
|
|
|
|
if ( undefined != data.query.disconnect ) {
|
|
if ( this.transports[data.id] && this.transports[data.id].open ) {
|
|
this.transports[data.id].onForcedDisconnect();
|
|
} else {
|
|
this.store.publish( 'disconnect-force:' + data.id );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( !~this.get( 'transports' ).indexOf( data.transport ) ) {
|
|
this.log.warn( 'unknown transport: "' + data.transport + '"' );
|
|
req.connection.end();
|
|
return;
|
|
}
|
|
|
|
var transport = new transports[data.transport]( this, data, req )
|
|
, handshaken = this.handshaken[data.id];
|
|
|
|
if ( transport.disconnected ) {
|
|
// failed during transport setup
|
|
req.connection.end();
|
|
return;
|
|
}
|
|
if ( handshaken ) {
|
|
if ( transport.open ) {
|
|
if ( this.closed[data.id] && this.closed[data.id].length ) {
|
|
transport.payload( this.closed[data.id] );
|
|
this.closed[data.id] = [];
|
|
}
|
|
|
|
this.onOpen( data.id );
|
|
this.store.publish( 'open', data.id );
|
|
this.transports[data.id] = transport;
|
|
}
|
|
|
|
if ( !this.connected[data.id] ) {
|
|
this.onConnect( data.id );
|
|
this.store.publish( 'connect', data.id );
|
|
|
|
// flag as used
|
|
delete handshaken.issued;
|
|
this.onHandshake( data.id, handshaken );
|
|
this.store.publish( 'handshake', data.id, handshaken );
|
|
|
|
// initialize the socket for all namespaces
|
|
for ( var i in this.namespaces ) {
|
|
var socket = this.namespaces[i].socket( data.id, true );
|
|
|
|
// echo back connect packet and fire connection event
|
|
if ( i === '' ) {
|
|
this.namespaces[i].handlePacket( data.id, { type : 'connect' } );
|
|
}
|
|
}
|
|
|
|
this.store.subscribe( 'message:' + data.id, function ( packet ) {
|
|
self.onClientMessage( data.id, packet );
|
|
} );
|
|
|
|
this.store.subscribe( 'disconnect:' + data.id, function ( reason ) {
|
|
self.onClientDisconnect( data.id, reason );
|
|
} );
|
|
}
|
|
} else {
|
|
if ( transport.open ) {
|
|
transport.error( 'client not handshaken', 'reconnect' );
|
|
}
|
|
|
|
transport.discard();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generates a session id.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.generateId = function () {
|
|
return Math.abs( Math.random() * Math.random() * Date.now() | 0 ).toString()
|
|
+ Math.abs( Math.random() * Math.random() * Date.now() | 0 ).toString();
|
|
};
|
|
|
|
/**
|
|
* Handles a handshake request.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.handleHandshake = function ( data, req, res ) {
|
|
var self = this
|
|
, origin = req.headers.origin
|
|
, headers = {
|
|
'Content-Type' : 'text/plain'
|
|
};
|
|
|
|
function writeErr( status, message ) {
|
|
if ( data.query.jsonp ) {
|
|
res.writeHead( 200, { 'Content-Type' : 'application/javascript' } );
|
|
res.end( 'io.j[' + data.query.jsonp + '](new Error("' + message + '"));' );
|
|
} else {
|
|
res.writeHead( status, headers );
|
|
res.end( message );
|
|
}
|
|
}
|
|
|
|
;
|
|
|
|
function error( err ) {
|
|
writeErr( 500, 'handshake error' );
|
|
self.log.warn( 'handshake error ' + err );
|
|
}
|
|
|
|
;
|
|
|
|
if ( !this.verifyOrigin( req ) ) {
|
|
writeErr( 403, 'handshake bad origin' );
|
|
return;
|
|
}
|
|
|
|
var handshakeData = this.handshakeData( data );
|
|
|
|
if ( origin ) {
|
|
// https://developer.mozilla.org/En/HTTP_Access_Control
|
|
headers['Access-Control-Allow-Origin'] = origin;
|
|
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
}
|
|
|
|
this.authorize( handshakeData, function ( err, authorized, newData ) {
|
|
if ( err ) {
|
|
return error( err );
|
|
}
|
|
|
|
if ( authorized ) {
|
|
var id = self.generateId()
|
|
, hs = [
|
|
id
|
|
, self.enabled( 'heartbeats' ) ? self.get( 'heartbeat timeout' ) || '' : ''
|
|
, self.get( 'close timeout' ) || ''
|
|
, self.transports( data ).join( ',' )
|
|
].join( ':' );
|
|
|
|
if ( data.query.jsonp ) {
|
|
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify( hs ) + ');';
|
|
res.writeHead( 200, { 'Content-Type' : 'application/javascript' } );
|
|
} else {
|
|
res.writeHead( 200, headers );
|
|
}
|
|
|
|
res.end( hs );
|
|
|
|
self.onHandshake( id, newData || handshakeData );
|
|
self.store.publish( 'handshake', id, newData || handshakeData );
|
|
|
|
self.log.info( 'handshake authorized', id );
|
|
} else {
|
|
writeErr( 403, 'handshake unauthorized' );
|
|
self.log.info( 'handshake unauthorized' );
|
|
}
|
|
} )
|
|
};
|
|
|
|
/**
|
|
* Gets normalized handshake data
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.handshakeData = function ( data ) {
|
|
var connection = data.request.connection
|
|
, connectionAddress
|
|
, date = new Date;
|
|
|
|
if ( connection.remoteAddress ) {
|
|
connectionAddress = {
|
|
address : connection.remoteAddress, port : connection.remotePort
|
|
};
|
|
} else if ( connection.socket && connection.socket.remoteAddress ) {
|
|
connectionAddress = {
|
|
address : connection.socket.remoteAddress, port : connection.socket.remotePort
|
|
};
|
|
}
|
|
|
|
return {
|
|
headers : data.headers, address : connectionAddress, time : date.toString(), query : data.query, url : data.request.url, xdomain : !!data.request.headers.origin, secure : data.request.connection.secure, issued : +date
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Verifies the origin of a request.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.verifyOrigin = function ( request ) {
|
|
var origin = request.headers.origin || request.headers.referer
|
|
, origins = this.get( 'origins' );
|
|
|
|
if ( origin === 'null' ) {
|
|
origin = '*';
|
|
}
|
|
|
|
if ( origins.indexOf( '*:*' ) !== -1 ) {
|
|
return true;
|
|
}
|
|
|
|
if ( origin ) {
|
|
try {
|
|
var parts = url.parse( origin );
|
|
parts.port = parts.port || 80;
|
|
var ok =
|
|
~origins.indexOf( parts.hostname + ':' + parts.port ) ||
|
|
~origins.indexOf( parts.hostname + ':*' ) ||
|
|
~origins.indexOf( '*:' + parts.port );
|
|
if ( !ok ) {
|
|
this.log.warn( 'illegal origin: ' + origin );
|
|
}
|
|
return ok;
|
|
} catch ( ex ) {
|
|
this.log.warn( 'error parsing origin' );
|
|
}
|
|
}
|
|
else {
|
|
this.log.warn( 'origin missing from handshake, yet required by config' );
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Handles an incoming packet.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.handlePacket = function ( sessid, packet ) {
|
|
this.of( packet.endpoint || '' ).handlePacket( sessid, packet );
|
|
};
|
|
|
|
/**
|
|
* Performs authentication.
|
|
*
|
|
* @param Object client request data
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.authorize = function ( data, fn ) {
|
|
if ( this.get( 'authorization' ) ) {
|
|
var self = this;
|
|
|
|
this.get( 'authorization' ).call( this, data, function ( err, authorized ) {
|
|
self.log.debug( 'client ' + authorized ? 'authorized' : 'unauthorized' );
|
|
fn( err, authorized );
|
|
} );
|
|
} else {
|
|
this.log.debug( 'client authorized' );
|
|
fn( null, true );
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Retrieves the transports adviced to the user.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.transports = function ( data ) {
|
|
var transp = this.get( 'transports' )
|
|
, ret = [];
|
|
|
|
for ( var i = 0, l = transp.length; i < l; i++ ) {
|
|
var transport = transp[i];
|
|
|
|
if ( transport ) {
|
|
if ( !transport.checkClient || transport.checkClient( data ) ) {
|
|
ret.push( transport );
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Checks whether a request is a socket.io one.
|
|
*
|
|
* @return {Object} a client request data object or `false`
|
|
* @api private
|
|
*/
|
|
|
|
var regexp = /^\/([^\/]+)\/?([^\/]+)?\/?([^\/]+)?\/?$/
|
|
|
|
Manager.prototype.checkRequest = function ( req ) {
|
|
var resource = this.get( 'resource' );
|
|
|
|
var match;
|
|
if ( typeof resource === 'string' ) {
|
|
match = req.url.substr( 0, resource.length );
|
|
if ( match !== resource ) {
|
|
match = null;
|
|
}
|
|
} else {
|
|
match = resource.exec( req.url );
|
|
if ( match ) {
|
|
match = match[0];
|
|
}
|
|
}
|
|
|
|
if ( match ) {
|
|
var uri = url.parse( req.url.substr( match.length ), true )
|
|
, path = uri.pathname || ''
|
|
, pieces = path.match( regexp );
|
|
|
|
// client request data
|
|
var data = {
|
|
query : uri.query || {}, headers : req.headers, request : req, path : path
|
|
};
|
|
|
|
if ( pieces ) {
|
|
data.protocol = Number( pieces[1] );
|
|
data.transport = pieces[2];
|
|
data.id = pieces[3];
|
|
data.static = !!this.static.has( path );
|
|
}
|
|
;
|
|
|
|
return data;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Declares a socket namespace
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Manager.prototype.of = function ( nsp ) {
|
|
if ( this.namespaces[nsp] ) {
|
|
return this.namespaces[nsp];
|
|
}
|
|
|
|
return this.namespaces[nsp] = new SocketNamespace( this, nsp );
|
|
};
|
|
|
|
/**
|
|
* Perform garbage collection on long living objects and properties that cannot
|
|
* be removed automatically.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
Manager.prototype.garbageCollection = function () {
|
|
// clean up unused handshakes
|
|
var ids = Object.keys( this.handshaken )
|
|
, i = ids.length
|
|
, now = Date.now()
|
|
, handshake;
|
|
|
|
while ( i-- ) {
|
|
handshake = this.handshaken[ids[i]];
|
|
|
|
if ( 'issued' in handshake && (now - handshake.issued) >= 3E4 ) {
|
|
this.onDisconnect( ids[i] );
|
|
}
|
|
}
|
|
};
|