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