mirror of
https://github.com/Hopiu/postal.js.git
synced 2026-03-17 14:40:24 +00:00
362 lines
6.8 KiB
JavaScript
362 lines
6.8 KiB
JavaScript
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var Socket = require( './socket' )
|
|
, EventEmitter = process.EventEmitter
|
|
, parser = require( './parser' )
|
|
, util = require( './util' );
|
|
|
|
/**
|
|
* Exports the constructor.
|
|
*/
|
|
|
|
exports = module.exports = SocketNamespace;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @api public.
|
|
*/
|
|
|
|
function SocketNamespace( mgr, name ) {
|
|
this.manager = mgr;
|
|
this.name = name || '';
|
|
this.sockets = {};
|
|
this.auth = false;
|
|
this.setFlags();
|
|
}
|
|
;
|
|
|
|
/**
|
|
* Inherits from EventEmitter.
|
|
*/
|
|
|
|
SocketNamespace.prototype.__proto__ = EventEmitter.prototype;
|
|
|
|
/**
|
|
* Copies emit since we override it.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
SocketNamespace.prototype.$emit = EventEmitter.prototype.emit;
|
|
|
|
/**
|
|
* Retrieves all clients as Socket instances as an array.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.clients = function ( room ) {
|
|
var room = this.name + (room !== undefined ?
|
|
'/' + room : '');
|
|
|
|
if ( !this.manager.rooms[room] ) {
|
|
return [];
|
|
}
|
|
|
|
return this.manager.rooms[room].map( function ( id ) {
|
|
return this.socket( id );
|
|
}, this );
|
|
};
|
|
|
|
/**
|
|
* Access logger interface.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.__defineGetter__( 'log', function () {
|
|
return this.manager.log;
|
|
} );
|
|
|
|
/**
|
|
* Access store.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.__defineGetter__( 'store', function () {
|
|
return this.manager.store;
|
|
} );
|
|
|
|
/**
|
|
* JSON message flag.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.__defineGetter__( 'json', function () {
|
|
this.flags.json = true;
|
|
return this;
|
|
} );
|
|
|
|
/**
|
|
* Volatile message flag.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.__defineGetter__( 'volatile', function () {
|
|
this.flags.volatile = true;
|
|
return this;
|
|
} );
|
|
|
|
/**
|
|
* Overrides the room to relay messages to (flag).
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.in = SocketNamespace.prototype.to = function ( room ) {
|
|
this.flags.endpoint = this.name + (room ? '/' + room : '');
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Adds a session id we should prevent relaying messages to (flag).
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.except = function ( id ) {
|
|
this.flags.exceptions.push( id );
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Sets the default flags.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
SocketNamespace.prototype.setFlags = function () {
|
|
this.flags = {
|
|
endpoint : this.name, exceptions : []
|
|
};
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Sends out a packet.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
SocketNamespace.prototype.packet = function ( packet ) {
|
|
packet.endpoint = this.name;
|
|
|
|
var store = this.store
|
|
, log = this.log
|
|
, volatile = this.flags.volatile
|
|
, exceptions = this.flags.exceptions
|
|
, packet = parser.encodePacket( packet );
|
|
|
|
this.manager.onDispatch( this.flags.endpoint, packet, volatile, exceptions );
|
|
this.store.publish( 'dispatch', this.flags.endpoint, packet, volatile, exceptions );
|
|
|
|
this.setFlags();
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Sends to everyone.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.send = function ( data ) {
|
|
return this.packet( {
|
|
type : this.flags.json ? 'json' : 'message', data : data
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Emits to everyone (override).
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.emit = function ( name ) {
|
|
if ( name == 'newListener' ) {
|
|
return this.$emit.apply( this, arguments );
|
|
}
|
|
|
|
return this.packet( {
|
|
type : 'event', name : name, args : util.toArray( arguments ).slice( 1 )
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Retrieves or creates a write-only socket for a client, unless specified.
|
|
*
|
|
* @param {Boolean} whether the socket will be readable when initialized
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.socket = function ( sid, readable ) {
|
|
if ( !this.sockets[sid] ) {
|
|
this.sockets[sid] = new Socket( this.manager, sid, this, readable );
|
|
}
|
|
|
|
return this.sockets[sid];
|
|
};
|
|
|
|
/**
|
|
* Sets authorization for this namespace.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
SocketNamespace.prototype.authorization = function ( fn ) {
|
|
this.auth = fn;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Called when a socket disconnects entirely.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
SocketNamespace.prototype.handleDisconnect = function ( sid, reason, raiseOnDisconnect ) {
|
|
if ( this.sockets[sid] && this.sockets[sid].readable ) {
|
|
if ( raiseOnDisconnect ) {
|
|
this.sockets[sid].onDisconnect( reason );
|
|
}
|
|
delete this.sockets[sid];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Performs authentication.
|
|
*
|
|
* @param Object client request data
|
|
* @api private
|
|
*/
|
|
|
|
SocketNamespace.prototype.authorize = function ( data, fn ) {
|
|
if ( this.auth ) {
|
|
var self = this;
|
|
|
|
this.auth.call( this, data, function ( err, authorized ) {
|
|
self.log.debug( 'client ' +
|
|
(authorized ? '' : 'un') + 'authorized for ' + self.name );
|
|
fn( err, authorized );
|
|
} );
|
|
} else {
|
|
this.log.debug( 'client authorized for ' + this.name );
|
|
fn( null, true );
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Handles a packet.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
SocketNamespace.prototype.handlePacket = function ( sessid, packet ) {
|
|
var socket = this.socket( sessid )
|
|
, dataAck = packet.ack == 'data'
|
|
, manager = this.manager
|
|
, self = this;
|
|
|
|
function ack() {
|
|
self.log.debug( 'sending data ack packet' );
|
|
socket.packet( {
|
|
type : 'ack', args : util.toArray( arguments ), ackId : packet.id
|
|
} );
|
|
}
|
|
|
|
;
|
|
|
|
function error( err ) {
|
|
self.log.warn( 'handshake error ' + err + ' for ' + self.name );
|
|
socket.packet( { type : 'error', reason : err } );
|
|
}
|
|
|
|
;
|
|
|
|
function connect() {
|
|
self.manager.onJoin( sessid, self.name );
|
|
self.store.publish( 'join', sessid, self.name );
|
|
|
|
// packet echo
|
|
socket.packet( { type : 'connect' } );
|
|
|
|
// emit connection event
|
|
self.$emit( 'connection', socket );
|
|
}
|
|
|
|
;
|
|
|
|
switch ( packet.type ) {
|
|
case 'connect':
|
|
if ( packet.endpoint == '' ) {
|
|
connect();
|
|
} else {
|
|
var handshakeData = manager.handshaken[sessid];
|
|
|
|
this.authorize( handshakeData, function ( err, authorized, newData ) {
|
|
if ( err ) {
|
|
return error( err );
|
|
}
|
|
|
|
if ( authorized ) {
|
|
manager.onHandshake( sessid, newData || handshakeData );
|
|
self.store.publish( 'handshake', sessid, newData || handshakeData );
|
|
connect();
|
|
} else {
|
|
error( 'unauthorized' );
|
|
}
|
|
} );
|
|
}
|
|
break;
|
|
|
|
case 'ack':
|
|
if ( socket.acks[packet.ackId] ) {
|
|
socket.acks[packet.ackId].apply( socket, packet.args );
|
|
} else {
|
|
this.log.info( 'unknown ack packet' );
|
|
}
|
|
break;
|
|
|
|
case 'event':
|
|
// check if the emitted event is not blacklisted
|
|
if ( -~manager.get( 'blacklist' ).indexOf( packet.name ) ) {
|
|
this.log.debug( 'ignoring blacklisted event `' + packet.name + '`' );
|
|
} else {
|
|
var params = [packet.name].concat( packet.args );
|
|
|
|
if ( dataAck ) {
|
|
params.push( ack );
|
|
}
|
|
|
|
socket.$emit.apply( socket, params );
|
|
}
|
|
break;
|
|
|
|
case 'disconnect':
|
|
this.manager.onLeave( sessid, this.name );
|
|
this.store.publish( 'leave', sessid, this.name );
|
|
|
|
socket.$emit( 'disconnect', packet.reason || 'packet' );
|
|
break;
|
|
|
|
case 'json':
|
|
case 'message':
|
|
var params = ['message', packet.data];
|
|
|
|
if ( dataAck ) {
|
|
params.push( ack );
|
|
}
|
|
|
|
socket.$emit.apply( socket, params );
|
|
}
|
|
;
|
|
};
|