client.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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} conn
  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.onerror = this.onerror.bind(this);
  38. this.ondecoded = this.ondecoded.bind(this);
  39. this.decoder.on('decoded', this.ondecoded);
  40. this.conn.on('data', this.ondata);
  41. this.conn.on('error', this.onerror);
  42. this.conn.on('close', this.onclose);
  43. };
  44. /**
  45. * Connects a client to a namespace.
  46. *
  47. * @param {String} name namespace
  48. * @api private
  49. */
  50. Client.prototype.connect = function(name){
  51. debug('connecting to namespace %s', name);
  52. var nsp = this.server.nsps[name];
  53. if (!nsp) {
  54. this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
  55. return;
  56. }
  57. if ('/' != name && !this.nsps['/']) {
  58. this.connectBuffer.push(name);
  59. return;
  60. }
  61. var self = this;
  62. var socket = nsp.add(this, function(){
  63. self.sockets[socket.id] = socket;
  64. self.nsps[nsp.name] = socket;
  65. if ('/' == nsp.name && self.connectBuffer.length > 0) {
  66. self.connectBuffer.forEach(self.connect, self);
  67. self.connectBuffer = [];
  68. }
  69. });
  70. };
  71. /**
  72. * Disconnects from all namespaces and closes transport.
  73. *
  74. * @api private
  75. */
  76. Client.prototype.disconnect = function(){
  77. for (var id in this.sockets) {
  78. if (this.sockets.hasOwnProperty(id)) {
  79. this.sockets[id].disconnect();
  80. }
  81. }
  82. this.sockets = {};
  83. this.close();
  84. };
  85. /**
  86. * Removes a socket. Called by each `Socket`.
  87. *
  88. * @api private
  89. */
  90. Client.prototype.remove = function(socket){
  91. if (this.sockets.hasOwnProperty(socket.id)) {
  92. var nsp = this.sockets[socket.id].nsp.name;
  93. delete this.sockets[socket.id];
  94. delete this.nsps[nsp];
  95. } else {
  96. debug('ignoring remove for %s', socket.id);
  97. }
  98. };
  99. /**
  100. * Closes the underlying connection.
  101. *
  102. * @api private
  103. */
  104. Client.prototype.close = function(){
  105. if ('open' == this.conn.readyState) {
  106. debug('forcing transport close');
  107. this.conn.close();
  108. this.onclose('forced server close');
  109. }
  110. };
  111. /**
  112. * Writes a packet to the transport.
  113. *
  114. * @param {Object} packet object
  115. * @param {Object} opts
  116. * @api private
  117. */
  118. Client.prototype.packet = function(packet, opts){
  119. opts = opts || {};
  120. var self = this;
  121. // this writes to the actual connection
  122. function writeToEngine(encodedPackets) {
  123. if (opts.volatile && !self.conn.transport.writable) return;
  124. for (var i = 0; i < encodedPackets.length; i++) {
  125. self.conn.write(encodedPackets[i], { compress: opts.compress });
  126. }
  127. }
  128. if ('open' == this.conn.readyState) {
  129. debug('writing packet %j', packet);
  130. if (!opts.preEncoded) { // not broadcasting, need to encode
  131. this.encoder.encode(packet, function (encodedPackets) { // encode, then write results to engine
  132. writeToEngine(encodedPackets);
  133. });
  134. } else { // a broadcast pre-encodes a packet
  135. writeToEngine(packet);
  136. }
  137. } else {
  138. debug('ignoring packet write %j', packet);
  139. }
  140. };
  141. /**
  142. * Called with incoming transport data.
  143. *
  144. * @api private
  145. */
  146. Client.prototype.ondata = function(data){
  147. // try/catch is needed for protocol violations (GH-1880)
  148. try {
  149. this.decoder.add(data);
  150. } catch(e) {
  151. this.onerror(e);
  152. }
  153. };
  154. /**
  155. * Called when parser fully decodes a packet.
  156. *
  157. * @api private
  158. */
  159. Client.prototype.ondecoded = function(packet) {
  160. if (parser.CONNECT == packet.type) {
  161. this.connect(packet.nsp);
  162. } else {
  163. var socket = this.nsps[packet.nsp];
  164. if (socket) {
  165. socket.onpacket(packet);
  166. } else {
  167. debug('no socket for namespace %s', packet.nsp);
  168. }
  169. }
  170. };
  171. /**
  172. * Handles an error.
  173. *
  174. * @param {Object} err object
  175. * @api private
  176. */
  177. Client.prototype.onerror = function(err){
  178. for (var id in this.sockets) {
  179. if (this.sockets.hasOwnProperty(id)) {
  180. this.sockets[id].onerror(err);
  181. }
  182. }
  183. this.onclose('client error');
  184. };
  185. /**
  186. * Called upon transport close.
  187. *
  188. * @param {String} reason
  189. * @api private
  190. */
  191. Client.prototype.onclose = function(reason){
  192. debug('client close with reason %s', reason);
  193. // ignore a potential subsequent `close` event
  194. this.destroy();
  195. // `nsps` and `sockets` are cleaned up seamlessly
  196. for (var id in this.sockets) {
  197. if (this.sockets.hasOwnProperty(id)) {
  198. this.sockets[id].onclose(reason);
  199. }
  200. }
  201. this.sockets = {};
  202. this.decoder.destroy(); // clean up decoder
  203. };
  204. /**
  205. * Cleans up event listeners.
  206. *
  207. * @api private
  208. */
  209. Client.prototype.destroy = function(){
  210. this.conn.removeListener('data', this.ondata);
  211. this.conn.removeListener('error', this.onerror);
  212. this.conn.removeListener('close', this.onclose);
  213. this.decoder.removeListener('decoded', this.ondecoded);
  214. };