You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

224 lines
4.6 KiB

  1. /**
  2. * Module dependencies.
  3. */
  4. var parser = require('socket.io-parser');
  5. var debug = require('debug')('socket.io:client');
  6. /**
  7. * Module exports.
  8. */
  9. module.exports = Client;
  10. /**
  11. * Client constructor.
  12. *
  13. * @param {Server} server instance
  14. * @param {Socket} connection
  15. * @api private
  16. */
  17. function Client(server, conn){
  18. this.server = server;
  19. this.conn = conn;
  20. this.encoder = new parser.Encoder();
  21. this.decoder = new parser.Decoder();
  22. this.id = conn.id;
  23. this.request = conn.request;
  24. this.setup();
  25. this.sockets = [];
  26. this.nsps = {};
  27. this.connectBuffer = [];
  28. }
  29. /**
  30. * Sets up event listeners.
  31. *
  32. * @api private
  33. */
  34. Client.prototype.setup = function(){
  35. this.onclose = this.onclose.bind(this);
  36. this.ondata = this.ondata.bind(this);
  37. this.ondecoded = this.ondecoded.bind(this);
  38. this.decoder.on('decoded', this.ondecoded);
  39. this.conn.on('data', this.ondata);
  40. this.conn.on('close', this.onclose);
  41. };
  42. /**
  43. * Connects a client to a namespace.
  44. *
  45. * @param {String} namespace name
  46. * @api private
  47. */
  48. Client.prototype.connect = function(name){
  49. debug('connecting to namespace %s', name);
  50. if (!this.server.nsps[name]) {
  51. this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
  52. return;
  53. }
  54. var nsp = this.server.of(name);
  55. if ('/' != name && !this.nsps['/']) {
  56. this.connectBuffer.push(name);
  57. return;
  58. }
  59. var self = this;
  60. var socket = nsp.add(this, function(){
  61. self.sockets.push(socket);
  62. self.nsps[nsp.name] = socket;
  63. if ('/' == nsp.name && self.connectBuffer.length > 0) {
  64. self.connectBuffer.forEach(self.connect, self);
  65. self.connectBuffer = [];
  66. }
  67. });
  68. };
  69. /**
  70. * Disconnects from all namespaces and closes transport.
  71. *
  72. * @api private
  73. */
  74. Client.prototype.disconnect = function(){
  75. var socket;
  76. // we don't use a for loop because the length of
  77. // `sockets` changes upon each iteration
  78. while (socket = this.sockets.shift()) {
  79. socket.disconnect();
  80. }
  81. this.close();
  82. };
  83. /**
  84. * Removes a socket. Called by each `Socket`.
  85. *
  86. * @api private
  87. */
  88. Client.prototype.remove = function(socket){
  89. var i = this.sockets.indexOf(socket);
  90. if (~i) {
  91. var nsp = this.sockets[i].nsp.name;
  92. this.sockets.splice(i, 1);
  93. delete this.nsps[nsp];
  94. } else {
  95. debug('ignoring remove for %s', socket.id);
  96. }
  97. };
  98. /**
  99. * Closes the underlying connection.
  100. *
  101. * @api private
  102. */
  103. Client.prototype.close = function(){
  104. if ('open' == this.conn.readyState) {
  105. debug('forcing transport close');
  106. this.conn.close();
  107. this.onclose('forced server close');
  108. }
  109. };
  110. /**
  111. * Writes a packet to the transport.
  112. *
  113. * @param {Object} packet object
  114. * @param {Boolean} whether packet is already encoded
  115. * @param {Boolean} whether packet is volatile
  116. * @api private
  117. */
  118. Client.prototype.packet = function(packet, preEncoded, volatile){
  119. var self = this;
  120. // this writes to the actual connection
  121. function writeToEngine(encodedPackets) {
  122. if (volatile && !self.conn.transport.writable) return;
  123. for (var i = 0; i < encodedPackets.length; i++) {
  124. self.conn.write(encodedPackets[i]);
  125. }
  126. }
  127. if ('open' == this.conn.readyState) {
  128. debug('writing packet %j', packet);
  129. if(!preEncoded) { // not broadcasting, need to encode
  130. this.encoder.encode(packet, function (encodedPackets) { // encode, then write results to engine
  131. writeToEngine(encodedPackets);
  132. });
  133. } else { // a broadcast pre-encodes a packet
  134. writeToEngine(packet);
  135. }
  136. } else {
  137. debug('ignoring packet write %j', packet);
  138. }
  139. };
  140. /**
  141. * Called with incoming transport data.
  142. *
  143. * @api private
  144. */
  145. Client.prototype.ondata = function(data){
  146. this.decoder.add(data);
  147. };
  148. /**
  149. * Called when parser fully decodes a packet.
  150. *
  151. * @api private
  152. */
  153. Client.prototype.ondecoded = function(packet) {
  154. if (parser.CONNECT == packet.type) {
  155. this.connect(packet.nsp);
  156. } else {
  157. var socket = this.nsps[packet.nsp];
  158. if (socket) {
  159. socket.onpacket(packet);
  160. } else {
  161. debug('no socket for namespace %s', packet.nsp);
  162. }
  163. }
  164. };
  165. /**
  166. * Called upon transport close.
  167. *
  168. * @param {String} reason
  169. * @api private
  170. */
  171. Client.prototype.onclose = function(reason){
  172. debug('client close with reason %s', reason);
  173. // ignore a potential subsequent `close` event
  174. this.destroy();
  175. // `nsps` and `sockets` are cleaned up seamlessly
  176. var socket;
  177. while (socket = this.sockets.shift()) {
  178. socket.onclose(reason);
  179. }
  180. this.decoder.destroy(); // clean up decoder
  181. };
  182. /**
  183. * Cleans up event listeners.
  184. *
  185. * @api private
  186. */
  187. Client.prototype.destroy = function(){
  188. this.conn.removeListener('data', this.ondata);
  189. this.conn.removeListener('close', this.onclose);
  190. this.decoder.removeListener('decoded', this.ondecoded);
  191. };