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