UNPKG

16.3 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21var __importDefault = (this && this.__importDefault) || function (mod) {
22 return (mod && mod.__esModule) ? mod : { "default": mod };
23};
24Object.defineProperty(exports, "__esModule", { value: true });
25exports.Namespace = exports.Socket = exports.Server = void 0;
26const http = require("http");
27const fs_1 = require("fs");
28const zlib_1 = require("zlib");
29const accepts = require("accepts");
30const stream_1 = require("stream");
31const path = require("path");
32const engine_io_1 = require("engine.io");
33const client_1 = require("./client");
34const events_1 = require("events");
35const namespace_1 = require("./namespace");
36Object.defineProperty(exports, "Namespace", { enumerable: true, get: function () { return namespace_1.Namespace; } });
37const parent_namespace_1 = require("./parent-namespace");
38const socket_io_adapter_1 = require("socket.io-adapter");
39const parser = __importStar(require("socket.io-parser"));
40const debug_1 = __importDefault(require("debug"));
41const socket_1 = require("./socket");
42Object.defineProperty(exports, "Socket", { enumerable: true, get: function () { return socket_1.Socket; } });
43const typed_events_1 = require("./typed-events");
44const debug = (0, debug_1.default)("socket.io:server");
45const clientVersion = require("../package.json").version;
46const dotMapRegex = /\.map/;
47class Server extends typed_events_1.StrictEventEmitter {
48 constructor(srv, opts = {}) {
49 super();
50 /**
51 * @private
52 */
53 this._nsps = new Map();
54 this.parentNsps = new Map();
55 if ("object" === typeof srv &&
56 srv instanceof Object &&
57 !srv.listen) {
58 opts = srv;
59 srv = undefined;
60 }
61 this.path(opts.path || "/socket.io");
62 this.connectTimeout(opts.connectTimeout || 45000);
63 this.serveClient(false !== opts.serveClient);
64 this._parser = opts.parser || parser;
65 this.encoder = new this._parser.Encoder();
66 this.adapter(opts.adapter || socket_io_adapter_1.Adapter);
67 this.sockets = this.of("/");
68 this.opts = opts;
69 if (srv || typeof srv == "number")
70 this.attach(srv);
71 }
72 serveClient(v) {
73 if (!arguments.length)
74 return this._serveClient;
75 this._serveClient = v;
76 return this;
77 }
78 /**
79 * Executes the middleware for an incoming namespace not already created on the server.
80 *
81 * @param name - name of incoming namespace
82 * @param auth - the auth parameters
83 * @param fn - callback
84 *
85 * @private
86 */
87 _checkNamespace(name, auth, fn) {
88 if (this.parentNsps.size === 0)
89 return fn(false);
90 const keysIterator = this.parentNsps.keys();
91 const run = () => {
92 const nextFn = keysIterator.next();
93 if (nextFn.done) {
94 return fn(false);
95 }
96 nextFn.value(name, auth, (err, allow) => {
97 if (err || !allow) {
98 run();
99 }
100 else {
101 const namespace = this.parentNsps
102 .get(nextFn.value)
103 .createChild(name);
104 // @ts-ignore
105 this.sockets.emitReserved("new_namespace", namespace);
106 fn(namespace);
107 }
108 });
109 };
110 run();
111 }
112 path(v) {
113 if (!arguments.length)
114 return this._path;
115 this._path = v.replace(/\/$/, "");
116 const escapedPath = this._path.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
117 this.clientPathRegex = new RegExp("^" +
118 escapedPath +
119 "/socket\\.io(\\.msgpack|\\.esm)?(\\.min)?\\.js(\\.map)?(?:\\?|$)");
120 return this;
121 }
122 connectTimeout(v) {
123 if (v === undefined)
124 return this._connectTimeout;
125 this._connectTimeout = v;
126 return this;
127 }
128 adapter(v) {
129 if (!arguments.length)
130 return this._adapter;
131 this._adapter = v;
132 for (const nsp of this._nsps.values()) {
133 nsp._initAdapter();
134 }
135 return this;
136 }
137 /**
138 * Attaches socket.io to a server or port.
139 *
140 * @param srv - server or port
141 * @param opts - options passed to engine.io
142 * @return self
143 * @public
144 */
145 listen(srv, opts = {}) {
146 return this.attach(srv, opts);
147 }
148 /**
149 * Attaches socket.io to a server or port.
150 *
151 * @param srv - server or port
152 * @param opts - options passed to engine.io
153 * @return self
154 * @public
155 */
156 attach(srv, opts = {}) {
157 if ("function" == typeof srv) {
158 const msg = "You are trying to attach socket.io to an express " +
159 "request handler function. Please pass a http.Server instance.";
160 throw new Error(msg);
161 }
162 // handle a port as a string
163 if (Number(srv) == srv) {
164 srv = Number(srv);
165 }
166 if ("number" == typeof srv) {
167 debug("creating http server and binding to %d", srv);
168 const port = srv;
169 srv = http.createServer((req, res) => {
170 res.writeHead(404);
171 res.end();
172 });
173 srv.listen(port);
174 }
175 // merge the options passed to the Socket.IO server
176 Object.assign(opts, this.opts);
177 // set engine.io path to `/socket.io`
178 opts.path = opts.path || this._path;
179 this.initEngine(srv, opts);
180 return this;
181 }
182 /**
183 * Initialize engine
184 *
185 * @param srv - the server to attach to
186 * @param opts - options passed to engine.io
187 * @private
188 */
189 initEngine(srv, opts) {
190 // initialize engine
191 debug("creating engine.io instance with opts %j", opts);
192 this.eio = (0, engine_io_1.attach)(srv, opts);
193 // attach static file serving
194 if (this._serveClient)
195 this.attachServe(srv);
196 // Export http server
197 this.httpServer = srv;
198 // bind to engine events
199 this.bind(this.eio);
200 }
201 /**
202 * Attaches the static file serving.
203 *
204 * @param srv http server
205 * @private
206 */
207 attachServe(srv) {
208 debug("attaching client serving req handler");
209 const evs = srv.listeners("request").slice(0);
210 srv.removeAllListeners("request");
211 srv.on("request", (req, res) => {
212 if (this.clientPathRegex.test(req.url)) {
213 this.serve(req, res);
214 }
215 else {
216 for (let i = 0; i < evs.length; i++) {
217 evs[i].call(srv, req, res);
218 }
219 }
220 });
221 }
222 /**
223 * Handles a request serving of client source and map
224 *
225 * @param req
226 * @param res
227 * @private
228 */
229 serve(req, res) {
230 const filename = req.url.replace(this._path, "").replace(/\?.*$/, "");
231 const isMap = dotMapRegex.test(filename);
232 const type = isMap ? "map" : "source";
233 // Per the standard, ETags must be quoted:
234 // https://tools.ietf.org/html/rfc7232#section-2.3
235 const expectedEtag = '"' + clientVersion + '"';
236 const weakEtag = "W/" + expectedEtag;
237 const etag = req.headers["if-none-match"];
238 if (etag) {
239 if (expectedEtag === etag || weakEtag === etag) {
240 debug("serve client %s 304", type);
241 res.writeHead(304);
242 res.end();
243 return;
244 }
245 }
246 debug("serve client %s", type);
247 res.setHeader("Cache-Control", "public, max-age=0");
248 res.setHeader("Content-Type", "application/" + (isMap ? "json" : "javascript"));
249 res.setHeader("ETag", expectedEtag);
250 Server.sendFile(filename, req, res);
251 }
252 /**
253 * @param filename
254 * @param req
255 * @param res
256 * @private
257 */
258 static sendFile(filename, req, res) {
259 const readStream = (0, fs_1.createReadStream)(path.join(__dirname, "../client-dist/", filename));
260 const encoding = accepts(req).encodings(["br", "gzip", "deflate"]);
261 const onError = (err) => {
262 if (err) {
263 res.end();
264 }
265 };
266 switch (encoding) {
267 case "br":
268 res.writeHead(200, { "content-encoding": "br" });
269 readStream.pipe((0, zlib_1.createBrotliCompress)()).pipe(res);
270 (0, stream_1.pipeline)(readStream, (0, zlib_1.createBrotliCompress)(), res, onError);
271 break;
272 case "gzip":
273 res.writeHead(200, { "content-encoding": "gzip" });
274 (0, stream_1.pipeline)(readStream, (0, zlib_1.createGzip)(), res, onError);
275 break;
276 case "deflate":
277 res.writeHead(200, { "content-encoding": "deflate" });
278 (0, stream_1.pipeline)(readStream, (0, zlib_1.createDeflate)(), res, onError);
279 break;
280 default:
281 res.writeHead(200);
282 (0, stream_1.pipeline)(readStream, res, onError);
283 }
284 }
285 /**
286 * Binds socket.io to an engine.io instance.
287 *
288 * @param {engine.Server} engine engine.io (or compatible) server
289 * @return self
290 * @public
291 */
292 bind(engine) {
293 this.engine = engine;
294 this.engine.on("connection", this.onconnection.bind(this));
295 return this;
296 }
297 /**
298 * Called with each incoming transport connection.
299 *
300 * @param {engine.Socket} conn
301 * @return self
302 * @private
303 */
304 onconnection(conn) {
305 debug("incoming connection with id %s", conn.id);
306 const client = new client_1.Client(this, conn);
307 if (conn.protocol === 3) {
308 // @ts-ignore
309 client.connect("/");
310 }
311 return this;
312 }
313 /**
314 * Looks up a namespace.
315 *
316 * @param {String|RegExp|Function} name nsp name
317 * @param fn optional, nsp `connection` ev handler
318 * @public
319 */
320 of(name, fn) {
321 if (typeof name === "function" || name instanceof RegExp) {
322 const parentNsp = new parent_namespace_1.ParentNamespace(this);
323 debug("initializing parent namespace %s", parentNsp.name);
324 if (typeof name === "function") {
325 this.parentNsps.set(name, parentNsp);
326 }
327 else {
328 this.parentNsps.set((nsp, conn, next) => next(null, name.test(nsp)), parentNsp);
329 }
330 if (fn) {
331 // @ts-ignore
332 parentNsp.on("connect", fn);
333 }
334 return parentNsp;
335 }
336 if (String(name)[0] !== "/")
337 name = "/" + name;
338 let nsp = this._nsps.get(name);
339 if (!nsp) {
340 debug("initializing namespace %s", name);
341 nsp = new namespace_1.Namespace(this, name);
342 this._nsps.set(name, nsp);
343 if (name !== "/") {
344 // @ts-ignore
345 this.sockets.emitReserved("new_namespace", nsp);
346 }
347 }
348 if (fn)
349 nsp.on("connect", fn);
350 return nsp;
351 }
352 /**
353 * Closes server connection
354 *
355 * @param [fn] optional, called as `fn([err])` on error OR all conns closed
356 * @public
357 */
358 close(fn) {
359 for (const socket of this.sockets.sockets.values()) {
360 socket._onclose("server shutting down");
361 }
362 this.engine.close();
363 if (this.httpServer) {
364 this.httpServer.close(fn);
365 }
366 else {
367 fn && fn();
368 }
369 }
370 /**
371 * Sets up namespace middleware.
372 *
373 * @return self
374 * @public
375 */
376 use(fn) {
377 this.sockets.use(fn);
378 return this;
379 }
380 /**
381 * Targets a room when emitting.
382 *
383 * @param room
384 * @return self
385 * @public
386 */
387 to(room) {
388 return this.sockets.to(room);
389 }
390 /**
391 * Targets a room when emitting.
392 *
393 * @param room
394 * @return self
395 * @public
396 */
397 in(room) {
398 return this.sockets.in(room);
399 }
400 /**
401 * Excludes a room when emitting.
402 *
403 * @param name
404 * @return self
405 * @public
406 */
407 except(name) {
408 return this.sockets.except(name);
409 }
410 /**
411 * Sends a `message` event to all clients.
412 *
413 * @return self
414 * @public
415 */
416 send(...args) {
417 this.sockets.emit("message", ...args);
418 return this;
419 }
420 /**
421 * Sends a `message` event to all clients.
422 *
423 * @return self
424 * @public
425 */
426 write(...args) {
427 this.sockets.emit("message", ...args);
428 return this;
429 }
430 /**
431 * Emit a packet to other Socket.IO servers
432 *
433 * @param ev - the event name
434 * @param args - an array of arguments, which may include an acknowledgement callback at the end
435 * @public
436 */
437 serverSideEmit(ev, ...args) {
438 return this.sockets.serverSideEmit(ev, ...args);
439 }
440 /**
441 * Gets a list of socket ids.
442 *
443 * @public
444 */
445 allSockets() {
446 return this.sockets.allSockets();
447 }
448 /**
449 * Sets the compress flag.
450 *
451 * @param compress - if `true`, compresses the sending data
452 * @return self
453 * @public
454 */
455 compress(compress) {
456 return this.sockets.compress(compress);
457 }
458 /**
459 * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
460 * receive messages (because of network slowness or other issues, or because they’re connected through long polling
461 * and is in the middle of a request-response cycle).
462 *
463 * @return self
464 * @public
465 */
466 get volatile() {
467 return this.sockets.volatile;
468 }
469 /**
470 * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
471 *
472 * @return self
473 * @public
474 */
475 get local() {
476 return this.sockets.local;
477 }
478 /**
479 * Returns the matching socket instances
480 *
481 * @public
482 */
483 fetchSockets() {
484 return this.sockets.fetchSockets();
485 }
486 /**
487 * Makes the matching socket instances join the specified rooms
488 *
489 * @param room
490 * @public
491 */
492 socketsJoin(room) {
493 return this.sockets.socketsJoin(room);
494 }
495 /**
496 * Makes the matching socket instances leave the specified rooms
497 *
498 * @param room
499 * @public
500 */
501 socketsLeave(room) {
502 return this.sockets.socketsLeave(room);
503 }
504 /**
505 * Makes the matching socket instances disconnect
506 *
507 * @param close - whether to close the underlying connection
508 * @public
509 */
510 disconnectSockets(close = false) {
511 return this.sockets.disconnectSockets(close);
512 }
513}
514exports.Server = Server;
515/**
516 * Expose main namespace (/).
517 */
518const emitterMethods = Object.keys(events_1.EventEmitter.prototype).filter(function (key) {
519 return typeof events_1.EventEmitter.prototype[key] === "function";
520});
521emitterMethods.forEach(function (fn) {
522 Server.prototype[fn] = function () {
523 return this.sockets[fn].apply(this.sockets, arguments);
524 };
525});
526module.exports = (srv, opts) => new Server(srv, opts);
527module.exports.Server = Server;
528module.exports.Namespace = namespace_1.Namespace;
529module.exports.Socket = socket_1.Socket;