socket.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  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');
  9. var assign = require('object-assign');
  10. /**
  11. * Module exports.
  12. */
  13. module.exports = exports = Socket;
  14. /**
  15. * Blacklisted events.
  16. *
  17. * @api public
  18. */
  19. exports.events = [
  20. 'error',
  21. 'connect',
  22. 'disconnect',
  23. 'disconnecting',
  24. 'newListener',
  25. 'removeListener'
  26. ];
  27. /**
  28. * Flags.
  29. *
  30. * @api private
  31. */
  32. var flags = [
  33. 'json',
  34. 'volatile',
  35. 'broadcast'
  36. ];
  37. /**
  38. * `EventEmitter#emit` reference.
  39. */
  40. var emit = Emitter.prototype.emit;
  41. /**
  42. * Interface to a `Client` for a given `Namespace`.
  43. *
  44. * @param {Namespace} nsp
  45. * @param {Client} client
  46. * @api public
  47. */
  48. function Socket(nsp, client, query){
  49. this.nsp = nsp;
  50. this.server = nsp.server;
  51. this.adapter = this.nsp.adapter;
  52. this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id;
  53. this.client = client;
  54. this.conn = client.conn;
  55. this.rooms = {};
  56. this.acks = {};
  57. this.connected = true;
  58. this.disconnected = false;
  59. this.handshake = this.buildHandshake(query);
  60. this.fns = [];
  61. }
  62. /**
  63. * Inherits from `EventEmitter`.
  64. */
  65. Socket.prototype.__proto__ = Emitter.prototype;
  66. /**
  67. * Apply flags from `Socket`.
  68. */
  69. flags.forEach(function(flag){
  70. Object.defineProperty(Socket.prototype, flag, {
  71. get: function() {
  72. this.flags = this.flags || {};
  73. this.flags[flag] = true;
  74. return this;
  75. }
  76. });
  77. });
  78. /**
  79. * `request` engine.io shortcut.
  80. *
  81. * @api public
  82. */
  83. Object.defineProperty(Socket.prototype, 'request', {
  84. get: function() {
  85. return this.conn.request;
  86. }
  87. });
  88. /**
  89. * Builds the `handshake` BC object
  90. *
  91. * @api private
  92. */
  93. Socket.prototype.buildHandshake = function(query){
  94. var self = this;
  95. function buildQuery(){
  96. var requestQuery = url.parse(self.request.url, true).query;
  97. //if socket-specific query exist, replace query strings in requestQuery
  98. return assign({}, query, requestQuery);
  99. }
  100. return {
  101. headers: this.request.headers,
  102. time: (new Date) + '',
  103. address: this.conn.remoteAddress,
  104. xdomain: !!this.request.headers.origin,
  105. secure: !!this.request.connection.encrypted,
  106. issued: +(new Date),
  107. url: this.request.url,
  108. query: buildQuery()
  109. };
  110. };
  111. /**
  112. * Emits to this client.
  113. *
  114. * @return {Socket} self
  115. * @api public
  116. */
  117. Socket.prototype.emit = function(ev){
  118. if (~exports.events.indexOf(ev)) {
  119. emit.apply(this, arguments);
  120. } else {
  121. var args = Array.prototype.slice.call(arguments);
  122. var packet = {};
  123. packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
  124. packet.data = args;
  125. var flags = this.flags || {};
  126. // access last argument to see if it's an ACK callback
  127. if ('function' == typeof args[args.length - 1]) {
  128. if (this._rooms || flags.broadcast) {
  129. throw new Error('Callbacks are not supported when broadcasting');
  130. }
  131. debug('emitting packet with ack id %d', this.nsp.ids);
  132. this.acks[this.nsp.ids] = args.pop();
  133. packet.id = this.nsp.ids++;
  134. }
  135. if (this._rooms || flags.broadcast) {
  136. this.adapter.broadcast(packet, {
  137. except: [this.id],
  138. rooms: this._rooms,
  139. flags: flags
  140. });
  141. } else {
  142. // dispatch packet
  143. this.packet(packet, {
  144. volatile: flags.volatile,
  145. compress: flags.compress
  146. });
  147. }
  148. // reset flags
  149. delete this._rooms;
  150. delete this.flags;
  151. }
  152. return this;
  153. };
  154. /**
  155. * Targets a room when broadcasting.
  156. *
  157. * @param {String} name
  158. * @return {Socket} self
  159. * @api public
  160. */
  161. Socket.prototype.to =
  162. Socket.prototype.in = function(name){
  163. this._rooms = this._rooms || [];
  164. if (!~this._rooms.indexOf(name)) this._rooms.push(name);
  165. return this;
  166. };
  167. /**
  168. * Sends a `message` event.
  169. *
  170. * @return {Socket} self
  171. * @api public
  172. */
  173. Socket.prototype.send =
  174. Socket.prototype.write = function(){
  175. var args = Array.prototype.slice.call(arguments);
  176. args.unshift('message');
  177. this.emit.apply(this, args);
  178. return this;
  179. };
  180. /**
  181. * Writes a packet.
  182. *
  183. * @param {Object} packet object
  184. * @param {Object} opts options
  185. * @api private
  186. */
  187. Socket.prototype.packet = function(packet, opts){
  188. packet.nsp = this.nsp.name;
  189. opts = opts || {};
  190. opts.compress = false !== opts.compress;
  191. this.client.packet(packet, opts);
  192. };
  193. /**
  194. * Joins a room.
  195. *
  196. * @param {String} room
  197. * @param {Function} fn optional, callback
  198. * @return {Socket} self
  199. * @api private
  200. */
  201. Socket.prototype.join = function(room, fn){
  202. debug('joining room %s', room);
  203. var self = this;
  204. if (this.rooms.hasOwnProperty(room)) {
  205. fn && fn(null);
  206. return this;
  207. }
  208. this.adapter.add(this.id, room, function(err){
  209. if (err) return fn && fn(err);
  210. debug('joined room %s', room);
  211. self.rooms[room] = room;
  212. fn && fn(null);
  213. });
  214. return this;
  215. };
  216. /**
  217. * Leaves a room.
  218. *
  219. * @param {String} room
  220. * @param {Function} fn optional, callback
  221. * @return {Socket} self
  222. * @api private
  223. */
  224. Socket.prototype.leave = function(room, fn){
  225. debug('leave room %s', room);
  226. var self = this;
  227. this.adapter.del(this.id, room, function(err){
  228. if (err) return fn && fn(err);
  229. debug('left room %s', room);
  230. delete self.rooms[room];
  231. fn && fn(null);
  232. });
  233. return this;
  234. };
  235. /**
  236. * Leave all rooms.
  237. *
  238. * @api private
  239. */
  240. Socket.prototype.leaveAll = function(){
  241. this.adapter.delAll(this.id);
  242. this.rooms = {};
  243. };
  244. /**
  245. * Called by `Namespace` upon successful
  246. * middleware execution (ie: authorization).
  247. * Socket is added to namespace array before
  248. * call to join, so adapters can access it.
  249. *
  250. * @api private
  251. */
  252. Socket.prototype.onconnect = function(){
  253. debug('socket connected - writing packet');
  254. this.nsp.connected[this.id] = this;
  255. this.join(this.id);
  256. this.packet({ type: parser.CONNECT });
  257. };
  258. /**
  259. * Called with each packet. Called by `Client`.
  260. *
  261. * @param {Object} packet
  262. * @api private
  263. */
  264. Socket.prototype.onpacket = function(packet){
  265. debug('got packet %j', packet);
  266. switch (packet.type) {
  267. case parser.EVENT:
  268. this.onevent(packet);
  269. break;
  270. case parser.BINARY_EVENT:
  271. this.onevent(packet);
  272. break;
  273. case parser.ACK:
  274. this.onack(packet);
  275. break;
  276. case parser.BINARY_ACK:
  277. this.onack(packet);
  278. break;
  279. case parser.DISCONNECT:
  280. this.ondisconnect();
  281. break;
  282. case parser.ERROR:
  283. this.emit('error', packet.data);
  284. }
  285. };
  286. /**
  287. * Called upon event packet.
  288. *
  289. * @param {Object} packet object
  290. * @api private
  291. */
  292. Socket.prototype.onevent = function(packet){
  293. var args = packet.data || [];
  294. debug('emitting event %j', args);
  295. if (null != packet.id) {
  296. debug('attaching ack callback to event');
  297. args.push(this.ack(packet.id));
  298. }
  299. this.dispatch(args);
  300. };
  301. /**
  302. * Produces an ack callback to emit with an event.
  303. *
  304. * @param {Number} id packet id
  305. * @api private
  306. */
  307. Socket.prototype.ack = function(id){
  308. var self = this;
  309. var sent = false;
  310. return function(){
  311. // prevent double callbacks
  312. if (sent) return;
  313. var args = Array.prototype.slice.call(arguments);
  314. debug('sending ack %j', args);
  315. var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
  316. self.packet({
  317. id: id,
  318. type: type,
  319. data: args
  320. });
  321. sent = true;
  322. };
  323. };
  324. /**
  325. * Called upon ack packet.
  326. *
  327. * @api private
  328. */
  329. Socket.prototype.onack = function(packet){
  330. var ack = this.acks[packet.id];
  331. if ('function' == typeof ack) {
  332. debug('calling ack %s with %j', packet.id, packet.data);
  333. ack.apply(this, packet.data);
  334. delete this.acks[packet.id];
  335. } else {
  336. debug('bad ack %s', packet.id);
  337. }
  338. };
  339. /**
  340. * Called upon client disconnect packet.
  341. *
  342. * @api private
  343. */
  344. Socket.prototype.ondisconnect = function(){
  345. debug('got disconnect packet');
  346. this.onclose('client namespace disconnect');
  347. };
  348. /**
  349. * Handles a client error.
  350. *
  351. * @api private
  352. */
  353. Socket.prototype.onerror = function(err){
  354. if (this.listeners('error').length) {
  355. this.emit('error', err);
  356. } else {
  357. console.error('Missing error handler on `socket`.');
  358. console.error(err.stack);
  359. }
  360. };
  361. /**
  362. * Called upon closing. Called by `Client`.
  363. *
  364. * @param {String} reason
  365. * @throw {Error} optional error object
  366. * @api private
  367. */
  368. Socket.prototype.onclose = function(reason){
  369. if (!this.connected) return this;
  370. debug('closing socket - reason %s', reason);
  371. this.emit('disconnecting', reason);
  372. this.leaveAll();
  373. this.nsp.remove(this);
  374. this.client.remove(this);
  375. this.connected = false;
  376. this.disconnected = true;
  377. delete this.nsp.connected[this.id];
  378. this.emit('disconnect', reason);
  379. };
  380. /**
  381. * Produces an `error` packet.
  382. *
  383. * @param {Object} err error object
  384. * @api private
  385. */
  386. Socket.prototype.error = function(err){
  387. this.packet({ type: parser.ERROR, data: err });
  388. };
  389. /**
  390. * Disconnects this client.
  391. *
  392. * @param {Boolean} close if `true`, closes the underlying connection
  393. * @return {Socket} self
  394. * @api public
  395. */
  396. Socket.prototype.disconnect = function(close){
  397. if (!this.connected) return this;
  398. if (close) {
  399. this.client.disconnect();
  400. } else {
  401. this.packet({ type: parser.DISCONNECT });
  402. this.onclose('server namespace disconnect');
  403. }
  404. return this;
  405. };
  406. /**
  407. * Sets the compress flag.
  408. *
  409. * @param {Boolean} compress if `true`, compresses the sending data
  410. * @return {Socket} self
  411. * @api public
  412. */
  413. Socket.prototype.compress = function(compress){
  414. this.flags = this.flags || {};
  415. this.flags.compress = compress;
  416. return this;
  417. };
  418. /**
  419. * Dispatch incoming event to socket listeners.
  420. *
  421. * @param {Array} event that will get emitted
  422. * @api private
  423. */
  424. Socket.prototype.dispatch = function(event){
  425. debug('dispatching an event %j', event);
  426. var self = this;
  427. this.run(event, function(err){
  428. process.nextTick(function(){
  429. if (err) {
  430. return self.error(err.data || err.message);
  431. }
  432. emit.apply(self, event);
  433. });
  434. });
  435. }
  436. /**
  437. * Sets up socket middleware.
  438. *
  439. * @param {Function} middleware function (event, next)
  440. * @return {Socket} self
  441. * @api public
  442. */
  443. Socket.prototype.use = function(fn){
  444. this.fns.push(fn);
  445. return this;
  446. };
  447. /**
  448. * Executes the middleware for an incoming event.
  449. *
  450. * @param {Array} event that will get emitted
  451. * @param {Function} last fn call in the middleware
  452. * @api private
  453. */
  454. Socket.prototype.run = function(event, fn){
  455. var fns = this.fns.slice(0);
  456. if (!fns.length) return fn(null);
  457. function run(i){
  458. fns[i](event, function(err){
  459. // upon error, short-circuit
  460. if (err) return fn(err);
  461. // if no middleware left, summon callback
  462. if (!fns[i + 1]) return fn(null);
  463. // go on to next
  464. run(i + 1);
  465. });
  466. }
  467. run(0);
  468. };