rc-web@69
|
1 /**
|
rc-web@69
|
2 * Module dependencies.
|
rc-web@69
|
3 */
|
rc-web@69
|
4
|
rc-web@69
|
5 var Socket = require('./socket')
|
rc-web@69
|
6 , EventEmitter = process.EventEmitter
|
rc-web@69
|
7 , parser = require('./parser')
|
rc-web@69
|
8 , util = require('./util');
|
rc-web@69
|
9
|
rc-web@69
|
10 /**
|
rc-web@69
|
11 * Exports the constructor.
|
rc-web@69
|
12 */
|
rc-web@69
|
13
|
rc-web@69
|
14 exports = module.exports = SocketNamespace;
|
rc-web@69
|
15
|
rc-web@69
|
16 /**
|
rc-web@69
|
17 * Constructor.
|
rc-web@69
|
18 *
|
rc-web@69
|
19 * @api public.
|
rc-web@69
|
20 */
|
rc-web@69
|
21
|
rc-web@69
|
22 function SocketNamespace (mgr, name) {
|
rc-web@69
|
23 this.manager = mgr;
|
rc-web@69
|
24 this.name = name || '';
|
rc-web@69
|
25 this.sockets = {};
|
rc-web@69
|
26 this.auth = false;
|
rc-web@69
|
27 this.setFlags();
|
rc-web@69
|
28 };
|
rc-web@69
|
29
|
rc-web@69
|
30 /**
|
rc-web@69
|
31 * Inherits from EventEmitter.
|
rc-web@69
|
32 */
|
rc-web@69
|
33
|
rc-web@69
|
34 SocketNamespace.prototype.__proto__ = EventEmitter.prototype;
|
rc-web@69
|
35
|
rc-web@69
|
36 /**
|
rc-web@69
|
37 * Copies emit since we override it.
|
rc-web@69
|
38 *
|
rc-web@69
|
39 * @api private
|
rc-web@69
|
40 */
|
rc-web@69
|
41
|
rc-web@69
|
42 SocketNamespace.prototype.$emit = EventEmitter.prototype.emit;
|
rc-web@69
|
43
|
rc-web@69
|
44 /**
|
rc-web@69
|
45 * Retrieves all clients as Socket instances as an array.
|
rc-web@69
|
46 *
|
rc-web@69
|
47 * @api public
|
rc-web@69
|
48 */
|
rc-web@69
|
49
|
rc-web@69
|
50 SocketNamespace.prototype.clients = function (room) {
|
rc-web@69
|
51 var room = this.name + (room !== undefined ?
|
rc-web@69
|
52 '/' + room : '');
|
rc-web@69
|
53
|
rc-web@69
|
54 if (!this.manager.rooms[room]) {
|
rc-web@69
|
55 return [];
|
rc-web@69
|
56 }
|
rc-web@69
|
57
|
rc-web@69
|
58 return this.manager.rooms[room].map(function (id) {
|
rc-web@69
|
59 return this.socket(id);
|
rc-web@69
|
60 }, this);
|
rc-web@69
|
61 };
|
rc-web@69
|
62
|
rc-web@69
|
63 /**
|
rc-web@69
|
64 * Access logger interface.
|
rc-web@69
|
65 *
|
rc-web@69
|
66 * @api public
|
rc-web@69
|
67 */
|
rc-web@69
|
68
|
rc-web@69
|
69 SocketNamespace.prototype.__defineGetter__('log', function () {
|
rc-web@69
|
70 return this.manager.log;
|
rc-web@69
|
71 });
|
rc-web@69
|
72
|
rc-web@69
|
73 /**
|
rc-web@69
|
74 * Access store.
|
rc-web@69
|
75 *
|
rc-web@69
|
76 * @api public
|
rc-web@69
|
77 */
|
rc-web@69
|
78
|
rc-web@69
|
79 SocketNamespace.prototype.__defineGetter__('store', function () {
|
rc-web@69
|
80 return this.manager.store;
|
rc-web@69
|
81 });
|
rc-web@69
|
82
|
rc-web@69
|
83 /**
|
rc-web@69
|
84 * JSON message flag.
|
rc-web@69
|
85 *
|
rc-web@69
|
86 * @api public
|
rc-web@69
|
87 */
|
rc-web@69
|
88
|
rc-web@69
|
89 SocketNamespace.prototype.__defineGetter__('json', function () {
|
rc-web@69
|
90 this.flags.json = true;
|
rc-web@69
|
91 return this;
|
rc-web@69
|
92 });
|
rc-web@69
|
93
|
rc-web@69
|
94 /**
|
rc-web@69
|
95 * Volatile message flag.
|
rc-web@69
|
96 *
|
rc-web@69
|
97 * @api public
|
rc-web@69
|
98 */
|
rc-web@69
|
99
|
rc-web@69
|
100 SocketNamespace.prototype.__defineGetter__('volatile', function () {
|
rc-web@69
|
101 this.flags.volatile = true;
|
rc-web@69
|
102 return this;
|
rc-web@69
|
103 });
|
rc-web@69
|
104
|
rc-web@69
|
105 /**
|
rc-web@69
|
106 * Overrides the room to relay messages to (flag).
|
rc-web@69
|
107 *
|
rc-web@69
|
108 * @api public
|
rc-web@69
|
109 */
|
rc-web@69
|
110
|
rc-web@69
|
111 SocketNamespace.prototype.in = SocketNamespace.prototype.to = function (room) {
|
rc-web@69
|
112 this.flags.endpoint = this.name + (room ? '/' + room : '');
|
rc-web@69
|
113 return this;
|
rc-web@69
|
114 };
|
rc-web@69
|
115
|
rc-web@69
|
116 /**
|
rc-web@69
|
117 * Adds a session id we should prevent relaying messages to (flag).
|
rc-web@69
|
118 *
|
rc-web@69
|
119 * @api public
|
rc-web@69
|
120 */
|
rc-web@69
|
121
|
rc-web@69
|
122 SocketNamespace.prototype.except = function (id) {
|
rc-web@69
|
123 this.flags.exceptions.push(id);
|
rc-web@69
|
124 return this;
|
rc-web@69
|
125 };
|
rc-web@69
|
126
|
rc-web@69
|
127 /**
|
rc-web@69
|
128 * Sets the default flags.
|
rc-web@69
|
129 *
|
rc-web@69
|
130 * @api private
|
rc-web@69
|
131 */
|
rc-web@69
|
132
|
rc-web@69
|
133 SocketNamespace.prototype.setFlags = function () {
|
rc-web@69
|
134 this.flags = {
|
rc-web@69
|
135 endpoint: this.name
|
rc-web@69
|
136 , exceptions: []
|
rc-web@69
|
137 };
|
rc-web@69
|
138 return this;
|
rc-web@69
|
139 };
|
rc-web@69
|
140
|
rc-web@69
|
141 /**
|
rc-web@69
|
142 * Sends out a packet.
|
rc-web@69
|
143 *
|
rc-web@69
|
144 * @api private
|
rc-web@69
|
145 */
|
rc-web@69
|
146
|
rc-web@69
|
147 SocketNamespace.prototype.packet = function (packet) {
|
rc-web@69
|
148 packet.endpoint = this.name;
|
rc-web@69
|
149
|
rc-web@69
|
150 var store = this.store
|
rc-web@69
|
151 , log = this.log
|
rc-web@69
|
152 , volatile = this.flags.volatile
|
rc-web@69
|
153 , exceptions = this.flags.exceptions
|
rc-web@69
|
154 , packet = parser.encodePacket(packet);
|
rc-web@69
|
155
|
rc-web@69
|
156 this.manager.onDispatch(this.flags.endpoint, packet, volatile, exceptions);
|
rc-web@69
|
157 this.store.publish('dispatch', this.flags.endpoint, packet, volatile, exceptions);
|
rc-web@69
|
158
|
rc-web@69
|
159 this.setFlags();
|
rc-web@69
|
160
|
rc-web@69
|
161 return this;
|
rc-web@69
|
162 };
|
rc-web@69
|
163
|
rc-web@69
|
164 /**
|
rc-web@69
|
165 * Sends to everyone.
|
rc-web@69
|
166 *
|
rc-web@69
|
167 * @api public
|
rc-web@69
|
168 */
|
rc-web@69
|
169
|
rc-web@69
|
170 SocketNamespace.prototype.send = function (data) {
|
rc-web@69
|
171 return this.packet({
|
rc-web@69
|
172 type: this.flags.json ? 'json' : 'message'
|
rc-web@69
|
173 , data: data
|
rc-web@69
|
174 });
|
rc-web@69
|
175 };
|
rc-web@69
|
176
|
rc-web@69
|
177 /**
|
rc-web@69
|
178 * Emits to everyone (override).
|
rc-web@69
|
179 *
|
rc-web@69
|
180 * @api public
|
rc-web@69
|
181 */
|
rc-web@69
|
182
|
rc-web@69
|
183 SocketNamespace.prototype.emit = function (name) {
|
rc-web@69
|
184 if (name == 'newListener') {
|
rc-web@69
|
185 return this.$emit.apply(this, arguments);
|
rc-web@69
|
186 }
|
rc-web@69
|
187
|
rc-web@69
|
188 return this.packet({
|
rc-web@69
|
189 type: 'event'
|
rc-web@69
|
190 , name: name
|
rc-web@69
|
191 , args: util.toArray(arguments).slice(1)
|
rc-web@69
|
192 });
|
rc-web@69
|
193 };
|
rc-web@69
|
194
|
rc-web@69
|
195 /**
|
rc-web@69
|
196 * Retrieves or creates a write-only socket for a client, unless specified.
|
rc-web@69
|
197 *
|
rc-web@69
|
198 * @param {Boolean} whether the socket will be readable when initialized
|
rc-web@69
|
199 * @api public
|
rc-web@69
|
200 */
|
rc-web@69
|
201
|
rc-web@69
|
202 SocketNamespace.prototype.socket = function (sid, readable) {
|
rc-web@69
|
203 if (!this.sockets[sid]) {
|
rc-web@69
|
204 this.sockets[sid] = new Socket(this.manager, sid, this, readable);
|
rc-web@69
|
205 }
|
rc-web@69
|
206
|
rc-web@69
|
207 return this.sockets[sid];
|
rc-web@69
|
208 };
|
rc-web@69
|
209
|
rc-web@69
|
210 /**
|
rc-web@69
|
211 * Sets authorization for this namespace.
|
rc-web@69
|
212 *
|
rc-web@69
|
213 * @api public
|
rc-web@69
|
214 */
|
rc-web@69
|
215
|
rc-web@69
|
216 SocketNamespace.prototype.authorization = function (fn) {
|
rc-web@69
|
217 this.auth = fn;
|
rc-web@69
|
218 return this;
|
rc-web@69
|
219 };
|
rc-web@69
|
220
|
rc-web@69
|
221 /**
|
rc-web@69
|
222 * Called when a socket disconnects entirely.
|
rc-web@69
|
223 *
|
rc-web@69
|
224 * @api private
|
rc-web@69
|
225 */
|
rc-web@69
|
226
|
rc-web@69
|
227 SocketNamespace.prototype.handleDisconnect = function (sid, reason, raiseOnDisconnect) {
|
rc-web@69
|
228 if (this.sockets[sid] && this.sockets[sid].readable) {
|
rc-web@69
|
229 if (raiseOnDisconnect) this.sockets[sid].onDisconnect(reason);
|
rc-web@69
|
230 delete this.sockets[sid];
|
rc-web@69
|
231 }
|
rc-web@69
|
232 };
|
rc-web@69
|
233
|
rc-web@69
|
234 /**
|
rc-web@69
|
235 * Performs authentication.
|
rc-web@69
|
236 *
|
rc-web@69
|
237 * @param Object client request data
|
rc-web@69
|
238 * @api private
|
rc-web@69
|
239 */
|
rc-web@69
|
240
|
rc-web@69
|
241 SocketNamespace.prototype.authorize = function (data, fn) {
|
rc-web@69
|
242 if (this.auth) {
|
rc-web@69
|
243 var self = this;
|
rc-web@69
|
244
|
rc-web@69
|
245 this.auth.call(this, data, function (err, authorized) {
|
rc-web@69
|
246 self.log.debug('client ' +
|
rc-web@69
|
247 (authorized ? '' : 'un') + 'authorized for ' + self.name);
|
rc-web@69
|
248 fn(err, authorized);
|
rc-web@69
|
249 });
|
rc-web@69
|
250 } else {
|
rc-web@69
|
251 this.log.debug('client authorized for ' + this.name);
|
rc-web@69
|
252 fn(null, true);
|
rc-web@69
|
253 }
|
rc-web@69
|
254
|
rc-web@69
|
255 return this;
|
rc-web@69
|
256 };
|
rc-web@69
|
257
|
rc-web@69
|
258 /**
|
rc-web@69
|
259 * Handles a packet.
|
rc-web@69
|
260 *
|
rc-web@69
|
261 * @api private
|
rc-web@69
|
262 */
|
rc-web@69
|
263
|
rc-web@69
|
264 SocketNamespace.prototype.handlePacket = function (sessid, packet) {
|
rc-web@69
|
265 var socket = this.socket(sessid)
|
rc-web@69
|
266 , dataAck = packet.ack == 'data'
|
rc-web@69
|
267 , manager = this.manager
|
rc-web@69
|
268 , self = this;
|
rc-web@69
|
269
|
rc-web@69
|
270 function ack () {
|
rc-web@69
|
271 self.log.debug('sending data ack packet');
|
rc-web@69
|
272 socket.packet({
|
rc-web@69
|
273 type: 'ack'
|
rc-web@69
|
274 , args: util.toArray(arguments)
|
rc-web@69
|
275 , ackId: packet.id
|
rc-web@69
|
276 });
|
rc-web@69
|
277 };
|
rc-web@69
|
278
|
rc-web@69
|
279 function error (err) {
|
rc-web@69
|
280 self.log.warn('handshake error ' + err + ' for ' + self.name);
|
rc-web@69
|
281 socket.packet({ type: 'error', reason: err });
|
rc-web@69
|
282 };
|
rc-web@69
|
283
|
rc-web@69
|
284 function connect () {
|
rc-web@69
|
285 self.manager.onJoin(sessid, self.name);
|
rc-web@69
|
286 self.store.publish('join', sessid, self.name);
|
rc-web@69
|
287
|
rc-web@69
|
288 // packet echo
|
rc-web@69
|
289 socket.packet({ type: 'connect' });
|
rc-web@69
|
290
|
rc-web@69
|
291 // emit connection event
|
rc-web@69
|
292 self.$emit('connection', socket);
|
rc-web@69
|
293 };
|
rc-web@69
|
294
|
rc-web@69
|
295 switch (packet.type) {
|
rc-web@69
|
296 case 'connect':
|
rc-web@69
|
297 if (packet.endpoint == '') {
|
rc-web@69
|
298 connect();
|
rc-web@69
|
299 } else {
|
rc-web@69
|
300 var handshakeData = manager.handshaken[sessid];
|
rc-web@69
|
301
|
rc-web@69
|
302 this.authorize(handshakeData, function (err, authorized, newData) {
|
rc-web@69
|
303 if (err) return error(err);
|
rc-web@69
|
304
|
rc-web@69
|
305 if (authorized) {
|
rc-web@69
|
306 manager.onHandshake(sessid, newData || handshakeData);
|
rc-web@69
|
307 self.store.publish('handshake', sessid, newData || handshakeData);
|
rc-web@69
|
308 connect();
|
rc-web@69
|
309 } else {
|
rc-web@69
|
310 error('unauthorized');
|
rc-web@69
|
311 }
|
rc-web@69
|
312 });
|
rc-web@69
|
313 }
|
rc-web@69
|
314 break;
|
rc-web@69
|
315
|
rc-web@69
|
316 case 'ack':
|
rc-web@69
|
317 if (socket.acks[packet.ackId]) {
|
rc-web@69
|
318 socket.acks[packet.ackId].apply(socket, packet.args);
|
rc-web@69
|
319 } else {
|
rc-web@69
|
320 this.log.info('unknown ack packet');
|
rc-web@69
|
321 }
|
rc-web@69
|
322 break;
|
rc-web@69
|
323
|
rc-web@69
|
324 case 'event':
|
rc-web@69
|
325 // check if the emitted event is not blacklisted
|
rc-web@69
|
326 if (-~manager.get('blacklist').indexOf(packet.name)) {
|
rc-web@69
|
327 this.log.debug('ignoring blacklisted event `' + packet.name + '`');
|
rc-web@69
|
328 } else {
|
rc-web@69
|
329 var params = [packet.name].concat(packet.args);
|
rc-web@69
|
330
|
rc-web@69
|
331 if (dataAck) {
|
rc-web@69
|
332 params.push(ack);
|
rc-web@69
|
333 }
|
rc-web@69
|
334
|
rc-web@69
|
335 socket.$emit.apply(socket, params);
|
rc-web@69
|
336 }
|
rc-web@69
|
337 break;
|
rc-web@69
|
338
|
rc-web@69
|
339 case 'disconnect':
|
rc-web@69
|
340 this.manager.onLeave(sessid, this.name);
|
rc-web@69
|
341 this.store.publish('leave', sessid, this.name);
|
rc-web@69
|
342
|
rc-web@69
|
343 socket.$emit('disconnect', packet.reason || 'packet');
|
rc-web@69
|
344 break;
|
rc-web@69
|
345
|
rc-web@69
|
346 case 'json':
|
rc-web@69
|
347 case 'message':
|
rc-web@69
|
348 var params = ['message', packet.data];
|
rc-web@69
|
349
|
rc-web@69
|
350 if (dataAck)
|
rc-web@69
|
351 params.push(ack);
|
rc-web@69
|
352
|
rc-web@69
|
353 socket.$emit.apply(socket, params);
|
rc-web@69
|
354 };
|
rc-web@69
|
355 };
|