index.js 18 KB

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