twitst4tz

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

client.js (5902B)


      1 
      2 /**
      3  * Module dependencies.
      4  */
      5 
      6 var parser = require('socket.io-parser');
      7 var debug = require('debug')('socket.io:client');
      8 var url = require('url');
      9 
     10 /**
     11  * Module exports.
     12  */
     13 
     14 module.exports = Client;
     15 
     16 /**
     17  * Client constructor.
     18  *
     19  * @param {Server} server instance
     20  * @param {Socket} conn
     21  * @api private
     22  */
     23 
     24 function Client(server, conn){
     25   this.server = server;
     26   this.conn = conn;
     27   this.encoder = server.encoder;
     28   this.decoder = new server.parser.Decoder();
     29   this.id = conn.id;
     30   this.request = conn.request;
     31   this.setup();
     32   this.sockets = {};
     33   this.nsps = {};
     34   this.connectBuffer = [];
     35 }
     36 
     37 /**
     38  * Sets up event listeners.
     39  *
     40  * @api private
     41  */
     42 
     43 Client.prototype.setup = function(){
     44   this.onclose = this.onclose.bind(this);
     45   this.ondata = this.ondata.bind(this);
     46   this.onerror = this.onerror.bind(this);
     47   this.ondecoded = this.ondecoded.bind(this);
     48 
     49   this.decoder.on('decoded', this.ondecoded);
     50   this.conn.on('data', this.ondata);
     51   this.conn.on('error', this.onerror);
     52   this.conn.on('close', this.onclose);
     53 };
     54 
     55 /**
     56  * Connects a client to a namespace.
     57  *
     58  * @param {String} name namespace
     59  * @param {Object} query the query parameters
     60  * @api private
     61  */
     62 
     63 Client.prototype.connect = function(name, query){
     64   if (this.server.nsps[name]) {
     65     debug('connecting to namespace %s', name);
     66     return this.doConnect(name, query);
     67   }
     68 
     69   this.server.checkNamespace(name, query, (dynamicNsp) => {
     70     if (dynamicNsp) {
     71       debug('dynamic namespace %s was created', dynamicNsp.name);
     72       this.doConnect(name, query);
     73     } else {
     74       debug('creation of namespace %s was denied', name);
     75       this.packet({ type: parser.ERROR, nsp: name, data: 'Invalid namespace' });
     76     }
     77   });
     78 };
     79 
     80 /**
     81  * Connects a client to a namespace.
     82  *
     83  * @param {String} name namespace
     84  * @param {String} query the query parameters
     85  * @api private
     86  */
     87 
     88 Client.prototype.doConnect = function(name, query){
     89   var nsp = this.server.of(name);
     90 
     91   if ('/' != name && !this.nsps['/']) {
     92     this.connectBuffer.push(name);
     93     return;
     94   }
     95 
     96   var self = this;
     97   var socket = nsp.add(this, query, function(){
     98     self.sockets[socket.id] = socket;
     99     self.nsps[nsp.name] = socket;
    100 
    101     if ('/' == nsp.name && self.connectBuffer.length > 0) {
    102       self.connectBuffer.forEach(self.connect, self);
    103       self.connectBuffer = [];
    104     }
    105   });
    106 };
    107 
    108 /**
    109  * Disconnects from all namespaces and closes transport.
    110  *
    111  * @api private
    112  */
    113 
    114 Client.prototype.disconnect = function(){
    115   for (var id in this.sockets) {
    116     if (this.sockets.hasOwnProperty(id)) {
    117       this.sockets[id].disconnect();
    118     }
    119   }
    120   this.sockets = {};
    121   this.close();
    122 };
    123 
    124 /**
    125  * Removes a socket. Called by each `Socket`.
    126  *
    127  * @api private
    128  */
    129 
    130 Client.prototype.remove = function(socket){
    131   if (this.sockets.hasOwnProperty(socket.id)) {
    132     var nsp = this.sockets[socket.id].nsp.name;
    133     delete this.sockets[socket.id];
    134     delete this.nsps[nsp];
    135   } else {
    136     debug('ignoring remove for %s', socket.id);
    137   }
    138 };
    139 
    140 /**
    141  * Closes the underlying connection.
    142  *
    143  * @api private
    144  */
    145 
    146 Client.prototype.close = function(){
    147   if ('open' == this.conn.readyState) {
    148     debug('forcing transport close');
    149     this.conn.close();
    150     this.onclose('forced server close');
    151   }
    152 };
    153 
    154 /**
    155  * Writes a packet to the transport.
    156  *
    157  * @param {Object} packet object
    158  * @param {Object} opts
    159  * @api private
    160  */
    161 
    162 Client.prototype.packet = function(packet, opts){
    163   opts = opts || {};
    164   var self = this;
    165 
    166   // this writes to the actual connection
    167   function writeToEngine(encodedPackets) {
    168     if (opts.volatile && !self.conn.transport.writable) return;
    169     for (var i = 0; i < encodedPackets.length; i++) {
    170       self.conn.write(encodedPackets[i], { compress: opts.compress });
    171     }
    172   }
    173 
    174   if ('open' == this.conn.readyState) {
    175     debug('writing packet %j', packet);
    176     if (!opts.preEncoded) { // not broadcasting, need to encode
    177       this.encoder.encode(packet, writeToEngine); // encode, then write results to engine
    178     } else { // a broadcast pre-encodes a packet
    179       writeToEngine(packet);
    180     }
    181   } else {
    182     debug('ignoring packet write %j', packet);
    183   }
    184 };
    185 
    186 /**
    187  * Called with incoming transport data.
    188  *
    189  * @api private
    190  */
    191 
    192 Client.prototype.ondata = function(data){
    193   // try/catch is needed for protocol violations (GH-1880)
    194   try {
    195     this.decoder.add(data);
    196   } catch(e) {
    197     this.onerror(e);
    198   }
    199 };
    200 
    201 /**
    202  * Called when parser fully decodes a packet.
    203  *
    204  * @api private
    205  */
    206 
    207 Client.prototype.ondecoded = function(packet) {
    208   if (parser.CONNECT == packet.type) {
    209     this.connect(url.parse(packet.nsp).pathname, url.parse(packet.nsp, true).query);
    210   } else {
    211     var socket = this.nsps[packet.nsp];
    212     if (socket) {
    213       process.nextTick(function() {
    214         socket.onpacket(packet);
    215       });
    216     } else {
    217       debug('no socket for namespace %s', packet.nsp);
    218     }
    219   }
    220 };
    221 
    222 /**
    223  * Handles an error.
    224  *
    225  * @param {Object} err object
    226  * @api private
    227  */
    228 
    229 Client.prototype.onerror = function(err){
    230   for (var id in this.sockets) {
    231     if (this.sockets.hasOwnProperty(id)) {
    232       this.sockets[id].onerror(err);
    233     }
    234   }
    235   this.conn.close();
    236 };
    237 
    238 /**
    239  * Called upon transport close.
    240  *
    241  * @param {String} reason
    242  * @api private
    243  */
    244 
    245 Client.prototype.onclose = function(reason){
    246   debug('client close with reason %s', reason);
    247 
    248   // ignore a potential subsequent `close` event
    249   this.destroy();
    250 
    251   // `nsps` and `sockets` are cleaned up seamlessly
    252   for (var id in this.sockets) {
    253     if (this.sockets.hasOwnProperty(id)) {
    254       this.sockets[id].onclose(reason);
    255     }
    256   }
    257   this.sockets = {};
    258 
    259   this.decoder.destroy(); // clean up decoder
    260 };
    261 
    262 /**
    263  * Cleans up event listeners.
    264  *
    265  * @api private
    266  */
    267 
    268 Client.prototype.destroy = function(){
    269   this.conn.removeListener('data', this.ondata);
    270   this.conn.removeListener('error', this.onerror);
    271   this.conn.removeListener('close', this.onclose);
    272   this.decoder.removeListener('decoded', this.ondecoded);
    273 };