io.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. const CoreClass = require("../core.js");
  2. const socketio = require("socket.io");
  3. const async = require("async");
  4. const config = require("config");
  5. class IOModule extends CoreClass {
  6. constructor() {
  7. super("io");
  8. }
  9. initialize() {
  10. return new Promise(async (resolve, reject) => {
  11. this.setStage(1);
  12. const app = this.moduleManager.modules["app"],
  13. cache = this.moduleManager.modules["cache"],
  14. utils = this.moduleManager.modules["utils"],
  15. db = this.moduleManager.modules["db"],
  16. punishments = this.moduleManager.modules["punishments"];
  17. const actions = require("./actions");
  18. this.setStage(2);
  19. const SIDname = config.get("cookie.SIDname");
  20. // TODO: Check every 30s/60s, for all sockets, if they are still allowed to be in the rooms they are in, and on socket at all (permission changing/banning)
  21. this._io = socketio(await app.runJob("SERVER", {}));
  22. this.setStage(3);
  23. this._io.use(async (socket, next) => {
  24. if (this.getStatus() !== "READY") {
  25. this.log(
  26. "INFO",
  27. "IO_REJECTED_CONNECTION",
  28. `A user tried to connect, but the IO module is currently not ready. IP: ${socket.ip}.${sessionInfo}`
  29. );
  30. return socket.disconnect(true);
  31. }
  32. let SID;
  33. socket.ip =
  34. socket.request.headers["x-forwarded-for"] || "0.0.0.0";
  35. async.waterfall(
  36. [
  37. (next) => {
  38. utils
  39. .runJob("PARSE_COOKIES", {
  40. cookieString: socket.request.headers.cookie,
  41. })
  42. .then((res) => {
  43. SID = res[SIDname];
  44. next(null);
  45. });
  46. },
  47. (next) => {
  48. if (!SID) return next("No SID.");
  49. next();
  50. },
  51. (next) => {
  52. cache
  53. .runJob("HGET", { table: "sessions", key: SID })
  54. .then((session) => {
  55. next(null, session);
  56. });
  57. },
  58. (session, next) => {
  59. if (!session) return next("No session found.");
  60. session.refreshDate = Date.now();
  61. socket.session = session;
  62. cache
  63. .runJob("HSET", {
  64. table: "sessions",
  65. key: SID,
  66. value: session,
  67. })
  68. .then((session) => {
  69. next(null, session);
  70. });
  71. },
  72. (res, next) => {
  73. // check if a session's user / IP is banned
  74. punishments
  75. .runJob("GET_PUNISHMENTS", {})
  76. .then((punishments) => {
  77. const isLoggedIn = !!(
  78. socket.session &&
  79. socket.session.refreshDate
  80. );
  81. const userId = isLoggedIn
  82. ? socket.session.userId
  83. : null;
  84. let banishment = { banned: false, ban: 0 };
  85. punishments.forEach((punishment) => {
  86. if (
  87. punishment.expiresAt >
  88. banishment.ban
  89. )
  90. banishment.ban = punishment;
  91. if (
  92. punishment.type === "banUserId" &&
  93. isLoggedIn &&
  94. punishment.value === userId
  95. )
  96. banishment.banned = true;
  97. if (
  98. punishment.type === "banUserIp" &&
  99. punishment.value === socket.ip
  100. )
  101. banishment.banned = true;
  102. });
  103. socket.banishment = banishment;
  104. next();
  105. })
  106. .catch(() => {
  107. next();
  108. });
  109. },
  110. ],
  111. () => {
  112. if (!socket.session)
  113. socket.session = { socketId: socket.id };
  114. else socket.session.socketId = socket.id;
  115. next();
  116. }
  117. );
  118. });
  119. this.setStage(4);
  120. this._io.on("connection", async (socket) => {
  121. if (this.getStatus() !== "READY") {
  122. this.log(
  123. "INFO",
  124. "IO_REJECTED_CONNECTION",
  125. `A user tried to connect, but the IO module is currently not ready. IP: ${socket.ip}.${sessionInfo}`
  126. );
  127. return socket.disconnect(true);
  128. }
  129. let sessionInfo = "";
  130. if (socket.session.sessionId)
  131. sessionInfo = ` UserID: ${socket.session.userId}.`;
  132. // if session is banned
  133. if (socket.banishment && socket.banishment.banned) {
  134. this.log(
  135. "INFO",
  136. "IO_BANNED_CONNECTION",
  137. `A user tried to connect, but is currently banned. IP: ${socket.ip}.${sessionInfo}`
  138. );
  139. socket.emit("keep.event:banned", socket.banishment.ban);
  140. socket.disconnect(true);
  141. } else {
  142. this.log(
  143. "INFO",
  144. "IO_CONNECTION",
  145. `User connected. IP: ${socket.ip}.${sessionInfo}`
  146. );
  147. // catch when the socket has been disconnected
  148. socket.on("disconnect", () => {
  149. if (socket.session.sessionId)
  150. sessionInfo = ` UserID: ${socket.session.userId}.`;
  151. this.log(
  152. "INFO",
  153. "IO_DISCONNECTION",
  154. `User disconnected. IP: ${socket.ip}.${sessionInfo}`
  155. );
  156. });
  157. socket.use((data, next) => {
  158. if (data.length === 0)
  159. return next(
  160. new Error("Not enough arguments specified.")
  161. );
  162. else if (typeof data[0] !== "string")
  163. return next(
  164. new Error("First argument must be a string.")
  165. );
  166. else {
  167. const namespaceAction = data[0];
  168. if (
  169. !namespaceAction ||
  170. namespaceAction.indexOf(".") === -1 ||
  171. namespaceAction.indexOf(".") !==
  172. namespaceAction.lastIndexOf(".")
  173. )
  174. return next(
  175. new Error("Invalid first argument")
  176. );
  177. const namespace = data[0].split(".")[0];
  178. const action = data[0].split(".")[1];
  179. if (!namespace)
  180. return next(new Error("Invalid namespace."));
  181. else if (!action)
  182. return next(new Error("Invalid action."));
  183. else if (!actions[namespace])
  184. return next(new Error("Namespace not found."));
  185. else if (!actions[namespace][action])
  186. return next(new Error("Action not found."));
  187. else return next();
  188. }
  189. });
  190. // catch errors on the socket (internal to socket.io)
  191. socket.on("error", console.error);
  192. // have the socket listen for each action
  193. Object.keys(actions).forEach((namespace) => {
  194. Object.keys(actions[namespace]).forEach((action) => {
  195. // the full name of the action
  196. let name = `${namespace}.${action}`;
  197. // listen for this action to be called
  198. socket.on(name, async (...args) => {
  199. let cb = args[args.length - 1];
  200. if (typeof cb !== "function")
  201. cb = () => {
  202. this.this.log(
  203. "INFO",
  204. "IO_MODULE",
  205. `There was no callback provided for ${name}.`
  206. );
  207. };
  208. else args.pop();
  209. if (this.getStatus() !== "READY") {
  210. this.log(
  211. "INFO",
  212. "IO_REJECTED_ACTION",
  213. `A user tried to execute an action, but the IO module is currently not ready. Action: ${namespace}.${action}.`
  214. );
  215. return;
  216. } else {
  217. this.log(
  218. "INFO",
  219. "IO_ACTION",
  220. `A user executed an action. Action: ${namespace}.${action}.`
  221. );
  222. }
  223. // load the session from the cache
  224. cache
  225. .runJob("HGET", {
  226. table: "sessions",
  227. key: socket.session.sessionId,
  228. })
  229. .then((session) => {
  230. // make sure the sockets sessionId isn't set if there is no session
  231. if (
  232. socket.session.sessionId &&
  233. session === null
  234. )
  235. delete socket.session.sessionId;
  236. try {
  237. // call the action, passing it the session, and the arguments socket.io passed us
  238. actions[namespace][action].apply(
  239. null,
  240. [socket.session]
  241. .concat(args)
  242. .concat([
  243. (result) => {
  244. this.log(
  245. "INFO",
  246. "IO_ACTION",
  247. `Response to action. Action: ${namespace}.${action}. Response status: ${result.status}`
  248. );
  249. // respond to the socket with our message
  250. if (
  251. typeof cb ===
  252. "function"
  253. )
  254. return cb(
  255. result
  256. );
  257. },
  258. ])
  259. );
  260. } catch (err) {
  261. this.log(
  262. "ERROR",
  263. "IO_ACTION_ERROR",
  264. `Some type of exception occurred in the action ${namespace}.${action}. Error message: ${err.message}`
  265. );
  266. if (typeof cb === "function")
  267. return cb({
  268. status: "error",
  269. message:
  270. "An error occurred while executing the specified action.",
  271. });
  272. }
  273. })
  274. .catch((err) => {
  275. if (typeof cb === "function")
  276. return cb({
  277. status: "error",
  278. message:
  279. "An error occurred while obtaining your session",
  280. });
  281. });
  282. });
  283. });
  284. });
  285. if (socket.session.sessionId) {
  286. cache
  287. .runJob("HGET", {
  288. table: "sessions",
  289. key: socket.session.sessionId,
  290. })
  291. .then((session) => {
  292. if (session && session.userId) {
  293. db.runJob("GET_MODEL", {
  294. modelName: "user",
  295. }).then((userModel) => {
  296. userModel.findOne(
  297. { _id: session.userId },
  298. (err, user) => {
  299. if (err || !user)
  300. return socket.emit(
  301. "ready",
  302. false
  303. );
  304. let role = "";
  305. let username = "";
  306. let userId = "";
  307. if (user) {
  308. role = user.role;
  309. username = user.username;
  310. userId = session.userId;
  311. }
  312. socket.emit(
  313. "ready",
  314. true,
  315. role,
  316. username,
  317. userId
  318. );
  319. }
  320. );
  321. });
  322. } else socket.emit("ready", false);
  323. })
  324. .catch((err) => {
  325. socket.emit("ready", false);
  326. });
  327. } else socket.emit("ready", false);
  328. }
  329. });
  330. this.setStage(5);
  331. resolve();
  332. });
  333. }
  334. IO() {
  335. return new Promise((resolve, reject) => {
  336. resolve(this._io);
  337. });
  338. }
  339. }
  340. module.exports = new IOModule();