/js/lib/Socket.IO-node/lib/socket.io/client.js

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs · JavaScript · 244 lines · 175 code · 35 blank · 34 comment · 38 complexity · 4775b440e0ddb0e3bf56f376ba8e825b MD5 · raw file

  1. /*!
  2. * Socket.IO - Client
  3. * Copyright (c) 2010-2011 Guillermo Rauch <guillermo@learnboost.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var EventEmitter = require('events').EventEmitter
  10. , OutgoingMessage = require('http').OutgoingMessage
  11. , Stream = require('net').Stream
  12. , encode = require('./utils').encode
  13. , decode = require('./utils').decode
  14. , merge = require('./utils').merge
  15. , url = require('url');
  16. /**
  17. * Expose `Client`.
  18. */
  19. module.exports = Client;
  20. /**
  21. * Initialize `Client`.
  22. *
  23. * @api private
  24. */
  25. function Client(listener, req, res, options, head) {
  26. this.listener = listener;
  27. var defaults = {
  28. timeout: 8000
  29. , heartbeatInterval: 10000
  30. , closeTimeout: 0
  31. };
  32. options = merge(merge(defaults, this.options || {}), options);
  33. merge(this, options);
  34. this.connections = 0;
  35. this._open = false;
  36. this._heartbeats = 0;
  37. this.connected = false;
  38. this.upgradeHead = head;
  39. this._onConnect(req, res);
  40. };
  41. /**
  42. * Inherit from `EventEmitter.prototype`.
  43. */
  44. Client.prototype.__proto__ = EventEmitter.prototype;
  45. /**
  46. * Send the given `message` which is automatically
  47. * converted to JSON unless a string is given.
  48. *
  49. * @param {Object|String} message
  50. * @return {Client} for chaining
  51. * @api public
  52. */
  53. Client.prototype.send = function(message){
  54. var state = this.connection.readyState;
  55. if (this._open && ('open' == state || 'writeOnly' == state)) {
  56. this._write(encode(message));
  57. } else {
  58. this._queue(message);
  59. }
  60. return this;
  61. };
  62. /**
  63. * Broadcast `message` to all the _other_ clients.
  64. *
  65. * @param {Object|String} message
  66. * @return {Client} for chaining
  67. * @api public
  68. */
  69. Client.prototype.broadcast = function(message){
  70. if (!('sessionId' in this)) return this;
  71. this.listener.broadcast(message, this.sessionId);
  72. return this;
  73. };
  74. Client.prototype._onMessage = function(data){
  75. var messages = decode(data);
  76. if (messages === false) return this.listener.log('Bad message received from client ' + this.sessionId);
  77. for (var i = 0, l = messages.length, frame; i < l; i++){
  78. frame = messages[i].substr(0, 3);
  79. switch (frame){
  80. case '~h~':
  81. return this._onHeartbeat(messages[i].substr(3));
  82. case '~j~':
  83. try {
  84. messages[i] = JSON.parse(messages[i].substr(3));
  85. } catch(e) {
  86. messages[i] = {};
  87. }
  88. break;
  89. }
  90. this.emit('message', messages[i]);
  91. this.listener._onClientMessage(messages[i], this);
  92. }
  93. };
  94. Client.prototype._onConnect = function(req, res){
  95. var self = this
  96. , attachConnection = !this.connection;
  97. this.request = req;
  98. this.response = res;
  99. this.connection = req.connection;
  100. if(!attachConnection) attachConnection = !attachConnection && this.connection.eventsAttached === undefined;
  101. this.connection.eventsAttached = true;
  102. if (attachConnection){
  103. function destroyConnection(){
  104. self._onClose();
  105. self.connection && self.connection.destroy()
  106. };
  107. this.connection.addListener('end', destroyConnection);
  108. this.connection.addListener('timeout', destroyConnection);
  109. this.connection.addListener('error', destroyConnection);
  110. }
  111. if (req){
  112. function destroyRequest(){
  113. req.destroy && req.destroy();
  114. };
  115. req.addListener('error', destroyRequest);
  116. req.addListener('timeout', destroyRequest);
  117. if (res){
  118. function destroyResponse(){
  119. res.destroy && res.destroy();
  120. }
  121. res.addListener('error', destroyResponse);
  122. res.addListener('timeout', destroyResponse);
  123. }
  124. if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
  125. }
  126. };
  127. Client.prototype._payload = function(){
  128. var payload = [];
  129. this.connections++;
  130. this.connected = true;
  131. this._open = true;
  132. if (!this.handshaked){
  133. this._generateSessionId();
  134. payload.push(this.sessionId);
  135. this.handshaked = true;
  136. }
  137. payload = payload.concat(this._writeQueue || []);
  138. this._writeQueue = [];
  139. if (payload.length) this._write(encode(payload));
  140. if (this.connections === 1) this.listener._onClientConnect(this);
  141. if (this.timeout) this._heartbeat();
  142. };
  143. Client.prototype._heartbeat = function(){
  144. var self = this;
  145. this._heartbeatInterval = setTimeout(function(){
  146. self.send('~h~' + ++self._heartbeats);
  147. self._heartbeatTimeout = setTimeout(function(){
  148. self._onClose();
  149. }, self.timeout);
  150. }, self.heartbeatInterval);
  151. };
  152. Client.prototype._onHeartbeat = function(h){
  153. if (h == this._heartbeats){
  154. clearTimeout(this._heartbeatTimeout);
  155. this._heartbeat();
  156. }
  157. };
  158. Client.prototype._onClose = function(skipDisconnect){
  159. if (!this._open) return this;
  160. var self = this;
  161. if (this._heartbeatInterval) clearTimeout(this._heartbeatInterval);
  162. if (this._heartbeatTimeout) clearTimeout(this._heartbeatTimeout);
  163. this._open = false;
  164. this.request = null;
  165. this.response = null;
  166. if (skipDisconnect !== false){
  167. if (this.handshaked){
  168. this._disconnectTimeout = setTimeout(function(){
  169. self._onDisconnect();
  170. }, this.closeTimeout);
  171. } else
  172. this._onDisconnect();
  173. }
  174. };
  175. Client.prototype._onDisconnect = function(){
  176. if (this._open) this._onClose(true);
  177. if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
  178. this._writeQueue = [];
  179. this.connected = false;
  180. if (this.handshaked){
  181. this.emit('disconnect');
  182. this.listener._onClientDisconnect(this);
  183. this.handshaked = false;
  184. }
  185. };
  186. Client.prototype._queue = function(message){
  187. this._writeQueue = this._writeQueue || [];
  188. this._writeQueue.push(message);
  189. return this;
  190. };
  191. Client.prototype._generateSessionId = function(){
  192. this.sessionId = Math.random().toString().substr(2);
  193. return this;
  194. };
  195. Client.prototype._verifyOrigin = function(origin){
  196. var origins = this.listener.origins;
  197. if (origins.indexOf('*:*') !== -1) {
  198. return true;
  199. }
  200. if (origin) {
  201. try {
  202. var parts = url.parse(origin);
  203. return origins.indexOf(parts.host + ':' + parts.port) !== -1 ||
  204. origins.indexOf(parts.host + ':*') !== -1 ||
  205. origins.indexOf('*:' + parts.port) !== -1;
  206. } catch (ex) {}
  207. }
  208. return false;
  209. };