6 var parser = require('socket.io-parser');
7 var Emitter = require('component-emitter');
8 var toArray = require('to-array');
9 var on = require('./on');
10 var bind = require('component-bind');
11 var debug = require('debug')('socket.io-client:socket');
12 var parseqs = require('parseqs');
13 var hasBin = require('has-binary2');
19 module.exports = exports = Socket;
22 * Internal events (blacklisted).
23 * These events can't be emitted by the user.
45 * Shortcut to `Emitter#emit`.
48 var emit = Emitter.prototype.emit;
51 * `Socket` constructor.
56 function Socket (io, nsp, opts) {
59 this.json = this; // compat
62 this.receiveBuffer = [];
64 this.connected = false;
65 this.disconnected = true;
67 if (opts && opts.query) {
68 this.query = opts.query;
70 if (this.io.autoConnect) this.open();
77 Emitter(Socket.prototype);
80 * Subscribe to open, close and packet events
85 Socket.prototype.subEvents = function () {
86 if (this.subs) return;
90 on(io, 'open', bind(this, 'onopen')),
91 on(io, 'packet', bind(this, 'onpacket')),
92 on(io, 'close', bind(this, 'onclose'))
102 Socket.prototype.open =
103 Socket.prototype.connect = function () {
104 if (this.connected) return this;
107 if (!this.io.reconnecting) this.io.open(); // ensure open
108 if ('open' === this.io.readyState) this.onopen();
109 this.emit('connecting');
114 * Sends a `message` event.
116 * @return {Socket} self
120 Socket.prototype.send = function () {
121 var args = toArray(arguments);
122 args.unshift('message');
123 this.emit.apply(this, args);
129 * If the event is in `events`, it's emitted normally.
131 * @param {String} event name
132 * @return {Socket} self
136 Socket.prototype.emit = function (ev) {
137 if (events.hasOwnProperty(ev)) {
138 emit.apply(this, arguments);
142 var args = toArray(arguments);
144 type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
149 packet.options.compress = !this.flags || false !== this.flags.compress;
151 // event ack callback
152 if ('function' === typeof args[args.length - 1]) {
153 debug('emitting packet with ack id %d', this.ids);
154 this.acks[this.ids] = args.pop();
155 packet.id = this.ids++;
158 if (this.connected) {
161 this.sendBuffer.push(packet);
172 * @param {Object} packet
176 Socket.prototype.packet = function (packet) {
177 packet.nsp = this.nsp;
178 this.io.packet(packet);
182 * Called upon engine `open`.
187 Socket.prototype.onopen = function () {
188 debug('transport is open - connecting');
190 // write connect packet if necessary
191 if ('/' !== this.nsp) {
193 var query = typeof this.query === 'object' ? parseqs.encode(this.query) : this.query;
194 debug('sending connect packet with query %s', query);
195 this.packet({type: parser.CONNECT, query: query});
197 this.packet({type: parser.CONNECT});
203 * Called upon engine `close`.
205 * @param {String} reason
209 Socket.prototype.onclose = function (reason) {
210 debug('close (%s)', reason);
211 this.connected = false;
212 this.disconnected = true;
214 this.emit('disconnect', reason);
218 * Called with socket packet.
220 * @param {Object} packet
224 Socket.prototype.onpacket = function (packet) {
225 var sameNamespace = packet.nsp === this.nsp;
226 var rootNamespaceError = packet.type === parser.ERROR && packet.nsp === '/';
228 if (!sameNamespace && !rootNamespaceError) return;
230 switch (packet.type) {
236 this.onevent(packet);
239 case parser.BINARY_EVENT:
240 this.onevent(packet);
247 case parser.BINARY_ACK:
251 case parser.DISCONNECT:
256 this.emit('error', packet.data);
262 * Called upon a server event.
264 * @param {Object} packet
268 Socket.prototype.onevent = function (packet) {
269 var args = packet.data || [];
270 debug('emitting event %j', args);
272 if (null != packet.id) {
273 debug('attaching ack callback to event');
274 args.push(this.ack(packet.id));
277 if (this.connected) {
278 emit.apply(this, args);
280 this.receiveBuffer.push(args);
285 * Produces an ack callback to emit with an event.
290 Socket.prototype.ack = function (id) {
294 // prevent double callbacks
297 var args = toArray(arguments);
298 debug('sending ack %j', args);
301 type: hasBin(args) ? parser.BINARY_ACK : parser.ACK,
309 * Called upon a server acknowlegement.
311 * @param {Object} packet
315 Socket.prototype.onack = function (packet) {
316 var ack = this.acks[packet.id];
317 if ('function' === typeof ack) {
318 debug('calling ack %s with %j', packet.id, packet.data);
319 ack.apply(this, packet.data);
320 delete this.acks[packet.id];
322 debug('bad ack %s', packet.id);
327 * Called upon server connect.
332 Socket.prototype.onconnect = function () {
333 this.connected = true;
334 this.disconnected = false;
335 this.emit('connect');
340 * Emit buffered events (received and emitted).
345 Socket.prototype.emitBuffered = function () {
347 for (i = 0; i < this.receiveBuffer.length; i++) {
348 emit.apply(this, this.receiveBuffer[i]);
350 this.receiveBuffer = [];
352 for (i = 0; i < this.sendBuffer.length; i++) {
353 this.packet(this.sendBuffer[i]);
355 this.sendBuffer = [];
359 * Called upon server disconnect.
364 Socket.prototype.ondisconnect = function () {
365 debug('server disconnect (%s)', this.nsp);
367 this.onclose('io server disconnect');
371 * Called upon forced client/server side disconnections,
372 * this method ensures the manager stops tracking us and
373 * that reconnections don't get triggered for this.
378 Socket.prototype.destroy = function () {
380 // clean subscriptions to avoid reconnections
381 for (var i = 0; i < this.subs.length; i++) {
382 this.subs[i].destroy();
387 this.io.destroy(this);
391 * Disconnects the socket manually.
393 * @return {Socket} self
397 Socket.prototype.close =
398 Socket.prototype.disconnect = function () {
399 if (this.connected) {
400 debug('performing disconnect (%s)', this.nsp);
401 this.packet({ type: parser.DISCONNECT });
404 // remove socket from pool
407 if (this.connected) {
409 this.onclose('io client disconnect');
415 * Sets the compress flag.
417 * @param {Boolean} if `true`, compresses the sending data
418 * @return {Socket} self
422 Socket.prototype.compress = function (compress) {
423 this.flags.compress = compress;
428 * Sets the binary flag
430 * @param {Boolean} whether the emitted data contains binary
431 * @return {Socket} self
435 Socket.prototype.binary = function (binary) {
436 this.flags.binary = binary;