UNPKG

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