|
|
/** * Module dependencies. */
var parser = require('socket.io-parser'); var Emitter = require('component-emitter'); var toArray = require('to-array'); var on = require('./on'); var bind = require('component-bind'); var debug = require('debug')('socket.io-client:socket'); var hasBin = require('has-binary');
/** * Module exports. */
module.exports = exports = Socket;
/** * Internal events (blacklisted). * These events can't be emitted by the user. * * @api private */
var events = { connect: 1, connect_error: 1, connect_timeout: 1, connecting: 1, disconnect: 1, error: 1, reconnect: 1, reconnect_attempt: 1, reconnect_failed: 1, reconnect_error: 1, reconnecting: 1, ping: 1, pong: 1 };
/** * Shortcut to `Emitter#emit`. */
var emit = Emitter.prototype.emit;
/** * `Socket` constructor. * * @api public */
function Socket (io, nsp, opts) { this.io = io; this.nsp = nsp; this.json = this; // compat
this.ids = 0; this.acks = {}; this.receiveBuffer = []; this.sendBuffer = []; this.connected = false; this.disconnected = true; if (opts && opts.query) { this.query = opts.query; } if (this.io.autoConnect) this.open(); }
/** * Mix in `Emitter`. */
Emitter(Socket.prototype);
/** * Subscribe to open, close and packet events * * @api private */
Socket.prototype.subEvents = function () { if (this.subs) return;
var io = this.io; this.subs = [ on(io, 'open', bind(this, 'onopen')), on(io, 'packet', bind(this, 'onpacket')), on(io, 'close', bind(this, 'onclose')) ]; };
/** * "Opens" the socket. * * @api public */
Socket.prototype.open = Socket.prototype.connect = function () { if (this.connected) return this;
this.subEvents(); this.io.open(); // ensure open
if ('open' === this.io.readyState) this.onopen(); this.emit('connecting'); return this; };
/** * 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 };
packet.options = {}; packet.options.compress = !this.flags || false !== this.flags.compress;
// 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++; }
if (this.connected) { this.packet(packet); } else { this.sendBuffer.push(packet); }
delete this.flags;
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 engine `open`. * * @api private */
Socket.prototype.onopen = function () { debug('transport is open - connecting');
// write connect packet if necessary
if ('/' !== this.nsp) { if (this.query) { this.packet({type: parser.CONNECT, query: this.query}); } else { this.packet({type: parser.CONNECT}); } } };
/** * Called upon engine `close`. * * @param {String} reason * @api private */
Socket.prototype.onclose = function (reason) { debug('close (%s)', reason); this.connected = false; this.disconnected = true; delete this.id; 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.BINARY_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.receiveBuffer.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; sent = true; var args = toArray(arguments); debug('sending ack %j', args);
var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK; self.packet({ type: type, id: id, data: args }); }; };
/** * Called upon a server acknowlegement. * * @param {Object} packet * @api private */
Socket.prototype.onack = function (packet) { var ack = this.acks[packet.id]; if ('function' === typeof ack) { debug('calling ack %s with %j', packet.id, packet.data); ack.apply(this, packet.data); delete this.acks[packet.id]; } else { debug('bad ack %s', 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 (received and emitted). * * @api private */
Socket.prototype.emitBuffered = function () { var i; for (i = 0; i < this.receiveBuffer.length; i++) { emit.apply(this, this.receiveBuffer[i]); } this.receiveBuffer = [];
for (i = 0; i < this.sendBuffer.length; i++) { this.packet(this.sendBuffer[i]); } this.sendBuffer = []; };
/** * Called upon server disconnect. * * @api private */
Socket.prototype.ondisconnect = function () { debug('server disconnect (%s)', this.nsp); this.destroy(); this.onclose('io server disconnect'); };
/** * Called upon forced client/server side disconnections, * this method ensures the manager stops tracking us and * that reconnections don't get triggered for this. * * @api private. */
Socket.prototype.destroy = function () { if (this.subs) { // clean subscriptions to avoid reconnections
for (var i = 0; i < this.subs.length; i++) { this.subs[i].destroy(); } this.subs = null; }
this.io.destroy(this); };
/** * Disconnects the socket manually. * * @return {Socket} self * @api public */
Socket.prototype.close = Socket.prototype.disconnect = function () { if (this.connected) { debug('performing disconnect (%s)', this.nsp); this.packet({ type: parser.DISCONNECT }); }
// remove socket from pool
this.destroy();
if (this.connected) { // fire events
this.onclose('io client disconnect'); } return this; };
/** * Sets the compress flag. * * @param {Boolean} if `true`, compresses the sending data * @return {Socket} self * @api public */
Socket.prototype.compress = function (compress) { this.flags = this.flags || {}; this.flags.compress = compress; return this; };
|