mirror of
https://github.com/socketio/socket.io.git
synced 2026-04-30 03:00:39 -04:00
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.
359 lines
6.1 KiB
JavaScript
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;
|
|
};
|