UNPKG

15.5 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.Socket = exports.RESERVED_EVENTS = void 0;
7const socket_io_parser_1 = require("socket.io-parser");
8const url = require("url");
9const debug_1 = __importDefault(require("debug"));
10const typed_events_1 = require("./typed-events");
11const base64id_1 = __importDefault(require("base64id"));
12const broadcast_operator_1 = require("./broadcast-operator");
13const debug = (0, debug_1.default)("socket.io:socket");
14exports.RESERVED_EVENTS = new Set([
15 "connect",
16 "connect_error",
17 "disconnect",
18 "disconnecting",
19 "newListener",
20 "removeListener",
21]);
22class Socket extends typed_events_1.StrictEventEmitter {
23 /**
24 * Interface to a `Client` for a given `Namespace`.
25 *
26 * @param {Namespace} nsp
27 * @param {Client} client
28 * @param {Object} auth
29 * @package
30 */
31 constructor(nsp, client, auth) {
32 super();
33 this.nsp = nsp;
34 this.client = client;
35 /**
36 * Additional information that can be attached to the Socket instance and which will be used in the fetchSockets method
37 */
38 this.data = {};
39 this.acks = new Map();
40 this.fns = [];
41 this.flags = {};
42 this.server = nsp.server;
43 this.adapter = this.nsp.adapter;
44 if (client.conn.protocol === 3) {
45 // @ts-ignore
46 this.id = nsp.name !== "/" ? nsp.name + "#" + client.id : client.id;
47 }
48 else {
49 this.id = base64id_1.default.generateId(); // don't reuse the Engine.IO id because it's sensitive information
50 }
51 this.connected = true;
52 this.disconnected = false;
53 this.handshake = this.buildHandshake(auth);
54 }
55 /**
56 * Builds the `handshake` BC object
57 *
58 * @private
59 */
60 buildHandshake(auth) {
61 return {
62 headers: this.request.headers,
63 time: new Date() + "",
64 address: this.conn.remoteAddress,
65 xdomain: !!this.request.headers.origin,
66 // @ts-ignore
67 secure: !!this.request.connection.encrypted,
68 issued: +new Date(),
69 url: this.request.url,
70 query: url.parse(this.request.url, true).query,
71 auth,
72 };
73 }
74 /**
75 * Emits to this client.
76 *
77 * @return Always returns `true`.
78 * @public
79 */
80 emit(ev, ...args) {
81 if (exports.RESERVED_EVENTS.has(ev)) {
82 throw new Error(`"${ev}" is a reserved event name`);
83 }
84 const data = [ev, ...args];
85 const packet = {
86 type: socket_io_parser_1.PacketType.EVENT,
87 data: data,
88 };
89 // access last argument to see if it's an ACK callback
90 if (typeof data[data.length - 1] === "function") {
91 debug("emitting packet with ack id %d", this.nsp._ids);
92 this.acks.set(this.nsp._ids, data.pop());
93 packet.id = this.nsp._ids++;
94 }
95 const flags = Object.assign({}, this.flags);
96 this.flags = {};
97 this.packet(packet, flags);
98 return true;
99 }
100 /**
101 * Targets a room when broadcasting.
102 *
103 * @param room
104 * @return self
105 * @public
106 */
107 to(room) {
108 return this.newBroadcastOperator().to(room);
109 }
110 /**
111 * Targets a room when broadcasting.
112 *
113 * @param room
114 * @return self
115 * @public
116 */
117 in(room) {
118 return this.newBroadcastOperator().in(room);
119 }
120 /**
121 * Excludes a room when broadcasting.
122 *
123 * @param room
124 * @return self
125 * @public
126 */
127 except(room) {
128 return this.newBroadcastOperator().except(room);
129 }
130 /**
131 * Sends a `message` event.
132 *
133 * @return self
134 * @public
135 */
136 send(...args) {
137 this.emit("message", ...args);
138 return this;
139 }
140 /**
141 * Sends a `message` event.
142 *
143 * @return self
144 * @public
145 */
146 write(...args) {
147 this.emit("message", ...args);
148 return this;
149 }
150 /**
151 * Writes a packet.
152 *
153 * @param {Object} packet - packet object
154 * @param {Object} opts - options
155 * @private
156 */
157 packet(packet, opts = {}) {
158 packet.nsp = this.nsp.name;
159 opts.compress = false !== opts.compress;
160 this.client._packet(packet, opts);
161 }
162 /**
163 * Joins a room.
164 *
165 * @param {String|Array} rooms - room or array of rooms
166 * @return a Promise or nothing, depending on the adapter
167 * @public
168 */
169 join(rooms) {
170 debug("join room %s", rooms);
171 return this.adapter.addAll(this.id, new Set(Array.isArray(rooms) ? rooms : [rooms]));
172 }
173 /**
174 * Leaves a room.
175 *
176 * @param {String} room
177 * @return a Promise or nothing, depending on the adapter
178 * @public
179 */
180 leave(room) {
181 debug("leave room %s", room);
182 return this.adapter.del(this.id, room);
183 }
184 /**
185 * Leave all rooms.
186 *
187 * @private
188 */
189 leaveAll() {
190 this.adapter.delAll(this.id);
191 }
192 /**
193 * Called by `Namespace` upon successful
194 * middleware execution (ie: authorization).
195 * Socket is added to namespace array before
196 * call to join, so adapters can access it.
197 *
198 * @private
199 */
200 _onconnect() {
201 debug("socket connected - writing packet");
202 this.join(this.id);
203 if (this.conn.protocol === 3) {
204 this.packet({ type: socket_io_parser_1.PacketType.CONNECT });
205 }
206 else {
207 this.packet({ type: socket_io_parser_1.PacketType.CONNECT, data: { sid: this.id } });
208 }
209 }
210 /**
211 * Called with each packet. Called by `Client`.
212 *
213 * @param {Object} packet
214 * @private
215 */
216 _onpacket(packet) {
217 debug("got packet %j", packet);
218 switch (packet.type) {
219 case socket_io_parser_1.PacketType.EVENT:
220 this.onevent(packet);
221 break;
222 case socket_io_parser_1.PacketType.BINARY_EVENT:
223 this.onevent(packet);
224 break;
225 case socket_io_parser_1.PacketType.ACK:
226 this.onack(packet);
227 break;
228 case socket_io_parser_1.PacketType.BINARY_ACK:
229 this.onack(packet);
230 break;
231 case socket_io_parser_1.PacketType.DISCONNECT:
232 this.ondisconnect();
233 break;
234 case socket_io_parser_1.PacketType.CONNECT_ERROR:
235 this._onerror(new Error(packet.data));
236 }
237 }
238 /**
239 * Called upon event packet.
240 *
241 * @param {Packet} packet - packet object
242 * @private
243 */
244 onevent(packet) {
245 const args = packet.data || [];
246 debug("emitting event %j", args);
247 if (null != packet.id) {
248 debug("attaching ack callback to event");
249 args.push(this.ack(packet.id));
250 }
251 if (this._anyListeners && this._anyListeners.length) {
252 const listeners = this._anyListeners.slice();
253 for (const listener of listeners) {
254 listener.apply(this, args);
255 }
256 }
257 this.dispatch(args);
258 }
259 /**
260 * Produces an ack callback to emit with an event.
261 *
262 * @param {Number} id - packet id
263 * @private
264 */
265 ack(id) {
266 const self = this;
267 let sent = false;
268 return function () {
269 // prevent double callbacks
270 if (sent)
271 return;
272 const args = Array.prototype.slice.call(arguments);
273 debug("sending ack %j", args);
274 self.packet({
275 id: id,
276 type: socket_io_parser_1.PacketType.ACK,
277 data: args,
278 });
279 sent = true;
280 };
281 }
282 /**
283 * Called upon ack packet.
284 *
285 * @private
286 */
287 onack(packet) {
288 const ack = this.acks.get(packet.id);
289 if ("function" == typeof ack) {
290 debug("calling ack %s with %j", packet.id, packet.data);
291 ack.apply(this, packet.data);
292 this.acks.delete(packet.id);
293 }
294 else {
295 debug("bad ack %s", packet.id);
296 }
297 }
298 /**
299 * Called upon client disconnect packet.
300 *
301 * @private
302 */
303 ondisconnect() {
304 debug("got disconnect packet");
305 this._onclose("client namespace disconnect");
306 }
307 /**
308 * Handles a client error.
309 *
310 * @private
311 */
312 _onerror(err) {
313 if (this.listeners("error").length) {
314 this.emitReserved("error", err);
315 }
316 else {
317 console.error("Missing error handler on `socket`.");
318 console.error(err.stack);
319 }
320 }
321 /**
322 * Called upon closing. Called by `Client`.
323 *
324 * @param {String} reason
325 * @throw {Error} optional error object
326 *
327 * @private
328 */
329 _onclose(reason) {
330 if (!this.connected)
331 return this;
332 debug("closing socket - reason %s", reason);
333 this.emitReserved("disconnecting", reason);
334 this.leaveAll();
335 this.nsp._remove(this);
336 this.client._remove(this);
337 this.connected = false;
338 this.disconnected = true;
339 this.emitReserved("disconnect", reason);
340 return;
341 }
342 /**
343 * Produces an `error` packet.
344 *
345 * @param {Object} err - error object
346 *
347 * @private
348 */
349 _error(err) {
350 this.packet({ type: socket_io_parser_1.PacketType.CONNECT_ERROR, data: err });
351 }
352 /**
353 * Disconnects this client.
354 *
355 * @param {Boolean} close - if `true`, closes the underlying connection
356 * @return {Socket} self
357 *
358 * @public
359 */
360 disconnect(close = false) {
361 if (!this.connected)
362 return this;
363 if (close) {
364 this.client._disconnect();
365 }
366 else {
367 this.packet({ type: socket_io_parser_1.PacketType.DISCONNECT });
368 this._onclose("server namespace disconnect");
369 }
370 return this;
371 }
372 /**
373 * Sets the compress flag.
374 *
375 * @param {Boolean} compress - if `true`, compresses the sending data
376 * @return {Socket} self
377 * @public
378 */
379 compress(compress) {
380 this.flags.compress = compress;
381 return this;
382 }
383 /**
384 * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
385 * receive messages (because of network slowness or other issues, or because they’re connected through long polling
386 * and is in the middle of a request-response cycle).
387 *
388 * @return {Socket} self
389 * @public
390 */
391 get volatile() {
392 this.flags.volatile = true;
393 return this;
394 }
395 /**
396 * Sets a modifier for a subsequent event emission that the event data will only be broadcast to every sockets but the
397 * sender.
398 *
399 * @return {Socket} self
400 * @public
401 */
402 get broadcast() {
403 return this.newBroadcastOperator();
404 }
405 /**
406 * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
407 *
408 * @return {Socket} self
409 * @public
410 */
411 get local() {
412 return this.newBroadcastOperator().local;
413 }
414 /**
415 * Dispatch incoming event to socket listeners.
416 *
417 * @param {Array} event - event that will get emitted
418 * @private
419 */
420 dispatch(event) {
421 debug("dispatching an event %j", event);
422 this.run(event, (err) => {
423 process.nextTick(() => {
424 if (err) {
425 return this._onerror(err);
426 }
427 if (this.connected) {
428 super.emitUntyped.apply(this, event);
429 }
430 else {
431 debug("ignore packet received after disconnection");
432 }
433 });
434 });
435 }
436 /**
437 * Sets up socket middleware.
438 *
439 * @param {Function} fn - middleware function (event, next)
440 * @return {Socket} self
441 * @public
442 */
443 use(fn) {
444 this.fns.push(fn);
445 return this;
446 }
447 /**
448 * Executes the middleware for an incoming event.
449 *
450 * @param {Array} event - event that will get emitted
451 * @param {Function} fn - last fn call in the middleware
452 * @private
453 */
454 run(event, fn) {
455 const fns = this.fns.slice(0);
456 if (!fns.length)
457 return fn(null);
458 function run(i) {
459 fns[i](event, function (err) {
460 // upon error, short-circuit
461 if (err)
462 return fn(err);
463 // if no middleware left, summon callback
464 if (!fns[i + 1])
465 return fn(null);
466 // go on to next
467 run(i + 1);
468 });
469 }
470 run(0);
471 }
472 /**
473 * A reference to the request that originated the underlying Engine.IO Socket.
474 *
475 * @public
476 */
477 get request() {
478 return this.client.request;
479 }
480 /**
481 * A reference to the underlying Client transport connection (Engine.IO Socket object).
482 *
483 * @public
484 */
485 get conn() {
486 return this.client.conn;
487 }
488 /**
489 * @public
490 */
491 get rooms() {
492 return this.adapter.socketRooms(this.id) || new Set();
493 }
494 /**
495 * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
496 * callback.
497 *
498 * @param listener
499 * @public
500 */
501 onAny(listener) {
502 this._anyListeners = this._anyListeners || [];
503 this._anyListeners.push(listener);
504 return this;
505 }
506 /**
507 * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
508 * callback. The listener is added to the beginning of the listeners array.
509 *
510 * @param listener
511 * @public
512 */
513 prependAny(listener) {
514 this._anyListeners = this._anyListeners || [];
515 this._anyListeners.unshift(listener);
516 return this;
517 }
518 /**
519 * Removes the listener that will be fired when any event is emitted.
520 *
521 * @param listener
522 * @public
523 */
524 offAny(listener) {
525 if (!this._anyListeners) {
526 return this;
527 }
528 if (listener) {
529 const listeners = this._anyListeners;
530 for (let i = 0; i < listeners.length; i++) {
531 if (listener === listeners[i]) {
532 listeners.splice(i, 1);
533 return this;
534 }
535 }
536 }
537 else {
538 this._anyListeners = [];
539 }
540 return this;
541 }
542 /**
543 * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
544 * e.g. to remove listeners.
545 *
546 * @public
547 */
548 listenersAny() {
549 return this._anyListeners || [];
550 }
551 newBroadcastOperator() {
552 const flags = Object.assign({}, this.flags);
553 this.flags = {};
554 return new broadcast_operator_1.BroadcastOperator(this.adapter, new Set(), new Set([this.id]), flags);
555 }
556}
557exports.Socket = Socket;