client.js 5.2 KB

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