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 };