1 | ;
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | exports.Socket = exports.RESERVED_EVENTS = void 0;
|
7 | const socket_io_parser_1 = require("socket.io-parser");
|
8 | const url = require("url");
|
9 | const debug_1 = __importDefault(require("debug"));
|
10 | const typed_events_1 = require("./typed-events");
|
11 | const base64id_1 = __importDefault(require("base64id"));
|
12 | const broadcast_operator_1 = require("./broadcast-operator");
|
13 | const debug = (0, debug_1.default)("socket.io:socket");
|
14 | exports.RESERVED_EVENTS = new Set([
|
15 | "connect",
|
16 | "connect_error",
|
17 | "disconnect",
|
18 | "disconnecting",
|
19 | "newListener",
|
20 | "removeListener",
|
21 | ]);
|
22 | class 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 | }
|
557 | exports.Socket = Socket;
|