6 var Emitter = require('events').EventEmitter;
7 var parser = require('socket.io-parser');
8 var hasBin = require('has-binary2');
9 var url = require('url');
10 var debug = require('debug')('socket.io:socket');
16 module.exports = exports = Socket;
47 * `EventEmitter#emit` reference.
50 var emit = Emitter.prototype.emit;
53 * Interface to a `Client` for a given `Namespace`.
55 * @param {Namespace} nsp
56 * @param {Client} client
60 function Socket(nsp, client, query){
62 this.server = nsp.server;
63 this.adapter = this.nsp.adapter;
64 this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id;
66 this.conn = client.conn;
69 this.connected = true;
70 this.disconnected = false;
71 this.handshake = this.buildHandshake(query);
78 * Inherits from `EventEmitter`.
81 Socket.prototype.__proto__ = Emitter.prototype;
84 * Apply flags from `Socket`.
87 flags.forEach(function(flag){
88 Object.defineProperty(Socket.prototype, flag, {
90 this.flags[flag] = true;
97 * `request` engine.io shortcut.
102 Object.defineProperty(Socket.prototype, 'request', {
104 return this.conn.request;
109 * Builds the `handshake` BC object
114 Socket.prototype.buildHandshake = function(query){
116 function buildQuery(){
117 var requestQuery = url.parse(self.request.url, true).query;
118 //if socket-specific query exist, replace query strings in requestQuery
119 return Object.assign({}, requestQuery, query);
122 headers: this.request.headers,
123 time: (new Date) + '',
124 address: this.conn.remoteAddress,
125 xdomain: !!this.request.headers.origin,
126 secure: !!this.request.connection.encrypted,
128 url: this.request.url,
134 * Emits to this client.
136 * @return {Socket} self
140 Socket.prototype.emit = function(ev){
141 if (~exports.events.indexOf(ev)) {
142 emit.apply(this, arguments);
146 var args = Array.prototype.slice.call(arguments);
148 type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
152 // access last argument to see if it's an ACK callback
153 if (typeof args[args.length - 1] === 'function') {
154 if (this._rooms.length || this.flags.broadcast) {
155 throw new Error('Callbacks are not supported when broadcasting');
158 debug('emitting packet with ack id %d', this.nsp.ids);
159 this.acks[this.nsp.ids] = args.pop();
160 packet.id = this.nsp.ids++;
163 var rooms = this._rooms.slice(0);
164 var flags = Object.assign({}, this.flags);
170 if (rooms.length || flags.broadcast) {
171 this.adapter.broadcast(packet, {
178 this.packet(packet, flags);
184 * Targets a room when broadcasting.
186 * @param {String} name
187 * @return {Socket} self
191 Socket.prototype.to =
192 Socket.prototype.in = function(name){
193 if (!~this._rooms.indexOf(name)) this._rooms.push(name);
198 * Sends a `message` event.
200 * @return {Socket} self
204 Socket.prototype.send =
205 Socket.prototype.write = function(){
206 var args = Array.prototype.slice.call(arguments);
207 args.unshift('message');
208 this.emit.apply(this, args);
215 * @param {Object} packet object
216 * @param {Object} opts options
220 Socket.prototype.packet = function(packet, opts){
221 packet.nsp = this.nsp.name;
223 opts.compress = false !== opts.compress;
224 this.client.packet(packet, opts);
230 * @param {String|Array} room or array of rooms
231 * @param {Function} fn optional, callback
232 * @return {Socket} self
236 Socket.prototype.join = function(rooms, fn){
237 debug('joining room %s', rooms);
239 if (!Array.isArray(rooms)) {
242 rooms = rooms.filter(function (room) {
243 return !self.rooms.hasOwnProperty(room);
249 this.adapter.addAll(this.id, rooms, function(err){
250 if (err) return fn && fn(err);
251 debug('joined room %s', rooms);
252 rooms.forEach(function (room) {
253 self.rooms[room] = room;
263 * @param {String} room
264 * @param {Function} fn optional, callback
265 * @return {Socket} self
269 Socket.prototype.leave = function(room, fn){
270 debug('leave room %s', room);
272 this.adapter.del(this.id, room, function(err){
273 if (err) return fn && fn(err);
274 debug('left room %s', room);
275 delete self.rooms[room];
287 Socket.prototype.leaveAll = function(){
288 this.adapter.delAll(this.id);
293 * Called by `Namespace` upon successful
294 * middleware execution (ie: authorization).
295 * Socket is added to namespace array before
296 * call to join, so adapters can access it.
301 Socket.prototype.onconnect = function(){
302 debug('socket connected - writing packet');
303 this.nsp.connected[this.id] = this;
305 var skip = this.nsp.name === '/' && this.nsp.fns.length === 0;
307 debug('packet already sent in initial handshake');
309 this.packet({ type: parser.CONNECT });
314 * Called with each packet. Called by `Client`.
316 * @param {Object} packet
320 Socket.prototype.onpacket = function(packet){
321 debug('got packet %j', packet);
322 switch (packet.type) {
324 this.onevent(packet);
327 case parser.BINARY_EVENT:
328 this.onevent(packet);
335 case parser.BINARY_ACK:
339 case parser.DISCONNECT:
344 this.onerror(new Error(packet.data));
349 * Called upon event packet.
351 * @param {Object} packet object
355 Socket.prototype.onevent = function(packet){
356 var args = packet.data || [];
357 debug('emitting event %j', args);
359 if (null != packet.id) {
360 debug('attaching ack callback to event');
361 args.push(this.ack(packet.id));
368 * Produces an ack callback to emit with an event.
370 * @param {Number} id packet id
374 Socket.prototype.ack = function(id){
378 // prevent double callbacks
380 var args = Array.prototype.slice.call(arguments);
381 debug('sending ack %j', args);
385 type: hasBin(args) ? parser.BINARY_ACK : parser.ACK,
394 * Called upon ack packet.
399 Socket.prototype.onack = function(packet){
400 var ack = this.acks[packet.id];
401 if ('function' == typeof ack) {
402 debug('calling ack %s with %j', packet.id, packet.data);
403 ack.apply(this, packet.data);
404 delete this.acks[packet.id];
406 debug('bad ack %s', packet.id);
411 * Called upon client disconnect packet.
416 Socket.prototype.ondisconnect = function(){
417 debug('got disconnect packet');
418 this.onclose('client namespace disconnect');
422 * Handles a client error.
427 Socket.prototype.onerror = function(err){
428 if (this.listeners('error').length) {
429 this.emit('error', err);
431 console.error('Missing error handler on `socket`.');
432 console.error(err.stack);
437 * Called upon closing. Called by `Client`.
439 * @param {String} reason
440 * @throw {Error} optional error object
444 Socket.prototype.onclose = function(reason){
445 if (!this.connected) return this;
446 debug('closing socket - reason %s', reason);
447 this.emit('disconnecting', reason);
449 this.nsp.remove(this);
450 this.client.remove(this);
451 this.connected = false;
452 this.disconnected = true;
453 delete this.nsp.connected[this.id];
454 this.emit('disconnect', reason);
458 * Produces an `error` packet.
460 * @param {Object} err error object
464 Socket.prototype.error = function(err){
465 this.packet({ type: parser.ERROR, data: err });
469 * Disconnects this client.
471 * @param {Boolean} close if `true`, closes the underlying connection
472 * @return {Socket} self
476 Socket.prototype.disconnect = function(close){
477 if (!this.connected) return this;
479 this.client.disconnect();
481 this.packet({ type: parser.DISCONNECT });
482 this.onclose('server namespace disconnect');
488 * Sets the compress flag.
490 * @param {Boolean} compress if `true`, compresses the sending data
491 * @return {Socket} self
495 Socket.prototype.compress = function(compress){
496 this.flags.compress = compress;
501 * Sets the binary flag
503 * @param {Boolean} Encode as if it has binary data if `true`, Encode as if it doesnt have binary data if `false`
504 * @return {Socket} self
508 Socket.prototype.binary = function (binary) {
509 this.flags.binary = binary;
514 * Dispatch incoming event to socket listeners.
516 * @param {Array} event that will get emitted
520 Socket.prototype.dispatch = function(event){
521 debug('dispatching an event %j', event);
523 function dispatchSocket(err) {
524 process.nextTick(function(){
526 return self.error(err.data || err.message);
528 emit.apply(self, event);
531 this.run(event, dispatchSocket);
535 * Sets up socket middleware.
537 * @param {Function} middleware function (event, next)
538 * @return {Socket} self
542 Socket.prototype.use = function(fn){
548 * Executes the middleware for an incoming event.
550 * @param {Array} event that will get emitted
551 * @param {Function} last fn call in the middleware
554 Socket.prototype.run = function(event, fn){
555 var fns = this.fns.slice(0);
556 if (!fns.length) return fn(null);
559 fns[i](event, function(err){
560 // upon error, short-circuit
561 if (err) return fn(err);
563 // if no middleware left, summon callback
564 if (!fns[i + 1]) return fn(null);