| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794 | 
							- /**
 
-  * @file
 
-  */
 
- import config from "config";
 
- import async from "async";
 
- import { WebSocketServer } from "ws";
 
- import { EventEmitter } from "events";
 
- import CoreClass from "../core";
 
- let WSModule;
 
- let AppModule;
 
- let CacheModule;
 
- let UtilsModule;
 
- let DBModule;
 
- let PunishmentsModule;
 
- class _WSModule extends CoreClass {
 
- 	// eslint-disable-next-line require-jsdoc
 
- 	constructor() {
 
- 		super("ws", { concurrency: 2 });
 
- 		WSModule = this;
 
- 	}
 
- 	/**
 
- 	 * Initialises the ws module
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async initialize() {
 
- 		this.setStage(1);
 
- 		AppModule = this.moduleManager.modules.app;
 
- 		CacheModule = this.moduleManager.modules.cache;
 
- 		UtilsModule = this.moduleManager.modules.utils;
 
- 		DBModule = this.moduleManager.modules.db;
 
- 		PunishmentsModule = this.moduleManager.modules.punishments;
 
- 		this.actions = (await import("./actions")).default;
 
- 		this.userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" });
 
- 		this.setStage(2);
 
- 		this.SIDname = config.get("cookie");
 
- 		// 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)
 
- 		const server = await AppModule.runJob("SERVER");
 
- 		this._io = new WebSocketServer({ server, path: "/ws" });
 
- 		this.rooms = {};
 
- 		return new Promise(resolve => {
 
- 			this.setStage(3);
 
- 			this._io.on("connection", async (socket, req) => {
 
- 				socket.dispatch = (...args) => socket.send(JSON.stringify(args));
 
- 				socket.actions = new EventEmitter();
 
- 				socket.actions.setMaxListeners(0);
 
- 				socket.listen = (target, cb) => socket.actions.addListener(target, args => cb(args));
 
- 				WSModule.runJob("HANDLE_WS_USE", { socket, req }).then(socket =>
 
- 					WSModule.runJob("HANDLE_WS_CONNECTION", { socket })
 
- 				);
 
- 				socket.isAlive = true;
 
- 				socket.on("pong", function heartbeat() {
 
- 					this.isAlive = true;
 
- 				});
 
- 			});
 
- 			const keepAliveInterval = setInterval(() => {
 
- 				this._io.clients.forEach(socket => {
 
- 					if (socket.isAlive === false) return socket.terminate();
 
- 					socket.isAlive = false;
 
- 					return socket.ping(() => {});
 
- 				});
 
- 			}, 45000);
 
- 			this._io.on("close", () => clearInterval(keepAliveInterval));
 
- 			this.setStage(4);
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Returns the websockets variable
 
- 	 * @returns {Promise} - returns a promise (resolve, reject)
 
- 	 */
 
- 	WS() {
 
- 		return new Promise(resolve => {
 
- 			resolve(WSModule._io);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Obtains socket object for a specified socket id
 
- 	 * @param {object} payload - object containing the payload
 
- 	 * @param {string} payload.socketId - the id of the socket
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async SOCKET_FROM_SOCKET_ID(payload) {
 
- 		return new Promise(resolve => {
 
- 			const { clients } = WSModule._io;
 
- 			if (clients)
 
- 				// eslint-disable-next-line consistent-return
 
- 				clients.forEach(socket => {
 
- 					if (socket.session.socketId === payload.socketId) return resolve(socket);
 
- 				});
 
- 			// socket doesn't exist
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Gets all sockets for a specified session id
 
- 	 * @param {object} payload - object containing the payload
 
- 	 * @param {string} payload.sessionId - user session id
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async SOCKETS_FROM_SESSION_ID(payload) {
 
- 		return new Promise(resolve => {
 
- 			const { clients } = WSModule._io;
 
- 			const sockets = [];
 
- 			if (clients) {
 
- 				async.each(
 
- 					Object.keys(clients),
 
- 					(id, next) => {
 
- 						const { session } = clients[id];
 
- 						if (session.sessionId === payload.sessionId) sockets.push(session.sessionId);
 
- 						next();
 
- 					},
 
- 					() => resolve(sockets)
 
- 				);
 
- 				return;
 
- 			}
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Returns any sockets for a specific user
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.userId - the user id
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async SOCKETS_FROM_USER(payload) {
 
- 		return new Promise((resolve, reject) => {
 
- 			const sockets = [];
 
- 			async.eachLimit(
 
- 				WSModule._io.clients,
 
- 				1,
 
- 				(socket, next) => {
 
- 					const { sessionId } = socket.session;
 
- 					if (sessionId) {
 
- 						return CacheModule.runJob("HGET", { table: "sessions", key: sessionId }, this)
 
- 							.then(session => {
 
- 								if (session && session.userId === payload.userId) sockets.push(socket);
 
- 								next();
 
- 							})
 
- 							.catch(err => next(err));
 
- 					}
 
- 					return next();
 
- 				},
 
- 				err => {
 
- 					if (err) return reject(err);
 
- 					return resolve(sockets);
 
- 				}
 
- 			);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Returns any sockets from a specific ip address
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.ip - the ip address in question
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async SOCKETS_FROM_IP(payload) {
 
- 		return new Promise(resolve => {
 
- 			const { clients } = WSModule._io;
 
- 			const sockets = [];
 
- 			async.each(
 
- 				Object.keys(clients),
 
- 				(id, next) => {
 
- 					const { session } = clients[id];
 
- 					CacheModule.runJob("HGET", { table: "sessions", key: session.sessionId }, this)
 
- 						.then(session => {
 
- 							if (session && clients[id].ip === payload.ip) sockets.push(clients[id]);
 
- 							next();
 
- 						})
 
- 						.catch(() => next());
 
- 				},
 
- 				() => resolve(sockets)
 
- 			);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Returns any sockets from a specific user without using redis/cache
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.userId - the id of the user in question
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async SOCKETS_FROM_USER_WITHOUT_CACHE(payload) {
 
- 		return new Promise(resolve => {
 
- 			const { clients } = WSModule._io;
 
- 			const sockets = [];
 
- 			if (clients) {
 
- 				async.each(
 
- 					Object.keys(clients),
 
- 					(id, next) => {
 
- 						const { session } = clients[id];
 
- 						if (session.userId === payload.userId) sockets.push(clients[id]);
 
- 						next();
 
- 					},
 
- 					() => resolve(sockets)
 
- 				);
 
- 				return;
 
- 			}
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Allows a socket to leave any rooms they are connected to
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.socketId - the id of the socket which should leave all their rooms
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async SOCKET_LEAVE_ROOMS(payload) {
 
- 		return new Promise(resolve => {
 
- 			// filter out rooms that the user is in
 
- 			Object.keys(WSModule.rooms).forEach(room => {
 
- 				WSModule.rooms[room] = WSModule.rooms[room].filter(participant => participant !== payload.socketId);
 
- 			});
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Allows a socket to leave a specific room they are connected to
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.socketId - the id of the socket which should leave a room
 
- 	 * @param {string} payload.room - the room
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async SOCKET_LEAVE_ROOM(payload) {
 
- 		return new Promise(resolve => {
 
- 			// filter out rooms that the user is in
 
- 			if (WSModule.rooms[payload.room])
 
- 				WSModule.rooms[payload.room] = WSModule.rooms[payload.room].filter(
 
- 					participant => participant !== payload.socketId
 
- 				);
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Allows a socket to join a specified room (this will remove them from any rooms they are currently in)
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.socketId - the id of the socket which should join the room
 
- 	 * @param {string} payload.room - the name of the room
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async SOCKET_JOIN_ROOM(payload) {
 
- 		const { room, socketId } = payload;
 
- 		return new Promise(resolve => {
 
- 			// create room if it doesn't exist, and add socketId to array
 
- 			if (WSModule.rooms[room]) {
 
- 				if (WSModule.rooms[room].indexOf(socketId) === -1) WSModule.rooms[room].push(socketId);
 
- 			} else WSModule.rooms[room] = [socketId];
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Emits arguments to any sockets that are in a specified a room
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.room - the name of the room to emit arguments
 
- 	 * @param {object} payload.args - any arguments to be emitted to the sockets in the specific room
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async EMIT_TO_ROOM(payload) {
 
- 		return new Promise(resolve => {
 
- 			// if the room exists
 
- 			if (WSModule.rooms[payload.room] && WSModule.rooms[payload.room].length > 0) {
 
- 				WSModule.rooms[payload.room].forEach(async socketId => {
 
- 					// get every socketId (and thus every socket) in the room, and dispatch to each
 
- 					const socket = await WSModule.runJob("SOCKET_FROM_SOCKET_ID", { socketId }, this);
 
- 					if (socket) socket.dispatch(...payload.args);
 
- 					resolve();
 
- 				});
 
- 				return;
 
- 			}
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Emits arguments to any sockets that are in specified rooms
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {Array} payload.rooms - array of strings with the name of each room e.g. ["station-page", "song.1234"]
 
- 	 * @param {object} payload.args - any arguments to be emitted to the sockets in the specific room
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async EMIT_TO_ROOMS(payload) {
 
- 		return new Promise(resolve => {
 
- 			async.each(
 
- 				payload.rooms,
 
- 				(room, next) => {
 
- 					WSModule.runJob("EMIT_TO_ROOM", { room, args: payload.args });
 
- 					next();
 
- 				},
 
- 				() => resolve()
 
- 			);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Allows a socket to join a 'song' room
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.socketId - the id of the socket which should join the room
 
- 	 * @param {string} payload.room - the name of the room
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async SOCKET_JOIN_SONG_ROOM(payload) {
 
- 		const { room, socketId } = payload;
 
- 		// leave any other song rooms the user is in
 
- 		await WSModule.runJob("SOCKETS_LEAVE_SONG_ROOMS", { sockets: [socketId] }, this);
 
- 		return new Promise(resolve => {
 
- 			// join the room
 
- 			if (WSModule.rooms[room]) WSModule.rooms[room].push(socketId);
 
- 			else WSModule.rooms[room] = [socketId];
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Allows multiple sockets to join a 'song' room
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {Array} payload.sockets - array of socketIds
 
- 	 * @param {object} payload.room - the name of the room
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	SOCKETS_JOIN_SONG_ROOM(payload) {
 
- 		return new Promise(resolve => {
 
- 			Promise.allSettled(
 
- 				payload.sockets.map(async socketId => {
 
- 					await WSModule.runJob("SOCKET_JOIN_SONG_ROOM", { socketId, room: payload.room }, this);
 
- 				})
 
- 			).then(() => resolve());
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Allows multiple sockets to leave any 'song' rooms they are in
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {Array} payload.sockets - array of socketIds
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	SOCKETS_LEAVE_SONG_ROOMS(payload) {
 
- 		return new Promise(resolve => {
 
- 			Promise.allSettled(
 
- 				payload.sockets.map(async socketId => {
 
- 					const rooms = await WSModule.runJob("GET_ROOMS_FOR_SOCKET", { socketId }, this);
 
- 					rooms.forEach(room => {
 
- 						if (room.indexOf("song.") !== -1)
 
- 							WSModule.rooms[room] = WSModule.rooms[room].filter(participant => participant !== socketId);
 
- 					});
 
- 				})
 
- 			).then(() => resolve());
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Gets any sockets connected to a room
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.room - the name of the room
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async GET_SOCKETS_FOR_ROOM(payload) {
 
- 		return new Promise(resolve => {
 
- 			if (WSModule.rooms[payload.room]) resolve(WSModule.rooms[payload.room]);
 
- 			else resolve([]);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Gets any rooms a socket is connected to
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {string} payload.socketId - the id of the socket to check the rooms for
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async GET_ROOMS_FOR_SOCKET(payload) {
 
- 		return new Promise(resolve => {
 
- 			const rooms = [];
 
- 			Object.keys(WSModule.rooms).forEach(room => {
 
- 				if (WSModule.rooms[room].includes(payload.socketId)) rooms.push(room);
 
- 			});
 
- 			resolve(rooms);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Handles use of websockets
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async HANDLE_WS_USE(payload) {
 
- 		return new Promise(resolve => {
 
- 			const { socket, req } = payload;
 
- 			let SID = "";
 
- 			socket.ip = req.headers["x-forwarded-for"] || "0.0.0.0";
 
- 			async.waterfall(
 
- 				[
 
- 					next => {
 
- 						if (!req.headers.cookie) return next("No cookie exists yet.");
 
- 						return UtilsModule.runJob("PARSE_COOKIES", { cookieString: req.headers.cookie }, this).then(
 
- 							res => {
 
- 								SID = res[WSModule.SIDname];
 
- 								next(null);
 
- 							}
 
- 						);
 
- 					},
 
- 					next => {
 
- 						if (!SID) return next("No SID.");
 
- 						return next();
 
- 					},
 
- 					// see if session exists for cookie
 
- 					next => {
 
- 						CacheModule.runJob("HGET", { table: "sessions", key: SID }, this)
 
- 							.then(session => next(null, session))
 
- 							.catch(next);
 
- 					},
 
- 					(session, next) => {
 
- 						if (!session) return next("No session found.");
 
- 						session.refreshDate = Date.now();
 
- 						socket.session = session;
 
- 						return CacheModule.runJob(
 
- 							"HSET",
 
- 							{
 
- 								table: "sessions",
 
- 								key: SID,
 
- 								value: session
 
- 							},
 
- 							this
 
- 						).then(session => next(null, session));
 
- 					},
 
- 					(res, next) => {
 
- 						// check if a session's user / IP is banned
 
- 						PunishmentsModule.runJob("GET_PUNISHMENTS", {}, this)
 
- 							.then(punishments => {
 
- 								const isLoggedIn = !!(socket.session && socket.session.refreshDate);
 
- 								const userId = isLoggedIn ? socket.session.userId : null;
 
- 								const banishment = {
 
- 									banned: false,
 
- 									ban: 0
 
- 								};
 
- 								punishments.forEach(punishment => {
 
- 									if (punishment.expiresAt > banishment.ban) banishment.ban = punishment;
 
- 									if (punishment.type === "banUserId" && isLoggedIn && punishment.value === userId)
 
- 										banishment.banned = true;
 
- 									if (punishment.type === "banUserIp" && punishment.value === socket.ip)
 
- 										banishment.banned = true;
 
- 								});
 
- 								socket.banishment = banishment;
 
- 								next();
 
- 							})
 
- 							.catch(() => next());
 
- 					}
 
- 				],
 
- 				() => {
 
- 					if (!socket.session) socket.session = { socketId: req.headers["sec-websocket-key"] };
 
- 					else socket.session.socketId = req.headers["sec-websocket-key"];
 
- 					resolve(socket);
 
- 				}
 
- 			);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Handles a websocket connection
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @param {object} payload.socket - socket itself
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async HANDLE_WS_CONNECTION(payload) {
 
- 		return new Promise(resolve => {
 
- 			const { socket } = payload;
 
- 			let sessionInfo = "";
 
- 			if (socket.session.sessionId) sessionInfo = ` UserID: ${socket.session.userId}.`;
 
- 			// if session is banned
 
- 			if (socket.banishment && socket.banishment.banned) {
 
- 				WSModule.log(
 
- 					"INFO",
 
- 					"IO_BANNED_CONNECTION",
 
- 					`A user tried to connect, but is currently banned. IP: ${socket.ip}.${sessionInfo}`
 
- 				);
 
- 				socket.dispatch("keep.event:user.banned", { data: { ban: socket.banishment.ban } });
 
- 				socket.close(); // close socket connection
 
- 				resolve();
 
- 				return;
 
- 			}
 
- 			WSModule.log("INFO", "IO_CONNECTION", `User connected. IP: ${socket.ip}.${sessionInfo}`);
 
- 			// catch when the socket has been disconnected
 
- 			socket.on("close", async () => {
 
- 				if (socket.session.sessionId) sessionInfo = ` UserID: ${socket.session.userId}.`;
 
- 				WSModule.log("INFO", "IO_DISCONNECTION", `User disconnected. IP: ${socket.ip}.${sessionInfo}`);
 
- 				// leave all rooms when a socket connection is closed (to prevent rooms object building up)
 
- 				await WSModule.runJob("SOCKET_LEAVE_ROOMS", { socketId: socket.session.socketId });
 
- 			});
 
- 			// catch errors on the socket
 
- 			socket.onerror = error => {
 
- 				console.error("SOCKET ERROR: ", error);
 
- 			};
 
- 			const readyData = {
 
- 				config: {
 
- 					cookie: config.get("cookie"),
 
- 					sitename: config.get("sitename"),
 
- 					recaptcha: {
 
- 						enabled: config.get("apis.recaptcha.enabled"),
 
- 						key: config.get("apis.recaptcha.key")
 
- 					},
 
- 					githubAuthentication: config.get("apis.github.enabled"),
 
- 					messages: config.get("messages"),
 
- 					christmas: config.get("christmas"),
 
- 					footerLinks: config.get("footerLinks"),
 
- 					primaryColor: config.get("primaryColor"),
 
- 					shortcutOverrides: config.get("shortcutOverrides"),
 
- 					registrationDisabled: config.get("registrationDisabled"),
 
- 					mailEnabled: config.get("mail.enabled"),
 
- 					discogsEnabled: config.get("apis.discogs.enabled"),
 
- 					experimental: {
 
- 						changable_listen_mode: config.get("experimental.changable_listen_mode"),
 
- 						media_session: config.get("experimental.media_session"),
 
- 						disable_youtube_search: config.get("experimental.disable_youtube_search"),
 
- 						station_history: config.get("experimental.station_history"),
 
- 						soundcloud: config.get("experimental.soundcloud"),
 
- 						spotify: config.get("experimental.spotify")
 
- 					}
 
- 				},
 
- 				user: { loggedIn: false }
 
- 			};
 
- 			if (socket.session.sessionId) {
 
- 				CacheModule.runJob("HGET", {
 
- 					table: "sessions",
 
- 					key: socket.session.sessionId
 
- 				})
 
- 					.then(session => {
 
- 						if (session && session.userId) {
 
- 							WSModule.userModel.findOne({ _id: session.userId }, (err, user) => {
 
- 								if (err || !user) return socket.dispatch("ready", readyData);
 
- 								if (user) {
 
- 									readyData.user = {
 
- 										loggedIn: true,
 
- 										role: user.role,
 
- 										username: user.username,
 
- 										email: user.email.address,
 
- 										userId: session.userId
 
- 									};
 
- 								}
 
- 								return socket.dispatch("ready", readyData);
 
- 							});
 
- 						} else socket.dispatch("ready", readyData);
 
- 					})
 
- 					.catch(() => socket.dispatch("ready", readyData));
 
- 			} else socket.dispatch("ready", readyData);
 
- 			socket.onmessage = message => {
 
- 				const data = JSON.parse(message.data);
 
- 				if (data.length === 0) return socket.dispatch("ERROR", "Not enough arguments specified.");
 
- 				if (typeof data[0] !== "string") return socket.dispatch("ERROR", "First argument must be a string.");
 
- 				if (data[0] === "ping" && data.length === 2) {
 
- 					const [, CB_REF] = data;
 
- 					return socket.dispatch("CB_REF", CB_REF.CB_REF, Date.now());
 
- 				}
 
- 				const namespaceAction = data[0];
 
- 				if (
 
- 					!namespaceAction ||
 
- 					namespaceAction.indexOf(".") === -1 ||
 
- 					namespaceAction.indexOf(".") !== namespaceAction.lastIndexOf(".")
 
- 				)
 
- 					return socket.dispatch("ERROR", "Invalid first argument");
 
- 				const namespace = data[0].split(".")[0];
 
- 				const action = data[0].split(".")[1];
 
- 				if (!namespace) return socket.dispatch("ERROR", "Invalid namespace.");
 
- 				if (!action) return socket.dispatch("ERROR", "Invalid action.");
 
- 				if (!WSModule.actions[namespace]) return socket.dispatch("ERROR", `Namespace ${namespace} not found.`);
 
- 				if (!WSModule.actions[namespace][action])
 
- 					return socket.dispatch("ERROR", `Action ${namespace}.${action} not found.`);
 
- 				if (data[data.length - 1].CB_REF) {
 
- 					const { CB_REF, onProgress } = data[data.length - 1];
 
- 					data.pop();
 
- 					return socket.actions.emit(data.shift(0), {
 
- 						args: [...data, res => socket.dispatch("CB_REF", CB_REF, res)],
 
- 						onProgress: onProgress ? res => socket.dispatch("PROGRESS_CB_REF", CB_REF, res) : null
 
- 					});
 
- 				}
 
- 				return socket.actions.emit(data.shift(0), { args: data });
 
- 			};
 
- 			// have the socket listen for each action
 
- 			Object.keys(WSModule.actions).forEach(namespace => {
 
- 				Object.keys(WSModule.actions[namespace]).forEach(action => {
 
- 					// the full name of the action
 
- 					const name = `${namespace}.${action}`;
 
- 					// listen for this action to be called
 
- 					socket.listen(name, async ({ args, onProgress }) => {
 
- 						WSModule.runJob("RUN_ACTION", { socket, namespace, action, args }, { onProgress });
 
- 					});
 
- 				});
 
- 			});
 
- 			resolve();
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Runs an action
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async RUN_ACTION(payload) {
 
- 		return new Promise((resolve, reject) => {
 
- 			const { socket, namespace, action, args } = payload;
 
- 			// the full name of the action
 
- 			const name = `${namespace}.${action}`;
 
- 			let cb = args[args.length - 1];
 
- 			if (typeof cb !== "function")
 
- 				cb = () => {
 
- 					WSModule.log("INFO", "IO_MODULE", `There was no callback provided for ${name}.`);
 
- 				};
 
- 			else args.pop();
 
- 			WSModule.log("INFO", "IO_ACTION", `A user executed an action. Action: ${namespace}.${action}.`);
 
- 			// load the session from the cache
 
- 			new Promise(resolve => {
 
- 				if (socket.session.sessionId)
 
- 					CacheModule.runJob("HGET", {
 
- 						table: "sessions",
 
- 						key: socket.session.sessionId
 
- 					})
 
- 						.then(session => {
 
- 							// make sure the sockets sessionId isn't set if there is no session
 
- 							if (socket.session.sessionId && session === null) delete socket.session.sessionId;
 
- 							resolve();
 
- 						})
 
- 						.catch(() => {
 
- 							if (typeof cb === "function")
 
- 								cb({
 
- 									status: "error",
 
- 									message: "An error occurred while obtaining your session"
 
- 								});
 
- 							reject(new Error("An error occurred while obtaining the session"));
 
- 						});
 
- 				else resolve();
 
- 			})
 
- 				.then(() => {
 
- 					// call the job that calls the action, passing it the session, and the arguments the websocket passed us
 
- 					WSModule.runJob("RUN_ACTION2", { session: socket.session, namespace, action, args }, this)
 
- 						.then(response => {
 
- 							cb(response);
 
- 							resolve();
 
- 						})
 
- 						.catch(err => {
 
- 							if (typeof cb === "function")
 
- 								cb({
 
- 									status: "error",
 
- 									message: "An error occurred while executing the specified action."
 
- 								});
 
- 							reject(err);
 
- 							WSModule.log(
 
- 								"ERROR",
 
- 								"IO_ACTION_ERROR",
 
- 								`Some type of exception occurred in the action ${namespace}.${action}. Error message: ${err.message}`
 
- 							);
 
- 						});
 
- 				})
 
- 				.catch(reject);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Runs an action
 
- 	 * @param {object} payload - object that contains the payload
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	async RUN_ACTION2(payload) {
 
- 		return new Promise((resolve, reject) => {
 
- 			const { session, namespace, action, args } = payload;
 
- 			try {
 
- 				// call the the action, passing it the session, and the arguments the websocket passed us
 
- 				WSModule.actions[namespace][action].apply(
 
- 					this,
 
- 					[session].concat(args).concat([
 
- 						result => {
 
- 							WSModule.log(
 
- 								"INFO",
 
- 								"RUN_ACTION2",
 
- 								`Response to action. Action: ${namespace}.${action}. Response status: ${result.status}`
 
- 							);
 
- 							resolve(result);
 
- 						}
 
- 					])
 
- 				);
 
- 			} catch (err) {
 
- 				reject(err);
 
- 				WSModule.log(
 
- 					"ERROR",
 
- 					"IO_ACTION_ERROR",
 
- 					`Some type of exception occurred in the action ${namespace}.${action}. Error message: ${err.message}`
 
- 				);
 
- 			}
 
- 		});
 
- 	}
 
- }
 
- export default new _WSModule();
 
 
  |