mirror of
https://github.com/socketio/socket.io.git
synced 2026-04-30 03:00:39 -04:00
Added changes to reflect socket.io-parser's async encoding, and use of has-binarydata to check the event type of an event. Next added browser tests for sending and receiving of binary data via arraybuffers. Then added blob tests and blob recognition. To make blobs fully work (and Files as well), had to add packet buffering to client so that slow-encoding blobs are still sent before other events. I fixed a stupid bug I had added where I used the indexof module (for old browsers) on a string somewhere instead of an array). This was causing old IE to receive all events twice. Old iphone tests were still failing so I updated tests to reflect that some browsers can receive a blob but not construct them. Finally, reduced build size by adding the "browser" field to package.json and making browserify less confused.
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-binarydata');
|
|
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;
|
|
};
|