twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

socket.js (8266B)


      1 
      2 /**
      3  * Module dependencies.
      4  */
      5 
      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');
     14 
     15 /**
     16  * Module exports.
     17  */
     18 
     19 module.exports = exports = Socket;
     20 
     21 /**
     22  * Internal events (blacklisted).
     23  * These events can't be emitted by the user.
     24  *
     25  * @api private
     26  */
     27 
     28 var events = {
     29   connect: 1,
     30   connect_error: 1,
     31   connect_timeout: 1,
     32   connecting: 1,
     33   disconnect: 1,
     34   error: 1,
     35   reconnect: 1,
     36   reconnect_attempt: 1,
     37   reconnect_failed: 1,
     38   reconnect_error: 1,
     39   reconnecting: 1,
     40   ping: 1,
     41   pong: 1
     42 };
     43 
     44 /**
     45  * Shortcut to `Emitter#emit`.
     46  */
     47 
     48 var emit = Emitter.prototype.emit;
     49 
     50 /**
     51  * `Socket` constructor.
     52  *
     53  * @api public
     54  */
     55 
     56 function Socket (io, nsp, opts) {
     57   this.io = io;
     58   this.nsp = nsp;
     59   this.json = this; // compat
     60   this.ids = 0;
     61   this.acks = {};
     62   this.receiveBuffer = [];
     63   this.sendBuffer = [];
     64   this.connected = false;
     65   this.disconnected = true;
     66   this.flags = {};
     67   if (opts && opts.query) {
     68     this.query = opts.query;
     69   }
     70   if (this.io.autoConnect) this.open();
     71 }
     72 
     73 /**
     74  * Mix in `Emitter`.
     75  */
     76 
     77 Emitter(Socket.prototype);
     78 
     79 /**
     80  * Subscribe to open, close and packet events
     81  *
     82  * @api private
     83  */
     84 
     85 Socket.prototype.subEvents = function () {
     86   if (this.subs) return;
     87 
     88   var io = this.io;
     89   this.subs = [
     90     on(io, 'open', bind(this, 'onopen')),
     91     on(io, 'packet', bind(this, 'onpacket')),
     92     on(io, 'close', bind(this, 'onclose'))
     93   ];
     94 };
     95 
     96 /**
     97  * "Opens" the socket.
     98  *
     99  * @api public
    100  */
    101 
    102 Socket.prototype.open =
    103 Socket.prototype.connect = function () {
    104   if (this.connected) return this;
    105 
    106   this.subEvents();
    107   this.io.open(); // ensure open
    108   if ('open' === this.io.readyState) this.onopen();
    109   this.emit('connecting');
    110   return this;
    111 };
    112 
    113 /**
    114  * Sends a `message` event.
    115  *
    116  * @return {Socket} self
    117  * @api public
    118  */
    119 
    120 Socket.prototype.send = function () {
    121   var args = toArray(arguments);
    122   args.unshift('message');
    123   this.emit.apply(this, args);
    124   return this;
    125 };
    126 
    127 /**
    128  * Override `emit`.
    129  * If the event is in `events`, it's emitted normally.
    130  *
    131  * @param {String} event name
    132  * @return {Socket} self
    133  * @api public
    134  */
    135 
    136 Socket.prototype.emit = function (ev) {
    137   if (events.hasOwnProperty(ev)) {
    138     emit.apply(this, arguments);
    139     return this;
    140   }
    141 
    142   var args = toArray(arguments);
    143   var packet = {
    144     type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
    145     data: args
    146   };
    147 
    148   packet.options = {};
    149   packet.options.compress = !this.flags || false !== this.flags.compress;
    150 
    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++;
    156   }
    157 
    158   if (this.connected) {
    159     this.packet(packet);
    160   } else {
    161     this.sendBuffer.push(packet);
    162   }
    163 
    164   this.flags = {};
    165 
    166   return this;
    167 };
    168 
    169 /**
    170  * Sends a packet.
    171  *
    172  * @param {Object} packet
    173  * @api private
    174  */
    175 
    176 Socket.prototype.packet = function (packet) {
    177   packet.nsp = this.nsp;
    178   this.io.packet(packet);
    179 };
    180 
    181 /**
    182  * Called upon engine `open`.
    183  *
    184  * @api private
    185  */
    186 
    187 Socket.prototype.onopen = function () {
    188   debug('transport is open - connecting');
    189 
    190   // write connect packet if necessary
    191   if ('/' !== this.nsp) {
    192     if (this.query) {
    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});
    196     } else {
    197       this.packet({type: parser.CONNECT});
    198     }
    199   }
    200 };
    201 
    202 /**
    203  * Called upon engine `close`.
    204  *
    205  * @param {String} reason
    206  * @api private
    207  */
    208 
    209 Socket.prototype.onclose = function (reason) {
    210   debug('close (%s)', reason);
    211   this.connected = false;
    212   this.disconnected = true;
    213   delete this.id;
    214   this.emit('disconnect', reason);
    215 };
    216 
    217 /**
    218  * Called with socket packet.
    219  *
    220  * @param {Object} packet
    221  * @api private
    222  */
    223 
    224 Socket.prototype.onpacket = function (packet) {
    225   var sameNamespace = packet.nsp === this.nsp;
    226   var rootNamespaceError = packet.type === parser.ERROR && packet.nsp === '/';
    227 
    228   if (!sameNamespace && !rootNamespaceError) return;
    229 
    230   switch (packet.type) {
    231     case parser.CONNECT:
    232       this.onconnect();
    233       break;
    234 
    235     case parser.EVENT:
    236       this.onevent(packet);
    237       break;
    238 
    239     case parser.BINARY_EVENT:
    240       this.onevent(packet);
    241       break;
    242 
    243     case parser.ACK:
    244       this.onack(packet);
    245       break;
    246 
    247     case parser.BINARY_ACK:
    248       this.onack(packet);
    249       break;
    250 
    251     case parser.DISCONNECT:
    252       this.ondisconnect();
    253       break;
    254 
    255     case parser.ERROR:
    256       this.emit('error', packet.data);
    257       break;
    258   }
    259 };
    260 
    261 /**
    262  * Called upon a server event.
    263  *
    264  * @param {Object} packet
    265  * @api private
    266  */
    267 
    268 Socket.prototype.onevent = function (packet) {
    269   var args = packet.data || [];
    270   debug('emitting event %j', args);
    271 
    272   if (null != packet.id) {
    273     debug('attaching ack callback to event');
    274     args.push(this.ack(packet.id));
    275   }
    276 
    277   if (this.connected) {
    278     emit.apply(this, args);
    279   } else {
    280     this.receiveBuffer.push(args);
    281   }
    282 };
    283 
    284 /**
    285  * Produces an ack callback to emit with an event.
    286  *
    287  * @api private
    288  */
    289 
    290 Socket.prototype.ack = function (id) {
    291   var self = this;
    292   var sent = false;
    293   return function () {
    294     // prevent double callbacks
    295     if (sent) return;
    296     sent = true;
    297     var args = toArray(arguments);
    298     debug('sending ack %j', args);
    299 
    300     self.packet({
    301       type: hasBin(args) ? parser.BINARY_ACK : parser.ACK,
    302       id: id,
    303       data: args
    304     });
    305   };
    306 };
    307 
    308 /**
    309  * Called upon a server acknowlegement.
    310  *
    311  * @param {Object} packet
    312  * @api private
    313  */
    314 
    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];
    321   } else {
    322     debug('bad ack %s', packet.id);
    323   }
    324 };
    325 
    326 /**
    327  * Called upon server connect.
    328  *
    329  * @api private
    330  */
    331 
    332 Socket.prototype.onconnect = function () {
    333   this.connected = true;
    334   this.disconnected = false;
    335   this.emit('connect');
    336   this.emitBuffered();
    337 };
    338 
    339 /**
    340  * Emit buffered events (received and emitted).
    341  *
    342  * @api private
    343  */
    344 
    345 Socket.prototype.emitBuffered = function () {
    346   var i;
    347   for (i = 0; i < this.receiveBuffer.length; i++) {
    348     emit.apply(this, this.receiveBuffer[i]);
    349   }
    350   this.receiveBuffer = [];
    351 
    352   for (i = 0; i < this.sendBuffer.length; i++) {
    353     this.packet(this.sendBuffer[i]);
    354   }
    355   this.sendBuffer = [];
    356 };
    357 
    358 /**
    359  * Called upon server disconnect.
    360  *
    361  * @api private
    362  */
    363 
    364 Socket.prototype.ondisconnect = function () {
    365   debug('server disconnect (%s)', this.nsp);
    366   this.destroy();
    367   this.onclose('io server disconnect');
    368 };
    369 
    370 /**
    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.
    374  *
    375  * @api private.
    376  */
    377 
    378 Socket.prototype.destroy = function () {
    379   if (this.subs) {
    380     // clean subscriptions to avoid reconnections
    381     for (var i = 0; i < this.subs.length; i++) {
    382       this.subs[i].destroy();
    383     }
    384     this.subs = null;
    385   }
    386 
    387   this.io.destroy(this);
    388 };
    389 
    390 /**
    391  * Disconnects the socket manually.
    392  *
    393  * @return {Socket} self
    394  * @api public
    395  */
    396 
    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 });
    402   }
    403 
    404   // remove socket from pool
    405   this.destroy();
    406 
    407   if (this.connected) {
    408     // fire events
    409     this.onclose('io client disconnect');
    410   }
    411   return this;
    412 };
    413 
    414 /**
    415  * Sets the compress flag.
    416  *
    417  * @param {Boolean} if `true`, compresses the sending data
    418  * @return {Socket} self
    419  * @api public
    420  */
    421 
    422 Socket.prototype.compress = function (compress) {
    423   this.flags.compress = compress;
    424   return this;
    425 };
    426 
    427 /**
    428  * Sets the binary flag
    429  *
    430  * @param {Boolean} whether the emitted data contains binary
    431  * @return {Socket} self
    432  * @api public
    433  */
    434 
    435 Socket.prototype.binary = function (binary) {
    436   this.flags.binary = binary;
    437   return this;
    438 };