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.

273 lines
5.8 KiB

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