PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/socket.js

https://github.com/eiriklv/socket.io
JavaScript | 422 lines | 215 code | 62 blank | 145 comment | 35 complexity | 32f267dc986b8d12d87fdd2a8cd4c60e MD5 | raw file
Possible License(s): MIT
  1. /**
  2. * Module dependencies.
  3. */
  4. var Emitter = require('events').EventEmitter;
  5. var parser = require('socket.io-parser');
  6. var url = require('url');
  7. var debug = require('debug')('socket.io:socket');
  8. var hasBin = require('has-binary-data');
  9. /**
  10. * Module exports.
  11. */
  12. module.exports = exports = Socket;
  13. /**
  14. * Blacklisted events.
  15. *
  16. * @api public
  17. */
  18. exports.events = [
  19. 'error',
  20. 'connect',
  21. 'disconnect',
  22. 'newListener'
  23. ];
  24. /**
  25. * Flags.
  26. *
  27. * @api private
  28. */
  29. var flags = [
  30. 'json',
  31. 'volatile',
  32. 'broadcast'
  33. ];
  34. /**
  35. * `EventEmitter#emit` reference.
  36. */
  37. var emit = Emitter.prototype.emit;
  38. /**
  39. * Interface to a `Client` for a given `Namespace`.
  40. *
  41. * @param {Namespace} nsp
  42. * @param {Client} client
  43. * @api public
  44. */
  45. function Socket(nsp, client){
  46. this.nsp = nsp;
  47. this.server = nsp.server;
  48. this.adapter = this.nsp.adapter;
  49. this.id = client.id;
  50. this.request = client.request;
  51. this.client = client;
  52. this.conn = client.conn;
  53. this.rooms = [];
  54. this.acks = {};
  55. this.connected = true;
  56. this.disconnected = false;
  57. this.handshake = this.buildHandshake();
  58. }
  59. /**
  60. * Inherits from `EventEmitter`.
  61. */
  62. Socket.prototype.__proto__ = Emitter.prototype;
  63. /**
  64. * Apply flags from `Socket`.
  65. */
  66. flags.forEach(function(flag){
  67. Socket.prototype.__defineGetter__(flag, function(){
  68. this.flags = this.flags || {};
  69. this.flags[flag] = true;
  70. return this;
  71. });
  72. });
  73. /**
  74. * `request` engine.io shorcut.
  75. *
  76. * @api public
  77. */
  78. Socket.prototype.__defineGetter__('request', function(){
  79. return this.conn.request;
  80. });
  81. /**
  82. * Builds the `handshake` BC object
  83. *
  84. * @api private
  85. */
  86. Socket.prototype.buildHandshake = function(){
  87. return {
  88. headers: this.request.headers,
  89. time: (new Date) + '',
  90. address: this.request.connection.address(),
  91. xdomain: !!this.request.headers.origin,
  92. secure: !!this.request.connection.encrypted,
  93. issued: +(new Date),
  94. url: this.request.url,
  95. query: url.parse(this.request.url, true).query || {}
  96. };
  97. };
  98. /**
  99. * Emits to this client.
  100. *
  101. * @return {Socket} self
  102. * @api public
  103. */
  104. Socket.prototype.emit = function(ev){
  105. if (~exports.events.indexOf(ev)) {
  106. emit.apply(this, arguments);
  107. } else {
  108. var args = Array.prototype.slice.call(arguments);
  109. var packet = {};
  110. packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
  111. packet.data = args;
  112. // access last argument to see if it's an ACK callback
  113. if ('function' == typeof args[args.length - 1]) {
  114. if (this._rooms || (this.flags && this.flags.broadcast)) {
  115. throw new Error('Callbacks are not supported when broadcasting');
  116. }
  117. debug('emitting packet with ack id %d', this.nsp.ids);
  118. this.acks[this.nsp.ids] = args.pop();
  119. packet.id = this.nsp.ids++;
  120. }
  121. if (this._rooms || (this.flags && this.flags.broadcast)) {
  122. this.adapter.broadcast(packet, {
  123. except: [this.id],
  124. rooms: this._rooms,
  125. flags: this.flags
  126. });
  127. } else {
  128. // dispatch packet
  129. this.packet(packet);
  130. }
  131. // reset flags
  132. delete this._rooms;
  133. delete this.flags;
  134. }
  135. return this;
  136. };
  137. /**
  138. * Targets a room when broadcasting.
  139. *
  140. * @param {String} name
  141. * @return {Socket} self
  142. * @api public
  143. */
  144. Socket.prototype.to =
  145. Socket.prototype.in = function(name){
  146. this._rooms = this._rooms || [];
  147. if (!~this._rooms.indexOf(name)) this._rooms.push(name);
  148. return this;
  149. };
  150. /**
  151. * Sends a `message` event.
  152. *
  153. * @return {Socket} self
  154. * @api public
  155. */
  156. Socket.prototype.send =
  157. Socket.prototype.write = function(){
  158. var args = Array.prototype.slice.call(arguments);
  159. args.unshift('message');
  160. this.emit.apply(this, args);
  161. return this;
  162. };
  163. /**
  164. * Writes a packet.
  165. *
  166. * @param {Object} packet object
  167. * @api private
  168. */
  169. Socket.prototype.packet = function(packet, preEncoded){
  170. packet.nsp = this.nsp.name;
  171. var volatile = this.flags && this.flags.volatile;
  172. this.client.packet(packet, preEncoded, volatile);
  173. };
  174. /**
  175. * Joins a room.
  176. *
  177. * @param {String} room
  178. * @param {Function} optional, callback
  179. * @return {Socket} self
  180. * @api private
  181. */
  182. Socket.prototype.join = function(room, fn){
  183. debug('joining room %s', room);
  184. var self = this;
  185. if (~this.rooms.indexOf(room)) return this;
  186. this.adapter.add(this.id, room, function(err){
  187. if (err) return fn && fn(err);
  188. debug('joined room %s', room);
  189. self.rooms.push(room);
  190. fn && fn(null);
  191. });
  192. return this;
  193. };
  194. /**
  195. * Leaves a room.
  196. *
  197. * @param {String} room
  198. * @param {Function} optional, callback
  199. * @return {Socket} self
  200. * @api private
  201. */
  202. Socket.prototype.leave = function(room, fn){
  203. debug('leave room %s', room);
  204. var self = this;
  205. this.adapter.del(this.id, room, function(err){
  206. if (err) return fn && fn(err);
  207. debug('left room %s', room);
  208. self.rooms.splice(self.rooms.indexOf(room, 1));
  209. fn && fn(null);
  210. });
  211. return this;
  212. };
  213. /**
  214. * Leave all rooms.
  215. *
  216. * @api private
  217. */
  218. Socket.prototype.leaveAll = function(){
  219. this.adapter.delAll(this.id);
  220. };
  221. /**
  222. * Called by `Namespace` upon succesful
  223. * middleware execution (ie: authorization).
  224. *
  225. * @api private
  226. */
  227. Socket.prototype.onconnect = function(){
  228. debug('socket connected - writing packet');
  229. this.join(this.id);
  230. this.packet({ type: parser.CONNECT });
  231. this.nsp.connected[this.id] = this;
  232. };
  233. /**
  234. * Called with each packet. Called by `Client`.
  235. *
  236. * @param {Object} packet
  237. * @api private
  238. */
  239. Socket.prototype.onpacket = function(packet){
  240. debug('got packet %j', packet);
  241. switch (packet.type) {
  242. case parser.EVENT:
  243. this.onevent(packet);
  244. break;
  245. case parser.BINARY_EVENT:
  246. this.onevent(packet);
  247. break;
  248. case parser.ACK:
  249. this.onack(packet);
  250. break;
  251. case parser.DISCONNECT:
  252. this.ondisconnect();
  253. break;
  254. case parser.ERROR:
  255. this.emit('error', packet.data);
  256. }
  257. };
  258. /**
  259. * Called upon event packet.
  260. *
  261. * @param {Object} packet object
  262. * @api private
  263. */
  264. Socket.prototype.onevent = function(packet){
  265. var args = packet.data || [];
  266. debug('emitting event %j', args);
  267. if (null != packet.id) {
  268. debug('attaching ack callback to event');
  269. args.push(this.ack(packet.id));
  270. }
  271. emit.apply(this, args);
  272. };
  273. /**
  274. * Produces an ack callback to emit with an event.
  275. *
  276. * @param {Number} packet id
  277. * @api private
  278. */
  279. Socket.prototype.ack = function(id){
  280. var self = this;
  281. var sent = false;
  282. return function(){
  283. // prevent double callbacks
  284. if (sent) return;
  285. var args = Array.prototype.slice.call(arguments);
  286. debug('sending ack %j', args);
  287. self.packet({
  288. id: id,
  289. type: parser.ACK,
  290. data: args
  291. });
  292. };
  293. };
  294. /**
  295. * Called upon ack packet.
  296. *
  297. * @api private
  298. */
  299. Socket.prototype.onack = function(packet){
  300. var ack = this.acks[packet.id];
  301. if ('function' == typeof ack) {
  302. debug('calling ack %s with %j', packet.id, packet.data);
  303. ack.apply(this, packet.data);
  304. delete this.acks[packet.id];
  305. } else {
  306. debug('bad ack %s', packet.id);
  307. }
  308. };
  309. /**
  310. * Called upon client disconnect packet.
  311. *
  312. * @api private
  313. */
  314. Socket.prototype.ondisconnect = function(){
  315. debug('got disconnect packet');
  316. this.onclose('client namespace disconnect');
  317. };
  318. /**
  319. * Called upon closing. Called by `Client`.
  320. *
  321. * @param {String} reason
  322. * @api private
  323. */
  324. Socket.prototype.onclose = function(reason){
  325. if (!this.connected) return this;
  326. debug('closing socket - reason %s', reason);
  327. this.leaveAll();
  328. this.nsp.remove(this);
  329. this.client.remove(this);
  330. this.connected = false;
  331. this.disconnected = true;
  332. delete this.nsp.connected[this.id];
  333. this.emit('disconnect', reason);
  334. };
  335. /**
  336. * Produces an `error` packet.
  337. *
  338. * @param {Object} error object
  339. * @api private
  340. */
  341. Socket.prototype.error = function(err){
  342. this.packet({ type: parser.ERROR, data: err });
  343. };
  344. /**
  345. * Disconnects this client.
  346. *
  347. * @param {Boolean} if `true`, closes the underlying connection
  348. * @return {Socket} self
  349. * @api public
  350. */
  351. Socket.prototype.disconnect = function(close){
  352. if (!this.connected) return this;
  353. if (close) {
  354. this.client.disconnect();
  355. } else {
  356. this.packet({ type: parser.DISCONNECT });
  357. this.onclose('server namespace disconnect');
  358. }
  359. return this;
  360. };