Files
socket.io/lib/socket.js
Kevin Roark 67f15b16d2 Updated protocol-facing code to reflect new binary protocol
This is a squash of 6 small commits. Below is a summary of each. The gist
is that manager.js encoding and decoding portions were changed to work
with the new socket.io-protocol; this includes handling of encoding
a list of packets, and handling sequences of binary packets.

Commit 1 was the initial rewrite.

Commit 2 got all the tests passing via bug fixes.

Commit 3 updated the has-binary-data dependency and the build.

Commit 4 added nice comments.

Commits 5 and 6 updated build and engine.io dependencies respectively.
2014-02-26 13:41:51 -05:00

359 lines
6.1 KiB
JavaScript

/**
* Module dependencies.
*/
var parser = require('socket.io-parser');
var Emitter = require('emitter');
var toArray = require('to-array');
var on = require('./on');
var bind = require('bind');
var debug = require('debug')('socket.io-client:socket');
var hasBin = require('has-binary-data');
var indexOf = require('indexof');
/**
* Module exports.
*/
module.exports = exports = Socket;
/**
* Internal events (blacklisted).
* These events can't be emitted by the user.
*
* @api private
*/
var events = {
connect: 1,
disconnect: 1,
error: 1
};
/**
* Shortcut to `Emitter#emit`.
*/
var emit = Emitter.prototype.emit;
/**
* `Socket` constructor.
*
* @api public
*/
function Socket(io, nsp){
this.io = io;
this.nsp = nsp;
this.json = this; // compat
this.ids = 0;
this.acks = {};
this.open();
this.buffer = [];
this.connected = false;
this.disconnected = true;
}
/**
* Mix in `Emitter`.
*/
Emitter(Socket.prototype);
/**
* Called upon engine `open`.
*
* @api private
*/
Socket.prototype.open =
Socket.prototype.connect = function(){
var io = this.io;
io.open(); // ensure open
if ('open' == this.io.readyState) this.onopen();
this.subs = [
on(io, 'open', bind(this, 'onopen')),
on(io, 'error', bind(this, 'onerror'))
];
};
/**
* Sends a `message` event.
*
* @return {Socket} self
* @api public
*/
Socket.prototype.send = function(){
var args = toArray(arguments);
args.unshift('message');
this.emit.apply(this, args);
return this;
};
/**
* Override `emit`.
* If the event is in `events`, it's emitted normally.
*
* @param {String} event name
* @return {Socket} self
* @api public
*/
Socket.prototype.emit = function(ev){
if (events.hasOwnProperty(ev)) {
emit.apply(this, arguments);
return this;
}
var args = toArray(arguments);
var parserType = parser.EVENT; // default
if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
var packet = { type: parserType, data: args };
// event ack callback
if ('function' == typeof args[args.length - 1]) {
debug('emitting packet with ack id %d', this.ids);
this.acks[this.ids] = args.pop();
packet.id = this.ids++;
}
this.packet(packet);
return this;
};
/**
* Sends a packet.
*
* @param {Object} packet
* @api private
*/
Socket.prototype.packet = function(packet){
packet.nsp = this.nsp;
this.io.packet(packet);
};
/**
* Called upon `error`.
*
* @param {Object} data
* @api private
*/
Socket.prototype.onerror = function(data){
this.emit('error', data);
};
/**
* "Opens" the socket.
*
* @api private
*/
Socket.prototype.onopen = function(){
debug('transport is open - connecting');
// write connect packet if necessary
if ('/' != this.nsp) {
this.packet({ type: parser.CONNECT });
}
// subscribe
var io = this.io;
this.subs.push(
on(io, 'packet', bind(this, 'onpacket')),
on(io, 'close', bind(this, 'onclose'))
);
};
/**
* Called upon engine `close`.
*
* @param {String} reason
* @api private
*/
Socket.prototype.onclose = function(reason){
debug('close (%s)', reason);
this.connected = false;
this.disconnected = true;
this.emit('disconnect', reason);
};
/**
* Called with socket packet.
*
* @param {Object} packet
* @api private
*/
Socket.prototype.onpacket = function(packet){
if (packet.nsp != this.nsp) return;
switch (packet.type) {
case parser.CONNECT:
this.onconnect();
break;
case parser.EVENT:
this.onevent(packet);
break;
case parser.BINARY_EVENT:
this.onevent(packet);
break;
case parser.ACK:
this.onack(packet);
break;
case parser.DISCONNECT:
this.ondisconnect();
break;
case parser.ERROR:
this.emit('error', packet.data);
break;
}
};
/**
* Called upon a server event.
*
* @param {Object} packet
* @api private
*/
Socket.prototype.onevent = function(packet){
var args = packet.data || [];
debug('emitting event %j', args);
if (null != packet.id) {
debug('attaching ack callback to event');
args.push(this.ack(packet.id));
}
if (this.connected) {
emit.apply(this, args);
} else {
this.buffer.push(args);
}
};
/**
* Produces an ack callback to emit with an event.
*
* @api private
*/
Socket.prototype.ack = function(id){
var self = this;
var sent = false;
return function(){
// prevent double callbacks
if (sent) return;
var args = toArray(arguments);
debug('sending ack %j', args);
self.packet({
type: parser.ACK,
id: id,
data: args
});
};
};
/**
* Called upon a server acknowlegement.
*
* @param {Object} packet
* @api private
*/
Socket.prototype.onack = function(packet){
debug('calling ack %s with %j', packet.id, packet.data);
var fn = this.acks[packet.id];
fn.apply(this, packet.data);
delete this.acks[packet.id];
};
/**
* Called upon server connect.
*
* @api private
*/
Socket.prototype.onconnect = function(){
this.connected = true;
this.disconnected = false;
this.emit('connect');
this.emitBuffered();
};
/**
* Emit buffered events.
*
* @api private
*/
Socket.prototype.emitBuffered = function(){
for (var i = 0; i < this.buffer.length; i++) {
emit.apply(this, this.buffer[i]);
}
this.buffer = [];
};
/**
* Called upon server disconnect.
*
* @api private
*/
Socket.prototype.ondisconnect = function(){
debug('server disconnect (%s)', this.nsp);
this.destroy();
this.onclose('io server disconnect');
};
/**
* Cleans up.
*
* @api private
*/
Socket.prototype.destroy = function(){
debug('destroying socket (%s)', this.nsp);
// manual close means no reconnect
for (var i = 0; i < this.subs.length; i++) {
this.subs[i].destroy();
}
// notify manager
this.io.destroy(this);
};
/**
* Disconnects the socket manually.
*
* @return {Socket} self
* @api public
*/
Socket.prototype.close =
Socket.prototype.disconnect = function(){
if (!this.connected) return this;
debug('performing disconnect (%s)', this.nsp);
this.packet({ type: parser.PACKET_DISCONNECT });
// destroy subscriptions
this.destroy();
// fire events
this.onclose('io client disconnect');
return this;
};