ws.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. /**
  2. * @file
  3. */
  4. import config from "config";
  5. import async from "async";
  6. import { WebSocketServer } from "ws";
  7. import { EventEmitter } from "events";
  8. import CoreClass from "../core";
  9. let WSModule;
  10. let AppModule;
  11. let CacheModule;
  12. let UtilsModule;
  13. let DBModule;
  14. let PunishmentsModule;
  15. class _WSModule extends CoreClass {
  16. // eslint-disable-next-line require-jsdoc
  17. constructor() {
  18. super("ws");
  19. WSModule = this;
  20. }
  21. /**
  22. * Initialises the ws module
  23. *
  24. * @returns {Promise} - returns promise (reject, resolve)
  25. */
  26. async initialize() {
  27. this.setStage(1);
  28. AppModule = this.moduleManager.modules.app;
  29. CacheModule = this.moduleManager.modules.cache;
  30. UtilsModule = this.moduleManager.modules.utils;
  31. DBModule = this.moduleManager.modules.db;
  32. PunishmentsModule = this.moduleManager.modules.punishments;
  33. this.actions = (await import("./actions")).default;
  34. this.userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
  35. this.setStage(2);
  36. this.SIDname = config.get("cookie.SIDname");
  37. // TODO: Check every 30s/, for all sockets, if they are still allowed to be in the rooms they are in, and on socket at all (permission changing/banning)
  38. const server = await AppModule.runJob("SERVER");
  39. this._io = new WebSocketServer({ server, path: "/ws" });
  40. this.rooms = {};
  41. return new Promise(resolve => {
  42. this.setStage(3);
  43. this._io.on("connection", async (socket, req) => {
  44. socket.dispatch = (...args) => socket.send(JSON.stringify(args));
  45. socket.actions = new EventEmitter();
  46. socket.actions.setMaxListeners(0);
  47. socket.listen = (target, cb) => socket.actions.addListener(target, args => cb(args));
  48. WSModule.runJob("HANDLE_WS_USE", { socket, req }).then(socket =>
  49. WSModule.runJob("HANDLE_WS_CONNECTION", { socket })
  50. );
  51. socket.isAlive = true;
  52. socket.on("pong", function heartbeat() {
  53. this.isAlive = true;
  54. });
  55. });
  56. const keepAliveInterval = setInterval(() => {
  57. this._io.clients.forEach(socket => {
  58. if (socket.isAlive === false) return socket.terminate();
  59. socket.isAlive = false;
  60. return socket.ping(() => {});
  61. });
  62. }, 45000);
  63. this._io.on("close", () => clearInterval(keepAliveInterval));
  64. this.setStage(4);
  65. return resolve();
  66. });
  67. }
  68. /**
  69. * Returns the websockets variable
  70. *
  71. * @returns {Promise} - returns a promise (resolve, reject)
  72. */
  73. WS() {
  74. return new Promise(resolve => resolve(WSModule._io));
  75. }
  76. /**
  77. * Obtains socket object for a specified socket id
  78. *
  79. * @param {object} payload - object containing the payload
  80. * @param {string} payload.socketId - the id of the socket
  81. * @returns {Promise} - returns promise (reject, resolve)
  82. */
  83. async SOCKET_FROM_SOCKET_ID(payload) {
  84. return new Promise(resolve => {
  85. const { clients } = WSModule._io;
  86. if (clients)
  87. // eslint-disable-next-line consistent-return
  88. clients.forEach(socket => {
  89. if (socket.session.socketId === payload.socketId) return resolve(socket);
  90. });
  91. // socket doesn't exist
  92. return resolve();
  93. });
  94. }
  95. /**
  96. * Gets all sockets for a specified session id
  97. *
  98. * @param {object} payload - object containing the payload
  99. * @param {string} payload.sessionId - user session id
  100. * @returns {Promise} - returns promise (reject, resolve)
  101. */
  102. async SOCKETS_FROM_SESSION_ID(payload) {
  103. return new Promise(resolve => {
  104. const { clients } = WSModule._io;
  105. const sockets = [];
  106. if (clients) {
  107. return async.each(
  108. Object.keys(clients),
  109. (id, next) => {
  110. const { session } = clients[id];
  111. if (session.sessionId === payload.sessionId) sockets.push(session.sessionId);
  112. next();
  113. },
  114. () => resolve(sockets)
  115. );
  116. }
  117. return resolve();
  118. });
  119. }
  120. /**
  121. * Returns any sockets for a specific user
  122. *
  123. * @param {object} payload - object that contains the payload
  124. * @param {string} payload.userId - the user id
  125. * @returns {Promise} - returns promise (reject, resolve)
  126. */
  127. async SOCKETS_FROM_USER(payload) {
  128. return new Promise((resolve, reject) => {
  129. const sockets = [];
  130. return async.eachLimit(
  131. WSModule._io.clients,
  132. 1,
  133. (socket, next) => {
  134. const { sessionId } = socket.session;
  135. if (sessionId) {
  136. return CacheModule.runJob("HGET", { table: "sessions", key: sessionId }, this)
  137. .then(session => {
  138. if (session && session.userId === payload.userId) sockets.push(socket);
  139. next();
  140. })
  141. .catch(err => next(err));
  142. }
  143. return next();
  144. },
  145. err => {
  146. if (err) return reject(err);
  147. return resolve(sockets);
  148. }
  149. );
  150. });
  151. }
  152. /**
  153. * Returns any sockets from a specific ip address
  154. *
  155. * @param {object} payload - object that contains the payload
  156. * @param {string} payload.ip - the ip address in question
  157. * @returns {Promise} - returns promise (reject, resolve)
  158. */
  159. async SOCKETS_FROM_IP(payload) {
  160. return new Promise(resolve => {
  161. const { clients } = WSModule._io;
  162. const sockets = [];
  163. return async.each(
  164. Object.keys(clients),
  165. (id, next) => {
  166. const { session } = clients[id];
  167. CacheModule.runJob("HGET", { table: "sessions", key: session.sessionId }, this)
  168. .then(session => {
  169. if (session && clients[id].ip === payload.ip) sockets.push(clients[id]);
  170. next();
  171. })
  172. .catch(() => next());
  173. },
  174. () => resolve(sockets)
  175. );
  176. });
  177. }
  178. /**
  179. * Returns any sockets from a specific user without using redis/cache
  180. *
  181. * @param {object} payload - object that contains the payload
  182. * @param {string} payload.userId - the id of the user in question
  183. * @returns {Promise} - returns promise (reject, resolve)
  184. */
  185. async SOCKETS_FROM_USER_WITHOUT_CACHE(payload) {
  186. return new Promise(resolve => {
  187. const { clients } = WSModule._io;
  188. const sockets = [];
  189. if (clients) {
  190. return async.each(
  191. Object.keys(clients),
  192. (id, next) => {
  193. const { session } = clients[id];
  194. if (session.userId === payload.userId) sockets.push(clients[id]);
  195. next();
  196. },
  197. () => resolve(sockets)
  198. );
  199. }
  200. return resolve();
  201. });
  202. }
  203. /**
  204. * Allows a socket to leave any rooms they are connected to
  205. *
  206. * @param {object} payload - object that contains the payload
  207. * @param {string} payload.socketId - the id of the socket which should leave all their rooms
  208. * @returns {Promise} - returns promise (reject, resolve)
  209. */
  210. async SOCKET_LEAVE_ROOMS(payload) {
  211. return new Promise(resolve => {
  212. // filter out rooms that the user is in
  213. Object.keys(WSModule.rooms).forEach(room => {
  214. WSModule.rooms[room] = WSModule.rooms[room].filter(participant => participant !== payload.socketId);
  215. });
  216. return resolve();
  217. });
  218. }
  219. /**
  220. * Allows a socket to leave a specific room they are connected to
  221. *
  222. * @param {object} payload - object that contains the payload
  223. * @param {string} payload.socketId - the id of the socket which should leave a room
  224. * @param {string} payload.room - the room
  225. * @returns {Promise} - returns promise (reject, resolve)
  226. */
  227. async SOCKET_LEAVE_ROOM(payload) {
  228. return new Promise(resolve => {
  229. // filter out rooms that the user is in
  230. if (WSModule.rooms[payload.room])
  231. WSModule.rooms[payload.room] = WSModule.rooms[payload.room].filter(
  232. participant => participant !== payload.socketId
  233. );
  234. return resolve();
  235. });
  236. }
  237. /**
  238. * Allows a socket to join a specified room (this will remove them from any rooms they are currently in)
  239. *
  240. * @param {object} payload - object that contains the payload
  241. * @param {string} payload.socketId - the id of the socket which should join the room
  242. * @param {string} payload.room - the name of the room
  243. * @returns {Promise} - returns promise (reject, resolve)
  244. */
  245. async SOCKET_JOIN_ROOM(payload) {
  246. const { room, socketId } = payload;
  247. return new Promise(resolve => {
  248. // create room if it doesn't exist, and add socketId to array
  249. if (WSModule.rooms[room]) {
  250. if (!(socketId in WSModule.rooms[room])) WSModule.rooms[room].push(socketId);
  251. } else WSModule.rooms[room] = [socketId];
  252. return resolve();
  253. });
  254. }
  255. /**
  256. * Emits arguments to any sockets that are in a specified a room
  257. *
  258. * @param {object} payload - object that contains the payload
  259. * @param {string} payload.room - the name of the room to emit arguments
  260. * @param {object} payload.args - any arguments to be emitted to the sockets in the specific room
  261. * @returns {Promise} - returns promise (reject, resolve)
  262. */
  263. async EMIT_TO_ROOM(payload) {
  264. return new Promise(resolve => {
  265. // if the room exists
  266. if (WSModule.rooms[payload.room] && WSModule.rooms[payload.room].length > 0)
  267. return WSModule.rooms[payload.room].forEach(async socketId => {
  268. // get every socketId (and thus every socket) in the room, and dispatch to each
  269. const socket = await WSModule.runJob("SOCKET_FROM_SOCKET_ID", { socketId }, this);
  270. if (socket) socket.dispatch(...payload.args);
  271. return resolve();
  272. });
  273. return resolve();
  274. });
  275. }
  276. /**
  277. * Emits arguments to any sockets that are in specified rooms
  278. *
  279. * @param {object} payload - object that contains the payload
  280. * @param {Array} payload.rooms - array of strings with the name of each room e.g. ["station-page", "song.1234"]
  281. * @param {object} payload.args - any arguments to be emitted to the sockets in the specific room
  282. * @returns {Promise} - returns promise (reject, resolve)
  283. */
  284. async EMIT_TO_ROOMS(payload) {
  285. return new Promise(resolve =>
  286. async.each(
  287. payload.rooms,
  288. (room, next) => {
  289. WSModule.runJob("EMIT_TO_ROOM", { room, args: payload.args });
  290. return next();
  291. },
  292. () => resolve()
  293. )
  294. );
  295. }
  296. /**
  297. * Allows a socket to join a 'song' room
  298. *
  299. * @param {object} payload - object that contains the payload
  300. * @param {string} payload.socketId - the id of the socket which should join the room
  301. * @param {string} payload.room - the name of the room
  302. * @returns {Promise} - returns promise (reject, resolve)
  303. */
  304. async SOCKET_JOIN_SONG_ROOM(payload) {
  305. const { room, socketId } = payload;
  306. // leave any other song rooms the user is in
  307. await WSModule.runJob("SOCKETS_LEAVE_SONG_ROOMS", { sockets: [socketId] }, this);
  308. return new Promise(resolve => {
  309. // join the room
  310. if (WSModule.rooms[room]) WSModule.rooms[room].push(socketId);
  311. else WSModule.rooms[room] = [socketId];
  312. return resolve();
  313. });
  314. }
  315. /**
  316. * Allows multiple sockets to join a 'song' room
  317. *
  318. * @param {object} payload - object that contains the payload
  319. * @param {Array} payload.sockets - array of socketIds
  320. * @param {object} payload.room - the name of the room
  321. * @returns {Promise} - returns promise (reject, resolve)
  322. */
  323. SOCKETS_JOIN_SONG_ROOM(payload) {
  324. return new Promise(resolve => {
  325. Promise.allSettled(
  326. payload.sockets.map(async socketId => {
  327. await WSModule.runJob("SOCKET_JOIN_SONG_ROOM", { socketId, room: payload.room }, this);
  328. })
  329. ).then(() => resolve());
  330. });
  331. }
  332. /**
  333. * Allows multiple sockets to leave any 'song' rooms they are in
  334. *
  335. * @param {object} payload - object that contains the payload
  336. * @param {Array} payload.sockets - array of socketIds
  337. * @returns {Promise} - returns promise (reject, resolve)
  338. */
  339. SOCKETS_LEAVE_SONG_ROOMS(payload) {
  340. return new Promise(resolve =>
  341. Promise.allSettled(
  342. payload.sockets.map(async socketId => {
  343. const rooms = await WSModule.runJob("GET_ROOMS_FOR_SOCKET", { socketId }, this);
  344. rooms.forEach(room => {
  345. if (room.indexOf("song.") !== -1)
  346. WSModule.rooms[room] = WSModule.rooms[room].filter(participant => participant !== socketId);
  347. });
  348. })
  349. ).then(() => resolve())
  350. );
  351. }
  352. /**
  353. * Gets any sockets connected to a room
  354. *
  355. * @param {object} payload - object that contains the payload
  356. * @param {string} payload.room - the name of the room
  357. * @returns {Promise} - returns promise (reject, resolve)
  358. */
  359. async GET_SOCKETS_FOR_ROOM(payload) {
  360. return new Promise(resolve => {
  361. if (WSModule.rooms[payload.room]) return resolve(WSModule.rooms[payload.room]);
  362. return resolve([]);
  363. });
  364. }
  365. /**
  366. * Gets any rooms a socket is connected to
  367. *
  368. * @param {object} payload - object that contains the payload
  369. * @param {string} payload.socketId - the id of the socket to check the rooms for
  370. * @returns {Promise} - returns promise (reject, resolve)
  371. */
  372. async GET_ROOMS_FOR_SOCKET(payload) {
  373. return new Promise(resolve => {
  374. const rooms = [];
  375. Object.keys(WSModule.rooms).forEach(room => {
  376. if (WSModule.rooms[room].includes(payload.socketId)) rooms.push(room);
  377. });
  378. return resolve(rooms);
  379. });
  380. }
  381. /**
  382. * Handles use of websockets
  383. *
  384. * @param {object} payload - object that contains the payload
  385. * @returns {Promise} - returns promise (reject, resolve)
  386. */
  387. async HANDLE_WS_USE(payload) {
  388. return new Promise(resolve => {
  389. const { socket, req } = payload;
  390. let SID = "";
  391. socket.ip = req.headers["x-forwarded-for"] || "0..0.0";
  392. return async.waterfall(
  393. [
  394. next => {
  395. if (!req.headers.cookie) return next("No cookie exists yet.");
  396. return UtilsModule.runJob("PARSE_COOKIES", { cookieString: req.headers.cookie }, this).then(
  397. res => {
  398. SID = res[WSModule.SIDname];
  399. next(null);
  400. }
  401. );
  402. },
  403. next => {
  404. if (!SID) return next("No SID.");
  405. return next();
  406. },
  407. // see if session exists for cookie
  408. next => {
  409. CacheModule.runJob("HGET", { table: "sessions", key: SID }, this)
  410. .then(session => next(null, session))
  411. .catch(next);
  412. },
  413. (session, next) => {
  414. if (!session) return next("No session found.");
  415. session.refreshDate = Date.now();
  416. socket.session = session;
  417. return CacheModule.runJob(
  418. "HSET",
  419. {
  420. table: "sessions",
  421. key: SID,
  422. value: session
  423. },
  424. this
  425. ).then(session => next(null, session));
  426. },
  427. (res, next) => {
  428. // check if a session's user / IP is banned
  429. PunishmentsModule.runJob("GET_PUNISHMENTS", {}, this)
  430. .then(punishments => {
  431. const isLoggedIn = !!(socket.session && socket.session.refreshDate);
  432. const userId = isLoggedIn ? socket.session.userId : null;
  433. const banishment = {
  434. banned: false,
  435. ban: 0
  436. };
  437. punishments.forEach(punishment => {
  438. if (punishment.expiresAt > banishment.ban) banishment.ban = punishment;
  439. if (punishment.type === "banUserId" && isLoggedIn && punishment.value === userId)
  440. banishment.banned = true;
  441. if (punishment.type === "banUserIp" && punishment.value === socket.ip)
  442. banishment.banned = true;
  443. });
  444. socket.banishment = banishment;
  445. next();
  446. })
  447. .catch(() => next());
  448. }
  449. ],
  450. () => {
  451. if (!socket.session) socket.session = { socketId: req.headers["sec-websocket-key"] };
  452. else socket.session.socketId = req.headers["sec-websocket-key"];
  453. resolve(socket);
  454. }
  455. );
  456. });
  457. }
  458. /**
  459. * Handles a websocket connection
  460. *
  461. * @param {object} payload - object that contains the payload
  462. * @param {object} payload.socket - socket itself
  463. * @returns {Promise} - returns promise (reject, resolve)
  464. */
  465. async HANDLE_WS_CONNECTION(payload) {
  466. return new Promise(resolve => {
  467. const { socket } = payload;
  468. let sessionInfo = "";
  469. if (socket.session.sessionId) sessionInfo = ` UserID: ${socket.session.userId}.`;
  470. // if session is banned
  471. if (socket.banishment && socket.banishment.banned) {
  472. WSModule.log(
  473. "INFO",
  474. "IO_BANNED_CONNECTION",
  475. `A user tried to connect, but is currently banned. IP: ${socket.ip}.${sessionInfo}`
  476. );
  477. socket.dispatch("keep.event:banned", { data: { ban: socket.banishment.ban } });
  478. return socket.close(); // close socket connection
  479. }
  480. WSModule.log("INFO", "IO_CONNECTION", `User connected. IP: ${socket.ip}.${sessionInfo}`);
  481. // catch when the socket has been disconnected
  482. socket.on("close", async () => {
  483. if (socket.session.sessionId) sessionInfo = ` UserID: ${socket.session.userId}.`;
  484. WSModule.log("INFO", "IO_DISCONNECTION", `User disconnected. IP: ${socket.ip}.${sessionInfo}`);
  485. // leave all rooms when a socket connection is closed (to prevent rooms object building up)
  486. await WSModule.runJob("SOCKET_LEAVE_ROOMS", { socketId: socket.session.socketId });
  487. });
  488. // catch errors on the socket
  489. socket.onerror = error => {
  490. console.error("SOCKET ERROR: ", error);
  491. };
  492. if (socket.session.sessionId) {
  493. CacheModule.runJob("HGET", {
  494. table: "sessions",
  495. key: socket.session.sessionId
  496. })
  497. .then(session => {
  498. if (session && session.userId) {
  499. WSModule.userModel.findOne({ _id: session.userId }, (err, user) => {
  500. if (err || !user) return socket.dispatch("ready", { data: { loggedIn: false } });
  501. let role = "";
  502. let username = "";
  503. let userId = "";
  504. let email = "";
  505. if (user) {
  506. role = user.role;
  507. username = user.username;
  508. email = user.email.address;
  509. userId = session.userId;
  510. }
  511. return socket.dispatch("ready", {
  512. data: { loggedIn: true, role, username, userId, email }
  513. });
  514. });
  515. } else socket.dispatch("ready", { data: { loggedIn: false } });
  516. })
  517. .catch(() => socket.dispatch("ready", { data: { loggedIn: false } }));
  518. } else socket.dispatch("ready", { data: { loggedIn: false } });
  519. socket.onmessage = message => {
  520. const data = JSON.parse(message.data);
  521. if (data.length === 0) return socket.dispatch("ERROR", "Not enough arguments specified.");
  522. if (typeof data[0] !== "string") return socket.dispatch("ERROR", "First argument must be a string.");
  523. const namespaceAction = data[0];
  524. if (
  525. !namespaceAction ||
  526. namespaceAction.indexOf(".") === -1 ||
  527. namespaceAction.indexOf(".") !== namespaceAction.lastIndexOf(".")
  528. )
  529. return socket.dispatch("ERROR", "Invalid first argument");
  530. const namespace = data[0].split(".")[0];
  531. const action = data[0].split(".")[1];
  532. if (!namespace) return socket.dispatch("ERROR", "Invalid namespace.");
  533. if (!action) return socket.dispatch("ERROR", "Invalid action.");
  534. if (!WSModule.actions[namespace]) return socket.dispatch("ERROR", "Namespace not found.");
  535. if (!WSModule.actions[namespace][action]) return socket.dispatch("ERROR", "Action not found.");
  536. if (data[data.length - 1].CB_REF) {
  537. const { CB_REF } = data[data.length - 1];
  538. data.pop();
  539. return socket.actions.emit(data.shift(0), [...data, res => socket.dispatch("CB_REF", CB_REF, res)]);
  540. }
  541. return socket.actions.emit(data.shift(0), data);
  542. };
  543. // have the socket listen for each action
  544. Object.keys(WSModule.actions).forEach(namespace => {
  545. Object.keys(WSModule.actions[namespace]).forEach(action => {
  546. // the full name of the action
  547. const name = `${namespace}.${action}`;
  548. // listen for this action to be called
  549. socket.listen(name, async args =>
  550. WSModule.runJob("RUN_ACTION", { socket, namespace, action, args })
  551. );
  552. });
  553. });
  554. return resolve();
  555. });
  556. }
  557. /**
  558. * Runs an action
  559. *
  560. * @param {object} payload - object that contains the payload
  561. * @returns {Promise} - returns promise (reject, resolve)
  562. */
  563. async RUN_ACTION(payload) {
  564. return new Promise((resolve, reject) => {
  565. const { socket, namespace, action, args } = payload;
  566. // the full name of the action
  567. const name = `${namespace}.${action}`;
  568. let cb = args[args.length - 1];
  569. if (typeof cb !== "function")
  570. cb = () => {
  571. WSModule.log("INFO", "IO_MODULE", `There was no callback provided for ${name}.`);
  572. };
  573. else args.pop();
  574. WSModule.log("INFO", "IO_ACTION", `A user executed an action. Action: ${namespace}.${action}.`);
  575. // load the session from the cache
  576. new Promise(resolve => {
  577. if (socket.session.sessionId)
  578. CacheModule.runJob("HGET", {
  579. table: "sessions",
  580. key: socket.session.sessionId
  581. })
  582. .then(session => {
  583. // make sure the sockets sessionId isn't set if there is no session
  584. if (socket.session.sessionId && session === null) delete socket.session.sessionId;
  585. resolve();
  586. })
  587. .catch(() => {
  588. if (typeof cb === "function")
  589. cb({
  590. status: "error",
  591. message: "An error occurred while obtaining your session"
  592. });
  593. reject(new Error("An error occurred while obtaining the session"));
  594. });
  595. else resolve();
  596. })
  597. .then(() => {
  598. // call the job that calls the action, passing it the session, and the arguments the websocket passed us
  599. WSModule.runJob("RUN_ACTION2", { session: socket.session, namespace, action, args }, this)
  600. .then(response => {
  601. cb(response);
  602. resolve();
  603. })
  604. .catch(err => {
  605. if (typeof cb === "function")
  606. cb({
  607. status: "error",
  608. message: "An error occurred while executing the specified action."
  609. });
  610. reject(err);
  611. WSModule.log(
  612. "ERROR",
  613. "IO_ACTION_ERROR",
  614. `Some type of exception occurred in the action ${namespace}.${action}. Error message: ${err.message}`
  615. );
  616. });
  617. })
  618. .catch(reject);
  619. });
  620. }
  621. /**
  622. * Runs an action
  623. *
  624. * @param {object} payload - object that contains the payload
  625. * @returns {Promise} - returns promise (reject, resolve)
  626. */
  627. async RUN_ACTION2(payload) {
  628. return new Promise((resolve, reject) => {
  629. const { session, namespace, action, args } = payload;
  630. try {
  631. // call the the action, passing it the session, and the arguments the websocket passed us
  632. WSModule.actions[namespace][action].apply(
  633. this,
  634. [session].concat(args).concat([
  635. result => {
  636. WSModule.log(
  637. "INFO",
  638. "RUN_ACTION2",
  639. `Response to action. Action: ${namespace}.${action}. Response status: ${result.status}`
  640. );
  641. resolve(result);
  642. }
  643. ])
  644. );
  645. } catch (err) {
  646. reject(err);
  647. WSModule.log(
  648. "ERROR",
  649. "IO_ACTION_ERROR",
  650. `Some type of exception occurred in the action ${namespace}.${action}. Error message: ${err.message}`
  651. );
  652. }
  653. });
  654. }
  655. }
  656. export default new _WSModule();