diff --git a/client/.eslintrc.json b/client/.eslintrc.json index b8c06745..92b60063 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -15,7 +15,8 @@ "env": { "browser": true, "jest": true, - "node": true + "node": true, + "es6": true }, "rules": { "array-bracket-newline": "error", diff --git a/client/src/components/Anonymous.jsx b/client/src/components/Anonymous.jsx index 74a7ceef..f9de5693 100644 --- a/client/src/components/Anonymous.jsx +++ b/client/src/components/Anonymous.jsx @@ -1,46 +1,42 @@ import React from 'react'; import { BiDotsVerticalRounded } from 'react-icons/bi'; import Chat from 'components/Chat'; -import { ChatProvider } from 'context/ChatContext'; -import Dropdown from 'rsuite/Dropdown'; +import Dropdown from 'rsuite/Dropdown'; import { useNavigate } from 'react-router-dom'; - const centerItems = `flex items-center justify-center`; - - const Anonymous = () => { - const navigate = useNavigate(); return ( - -
-
-

Anonymous User

- - } noCaret> - { - navigate("/"); - }} > - Close Chat - - - +
+
+

Anonymous User

- -
-
+ } + noCaret > - -
+ { + navigate('/'); + }} + > + Close Chat + + +
+
+
- +
); }; diff --git a/client/src/components/BuddyMatcher.jsx b/client/src/components/BuddyMatcher.jsx new file mode 100644 index 00000000..84ccc550 --- /dev/null +++ b/client/src/components/BuddyMatcher.jsx @@ -0,0 +1,97 @@ +import { useState, useContext, useEffect } from 'react'; +import { ThreeDots } from 'react-loading-icons'; +import { SocketContext } from 'context/Context'; + +import Anonymous from 'components/Anonymous'; +import { useAuth } from 'src/context/AuthContext'; +import { useChat } from 'src/context/ChatContext'; + +const BuddyMatcher = () => { + const { auth } = useAuth(); + const { createChat } = useChat(); + + // eslint-disable-next-line no-unused-vars + const [isFound, setIsFound] = useState(false); + const socket = useContext(SocketContext); + + const userID = auth.loginId; + const defaultLoadingText = 'Looking for a random buddy'; + const [loadingText, setLoadingText] = useState(defaultLoadingText); + let timeout = null; + + useEffect(() => { + if (loadingText === defaultLoadingText) { + timeout = setTimeout(() => { + setLoadingText( + 'Taking too long? No chat buddy is currently available :(\nTry refreshing!' + ); + }, 15000); + } + + return () => { + clearTimeout(timeout); + }; + }, [loadingText]); + + useEffect(() => { + if (isFound) { + return; + } + + if (!socket.connected) { + socket.connect(); + } + + // This is necessary else chat won't be restored after re-connections + socket.on('connect', () => { + // Here server will be informed that user is searching for + // another user + socket.emit('join', { loginId: auth.loginId, email: auth.email }); + }); + socket.connected && socket.emit('adding', { userID }); + socket.emit('createRoom', `${userID}-in-search`); + // From here will get the info from server that user has joined the room + + socket.on('joined', ({ roomId, userIds }) => { + localStorage.setItem('currentChatId', roomId); + + createChat(roomId, userIds); + setIsFound(true); + }); + + socket.on('chat_restore', ({ chats, currentChatId }) => { + localStorage.setItem('currentChatId', currentChatId); + Object.values(chats).forEach((chat) => { + createChat( + chat.id, + chat.userIds, + chat.messages, + chat.createdAt + ); + }); + + setIsFound(true); + console.log('chat restored!', chats, currentChatId); + }); + + return () => { + socket.off('connect').off('joined').off('chat_restore'); + socket.disconnect(); + }; + }, []); + + return isFound ? ( + + ) : ( +
+ +
+ {loadingText.split('\n').map((text) => ( +

{text}

+ ))} +
+
+ ); +}; + +export default BuddyMatcher; diff --git a/client/src/components/Chat.jsx b/client/src/components/Chat.jsx index 0d9479b8..d4be8587 100644 --- a/client/src/components/Chat.jsx +++ b/client/src/components/Chat.jsx @@ -1,67 +1,59 @@ -import { useState, useEffect, useRef, useContext, useMemo } from 'react'; +import { useEffect, useRef, useContext, useMemo, useState } from 'react'; import { SocketContext } from 'context/Context'; import 'styles/chat.css'; import ScrollToBottom from 'react-scroll-to-bottom'; import { IoSend } from 'react-icons/io5'; +import { v4 as uuid } from 'uuid'; import { useChat } from 'src/context/ChatContext'; import { useAuth } from 'src/context/AuthContext'; +import useChatUtils from 'src/lib/chat'; +import MessageStatus from './MessageStatus'; let senderId; const Chat = () => { - const { messages: state, addMessage } = useChat(); - const { auth } = useAuth(); - + const [currentChatId, setCurrentChatId] = useState(null); + const { messages: state, addMessage, updateMessage } = useChat(); + const { auth, logout } = useAuth(); const socket = useContext(SocketContext); - const [sentMessages, setSentMessages] = useState([]); - const [receivedMessages, setReceivedMessages] = useState([]); + + const { sendMessage } = useChatUtils(socket); const inputRef = useRef(''); senderId = auth.loginId; useEffect(() => { - const newMessageHandler = ({ id, senderId, message, time }) => { - addMessage({ - id: senderId, - message, - time, - room: 'anon', - messageId: id, - }); + const newMessageHandler = (message) => { + try { + addMessage(message); + } catch { + logout(); + } }; // This is used to recive message form other user. socket.on('receive_message', newMessageHandler); + setCurrentChatId(localStorage.getItem('currentChatId')); return () => { socket.off('receive_message', newMessageHandler); }; }, []); - useEffect(() => { - const userIDs = Object.keys(state).map((item) => item); - const available = userIDs.length === 0; - const sendID = userIDs.find((item) => item === senderId); - const receiverID = userIDs.find((item) => item !== senderId); - const handleMessages = () => { - sendID && setSentMessages([...state[sendID].messages]); - receiverID && setReceivedMessages([...state[receiverID].messages]); - }; - !available && handleMessages(); - }, [state]); - const sortedMessages = useMemo( () => - [...sentMessages, ...receivedMessages].sort((a, b) => { - const da = new Date(a.time), - db = new Date(b.time); - return da - db; - }), - [sentMessages, receivedMessages] + Object.values(state[currentChatId]?.messages ?? {})?.sort( + (a, b) => { + const da = new Date(a.time), + db = new Date(b.time); + return da - db; + } + ), + [state, currentChatId] ); // Here whenever user will submit message it will be send to the server - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); const d = new Date(); @@ -70,30 +62,63 @@ const Chat = () => { if (message === '' || senderId === undefined || senderId === '123456') { return; } - if (socket.connected) { - socket.emit('send_message', { senderId, message, time }); - } else { - console.log('Something went wrong on the server 4853789'); + + if (inputRef.current) { + inputRef.current.value = ''; + inputRef.current.focus(); + } + + const tmpId = uuid(); + + try { addMessage({ - id: senderId, + senderId, + room: currentChatId, + id: tmpId, message, time, - room: 'anon', + status: 'pending', }); + } catch { + logout(); } - console.log(`sender: ${message}`); - // Socket.emit('privatemessage', message); - // addMessage({ - // id: senderId, - // message, - // time, - // room: 'anon', - // }); + try { + console.log('sending...'); + const sentMessage = await sendMessage({ + senderId, + message, + time, + }); - if (inputRef.current) { - inputRef.current.value = ''; - inputRef.current.focus(); + try { + updateMessage(tmpId, sentMessage); + } catch { + logout(); + } + } catch (e) { + try { + updateMessage(tmpId, { + senderId, + room: currentChatId, + id: tmpId, + message, + time, + status: 'failed', + }); + } catch { + logout(); + } + } finally { + console.log('send complete'); + console.log(`sender: ${message}`); + // Socket.emit('privatemessage', message); + // addMessage({ + // id: senderId, + // message, + // time, + // room: 'anon', + // }); } }; @@ -110,7 +135,7 @@ const Chat = () => { className="displayMessgaes h-[75%] w-[100%]" > {sortedMessages.map( - ({ message, time, senderId: sender, id }) => ( + ({ senderId: sender, id, message, time, status }) => (
{ >

{message}

-

{getTime(time)}

+
+ +
) diff --git a/client/src/components/MessageStatus.jsx b/client/src/components/MessageStatus.jsx new file mode 100644 index 00000000..a2ff6b27 --- /dev/null +++ b/client/src/components/MessageStatus.jsx @@ -0,0 +1,52 @@ +import { + IoCheckmarkCircle, + IoCloseCircleOutline, + IoRefreshCircle, +} from 'react-icons/io5'; +import { ThreeDots } from 'react-loading-icons'; +import PropTypes from 'prop-types'; + +export default function MessageStatus({ + iAmTheSender, + time, + status, + onResend, +}) { + const handleResend = () => { + if (onResend) { + onResend(); + } + }; + return ( + <> + {time} + {iAmTheSender && ( + <> + {status === 'sent' && } + {status === 'failed' && ( + <> + + + + )} + {status === 'pending' && ( + + )} + + )} + + ); +} + +MessageStatus.propTypes = { + iAmTheSender: PropTypes.bool, + time: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + onResend: PropTypes.func, +}; diff --git a/client/src/context/ChatContext.jsx b/client/src/context/ChatContext.jsx index 78a96f8b..adfe685a 100644 --- a/client/src/context/ChatContext.jsx +++ b/client/src/context/ChatContext.jsx @@ -3,12 +3,27 @@ import PropTypes from 'prop-types'; import chatReducer from './reducers/chatReducer'; +/** + * @typedef {'pending' | 'sent' | 'failed'} MessageStatus + * + * @typedef {{ + * senderId: string, + * room: string, + * id: string, + * message: string, + * time: number | string, + * status: MessageStatus, + * }} Message + */ + const initialState = {}; const ChatContext = createContext({ ...initialState, deleteMessage: () => undefined, addMessage: () => undefined, + updateMessage: () => undefined, + createChat: () => undefined, }); export const useChat = () => { @@ -36,21 +51,46 @@ export const ChatProvider = ({ children }) => { } ); - function addMessage({ id, message, time, room, messageId }) { + /** + * + * @param {Message} message + */ + function addMessage(message) { dispatch({ type: 'ADD_MESSAGE', - payload: { - id, - message, - time, - room, - messageId - }, + payload: message, + }); + } + + /** + * @param {string} id + * @param {Message} message + */ + function updateMessage(id, message) { + dispatch({ + type: 'UPDATE_MESSAGE', + payload: { id, message }, + }); + } + + /** + * + * @param {string} chatId + * @param {string[]} userIds + * @param {{[key: string]: Message}} messages + * @param { string | number | Date } createdAt + */ + function createChat(chatId, userIds, messages, createdAt) { + dispatch({ + type: 'CREATE_CHAT', + payload: { chatId, userIds, messages, createdAt }, }); } return ( - + {children} ); diff --git a/client/src/context/reducers/chatReducer.js b/client/src/context/reducers/chatReducer.js index 778bb6ba..8326db7e 100644 --- a/client/src/context/reducers/chatReducer.js +++ b/client/src/context/reducers/chatReducer.js @@ -4,14 +4,15 @@ export default function chatReducer(state, action) { const clonedState = cloneState(state); switch (action.type) { - case 'ADD_MESSAGE': { - const { id, time, room, messageId } = action.payload; + case 'ADD_MESSAGE_OLD': { + const { id, time, room, messageId, status } = action.payload; const message = { message: action.payload.message, time, senderId: id, id: messageId, + status, }; if (clonedState[id] === undefined) { @@ -26,6 +27,56 @@ export default function chatReducer(state, action) { break; } + case 'CREATE_CHAT': { + const { + chatId, + userIds, + messages = {}, + createdAt = new Date(), + } = action.payload; + + clonedState[chatId] = { + userIds, + messages, + createdAt, + }; + break; + } + + case 'ADD_MESSAGE': { + const { senderId, room, id, message, time, status } = + action.payload; + + if (!clonedState[room]) { + throw new Error('Room not found!'); + } + + clonedState[room].messages[id] = { + senderId, + room, + id, + message, + time, + status, + }; + break; + } + + case 'UPDATE_MESSAGE': { + const { id, message } = action.payload; + + if (!clonedState[message.room]) { + throw new Error('Room not found!'); + } + + if (id !== message.id) { + delete clonedState[message.room].messages[id]; + } + + clonedState[message.room].messages[message.id] = message; + break; + } + default: throw new Error('No action provided!'); } diff --git a/client/src/lib/chat.js b/client/src/lib/chat.js new file mode 100644 index 00000000..b93ca205 --- /dev/null +++ b/client/src/lib/chat.js @@ -0,0 +1,33 @@ +/** + * @typedef {import('socket.io-client').Socket} Socket + */ + +/** + * + * @param {Socket} socket + */ +export default function useChatUtils(socket) { + function sendMessage(message) { + return new Promise((resolve, reject) => { + if (!socket.connected) { + reject(null); + return; + } + + socket + .timeout(30000) + .emit('send_message', message, (err, sentMessage) => { + if (err) { + reject(err); + return; + } + + resolve(sentMessage); + }); + }); + } + + return { + sendMessage, + }; +} diff --git a/client/src/pages/Searching.jsx b/client/src/pages/Searching.jsx index fa20a258..4ea1d7e5 100644 --- a/client/src/pages/Searching.jsx +++ b/client/src/pages/Searching.jsx @@ -1,81 +1,11 @@ -import { useState, useContext, useEffect } from 'react'; -import { ThreeDots } from 'react-loading-icons'; -import { SocketContext } from 'context/Context'; - -import Anonymous from 'components/Anonymous'; -import { useAuth } from 'src/context/AuthContext'; +import { ChatProvider } from 'src/context/ChatContext'; +import BuddyMatcher from 'src/components/BuddyMatcher'; const Searching = () => { - const { auth } = useAuth(); - // eslint-disable-next-line no-unused-vars - const [isFound, setIsFound] = useState(false); - const socket = useContext(SocketContext); - - const userID = auth.loginId; - const defaultLoadingText = 'Looking for a random buddy'; - const [loadingText, setLoadingText] = useState(defaultLoadingText); - let timeout = null; - - useEffect(() => { - if (loadingText === defaultLoadingText) { - timeout = setTimeout(() => { - setLoadingText( - 'Taking too long? No chat buddy is currently available :(\nTry refreshing!' - ); - }, 15000); - } - - return () => { - clearTimeout(timeout); - }; - }, [loadingText]); - - useEffect(() => { - if (isFound) { - return; - } - - if (!socket.connected) { - socket.connect(); - } - - // This is necessary else chat won't be restored after re-connections - socket.on('connect', () => { - // Here server will be informed that user is searching for - // another user - socket.emit('join', { loginId: auth.loginId, email: auth.email }); - }); - socket.connected && socket.emit('adding', { userID }); - socket.emit('createRoom', `${userID}-in-search`); - // From here will get the info from server that user has joined the room - - socket.on('joined', ({ roomId }) => { - console.log(roomId); // Had to do this to make eslint happy lol - setIsFound(true); - }); - - socket.on('chat_restore', ({ chats, currentChatId }) => { - setIsFound(true); - console.log('chat restored!', chats, currentChatId); - }); - - return () => { - socket.off('connect').off('joined').off('chat_restore'); - socket.disconnect(); - }; - }, []); - - return isFound ? ( - - ) : ( -
- -
- {loadingText.split('\n').map((text) => ( -

{text}

- ))} -
-
+ return ( + + + ); }; diff --git a/client/src/styles/chat.css b/client/src/styles/chat.css index a5d54892..b0ce53b9 100644 --- a/client/src/styles/chat.css +++ b/client/src/styles/chat.css @@ -36,7 +36,7 @@ @apply bg-[#FF9F1C] rounded-[20px] p-[15px] break-all w-full; } - .message-block .message .time { - @apply text-white px-[20px] text-[12px]; + .message-block .message .status { + @apply px-[20px] text-[12px] flex gap-2 items-center; } } diff --git a/server/index.js b/server/index.js index e49ec08d..3c222d46 100644 --- a/server/index.js +++ b/server/index.js @@ -128,7 +128,7 @@ const matchMaker = (io) => { const chat = { id: newRoomId, userIds: [], - messages: [], + messages: {}, createdAt: new Date(), }; @@ -145,6 +145,7 @@ const matchMaker = (io) => { io.to(newRoomId).emit("joined", { roomId: newRoomId, + userIds: pairedUsers.map((user) => user.emailOrLoginId), }); } }; @@ -197,50 +198,63 @@ io.on("connection", (socket) => { matchMaker(io); }); - socket.on("send_message", ({ senderId, message, time }, callback) => { - const user = getActiveUser({ - socketId: socket.id, - }); - - if (!user) { - socket.emit("send_failed", { - message: - "Hmmm. It seems your login session has expired. " + - "Re-login and try again", - messageId: id, - }); - } - - const id = uuid.v4(); - - /** - * Cache the sent message for each user in the chat. - * This is also the point, where we persist the message in the db - */ - user.chats[user.currentChatId ?? ""].userIds.forEach((userId) => { + socket.on( + "send_message", + ({ senderId, message, time, chatId }, returnMessageToSender) => { const user = getActiveUser({ - email: userId, - loginId: userId, + socketId: socket.id, }); - if (user) { - user.chats[user.currentChatId ?? ""].messages.push({ - id, - message, - time, - senderId, - type: "message", + if (!user) { + socket.emit("send_failed", { + message: + "Hmmm. It seems your login session has expired. " + + "Re-login and try again", + messageId: id, }); + + return; } - }); - io.to(user.currentChatId).emit("receive_message", { - senderId, - message, - time, - id, - }); - }); + const id = uuid.v4(); + + /** + * Cache the sent message for each user in the chat. + * This is also the point, where we persist the message in the db + */ + user.chats[user.currentChatId ?? ""].userIds.forEach((userId) => { + const user = getActiveUser({ + email: userId, + loginId: userId, + }); + + if (user) { + user.chats[user.currentChatId ?? ""].messages[id] = { + id, + message, + time, + senderId, + type: "message", + }; + } + }); + + const sentMessage = { + senderId, + message, + time, + id, + room: user.currentChatId, + status: "sent", + }; + + returnMessageToSender(sentMessage); + + socket.broadcast + .to(user.currentChatId) + .emit("receive_message", sentMessage); + } + ); // socket.on('adding', (data) => { // if (data.userID.ID === '') return; // userModule.allUsers(data.userID.ID); diff --git a/server/users.js b/server/users.js index 93420a32..ff72a40c 100644 --- a/server/users.js +++ b/server/users.js @@ -1,4 +1,4 @@ -const { Socket } = require('socket.io'); +const { Socket } = require("socket.io"); /** * @typedef {{ @@ -16,7 +16,9 @@ const { Socket } = require('socket.io'); * @typedef {{ * id: string, * userIds: string[], - * messages: Message[], + * messages: { + * [key: string]: Message + * }, * createdAt: string | Date * }} Chat * @@ -50,31 +52,31 @@ const rooms = []; let currentRoom; function getRandomPairFromWaitingList() { - /** - * Since we indexed waiting users by emailOrLoginId, we need to first - * retrieve all the keys which would be used for getting random users - */ - const waitingUserIds = Object.keys(waiting_users); - const pairedUsers = []; + /** + * Since we indexed waiting users by emailOrLoginId, we need to first + * retrieve all the keys which would be used for getting random users + */ + const waitingUserIds = Object.keys(waiting_users); + const pairedUsers = []; - for (let i = 0; i < 2; i++) { - const randomIndex = Math.floor(Math.random() * waitingUserIds.length); + for (let i = 0; i < 2; i++) { + const randomIndex = Math.floor(Math.random() * waitingUserIds.length); - const randomId = waitingUserIds[randomIndex]; - pairedUsers.push(waiting_users[randomId]); + const randomId = waitingUserIds[randomIndex]; + pairedUsers.push(waiting_users[randomId]); - delWaitingUser(randomId); - waitingUserIds.splice(randomIndex, 1); - } + delWaitingUser(randomId); + waitingUserIds.splice(randomIndex, 1); + } - return pairedUsers; + return pairedUsers; } /** * @param {string} emailOrLoginId */ function isUserActive(emailOrLoginId) { - return Boolean(active_users[emailOrLoginId]); + return Boolean(active_users[emailOrLoginId]); } /** @@ -82,19 +84,19 @@ function isUserActive(emailOrLoginId) { * @param {{ socketId: string, loginId?: string, email?: null | string}} */ function getActiveUser({ socketId, loginId, email }) { - for (let id in active_users) { - const user = active_users[id]; - - if ( - user.socketIds.includes(socketId) || - (email && user.email == email) || - (loginId && user.loginId == loginId) - ) { - return user; - } + for (let id in active_users) { + const user = active_users[id]; + + if ( + user.socketIds.includes(socketId) || + (email && user.email == email) || + (loginId && user.loginId == loginId) + ) { + return user; } + } - return null; + return null; } /** @@ -106,27 +108,27 @@ function getActiveUser({ socketId, loginId, email }) { * }} param0 */ function addToWaitingList({ loginId, email, socket }) { - const emailOrLoginId = email ?? loginId; - - waiting_users[emailOrLoginId] = new Proxy( - { - loginId, - email, - socketConnections: [socket], - socketIds: [socket.id], - chats: {}, - currentChatId: null, - }, - { - get(target, prop, receiver) { - if (prop === 'emailOrLoginId') { - return target.email ?? target.loginId; - } - - return Reflect.get(...arguments); - }, + const emailOrLoginId = email ?? loginId; + + waiting_users[emailOrLoginId] = new Proxy( + { + loginId, + email, + socketConnections: [socket], + socketIds: [socket.id], + chats: {}, + currentChatId: null, + }, + { + get(target, prop, receiver) { + if (prop === "emailOrLoginId") { + return target.email ?? target.loginId; } - ); + + return Reflect.get(...arguments); + }, + } + ); } /** @@ -134,7 +136,7 @@ function addToWaitingList({ loginId, email, socket }) { * @param {ActiveUser} user */ function addActiveUser(user) { - active_users[user.emailOrLoginId] = user; + active_users[user.emailOrLoginId] = user; } /* @@ -142,47 +144,47 @@ function addActiveUser(user) { @return: void */ function allUsers(user) { - users.push(user); + users.push(user); } // Here we are adding user function addUser(udata) { - users[udata.id] = udata.room; + users[udata.id] = udata.room; } // Here we are getting user function getUserRoom(id) { - return users[id]; + return users[id]; } // Here we are adding user's socket with key as soket id function addWaitingUser(udata) { - waiting_users[udata.id] = udata; + waiting_users[udata.id] = udata; } function getWaitingUser(id) { - return waiting_users[id]; + return waiting_users[id]; } // from here we are getting user who are in waiting room. function getWaitingUser(id) { - return waiting_users[id]; + return waiting_users[id]; } // This funtion is used for removing user from waiting list function delWaitingUser(id) { - delete waiting_users[id]; + delete waiting_users[id]; } function getWaitingUserLen() { - return Object.keys(waiting_users).length; + return Object.keys(waiting_users).length; } function getWaitingUserKeys() { - return Object.keys(waiting_users); + return Object.keys(waiting_users); } function remWaitingUser(udata) { - delete waiting_users[udata.id]; + delete waiting_users[udata.id]; } // This funtion is used for adding user to active list @@ -191,11 +193,11 @@ function remWaitingUser(udata) { } */ function getUser() { - let keys = getWaitingUserKeys(); - let index = Math.floor(Math.random() * keys.length); - let user1 = waiting_users[keys[index]]; - delWaitingUser(user1.id); - return user1; + let keys = getWaitingUserKeys(); + let index = Math.floor(Math.random() * keys.length); + let user1 = waiting_users[keys[index]]; + delWaitingUser(user1.id); + return user1; } /* @@ -203,27 +205,27 @@ function getUser() { @return: void */ function createRooms() { - if (users.length < 2) return; - let numberOfRoomsToGenerate = Math.floor(users.length / 2); - for (let i = 0; i < numberOfRoomsToGenerate; i++) { - rooms.push(Math.random().toString(36).substring(1, 10)); - } + if (users.length < 2) return; + let numberOfRoomsToGenerate = Math.floor(users.length / 2); + for (let i = 0; i < numberOfRoomsToGenerate; i++) { + rooms.push(Math.random().toString(36).substring(1, 10)); + } } module.exports = { - allUsers, - addUser, - addWaitingUser, - remWaitingUser, - addActiveUser, - getUserRoom, - getWaitingUserLen, - getWaitingUserKeys, - getWaitingUser, - delWaitingUser, - getUser, - getRandomPairFromWaitingList, - isUserActive, - getActiveUser, - addToWaitingList, + allUsers, + addUser, + addWaitingUser, + remWaitingUser, + addActiveUser, + getUserRoom, + getWaitingUserLen, + getWaitingUserKeys, + getWaitingUser, + delWaitingUser, + getUser, + getRandomPairFromWaitingList, + isUserActive, + getActiveUser, + addToWaitingList, };