├── README.md ├── client ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── Apis │ └── chatApi.js │ ├── App.css │ ├── App.js │ ├── components │ ├── ChatWindow │ │ ├── ChatWindow.jsx │ │ ├── ChatWindow.min.css │ │ └── ChatWindow.scss │ ├── LoggedInUserInfo │ │ ├── LoggedInUserInfo.jsx │ │ ├── LoggedInUserInfo.min.css │ │ └── LoggedInUserInfo.scss │ ├── LoginForm │ │ ├── LoginForm.jsx │ │ ├── LoginForm.min.css │ │ └── LoginForm.scss │ ├── NewMsgForm │ │ ├── NewMsgForm.jsx │ │ ├── NewMsgForm.min.css │ │ └── NewMsgForm.scss │ ├── OtherUserInfo │ │ ├── OtherUserInfo.jsx │ │ ├── OtherUserInfo.min.css │ │ └── OtherUserInfo.scss │ ├── SelectionToolBar │ │ ├── SelectionToolBar.jsx │ │ ├── SelectionToolBar.min.css │ │ └── SelectionToolBar.scss │ ├── User │ │ ├── User.jsx │ │ ├── User.min.css │ │ └── User.scss │ ├── UserChats │ │ ├── UserChats.jsx │ │ ├── UserChats.min.css │ │ └── UserChats.scss │ └── UsersList │ │ ├── UsersList.jsx │ │ ├── UsersList.min.css │ │ └── UsersList.scss │ ├── contexts │ ├── ActiveConversationProvider.jsx │ ├── ActiveMessageOptionsProvider.jsx │ ├── ChatDBProvider.jsx │ ├── CurrentUserProvider.jsx │ ├── NewMsgsMapProvider.jsx │ ├── SelectedMessagesProvider.jsx │ ├── SelectionToolBarStatusProvider.jsx │ ├── SidebarRefProvider.jsx │ ├── SidebarStatusProvider.jsx │ ├── SocketProvider.jsx │ ├── UserChatsProvider.jsx │ ├── UsersProvider.jsx │ └── ViewProvider.jsx │ ├── hooks │ ├── useLocalStorage.js │ └── useSessionStorage.js │ ├── index.css │ └── index.js ├── package-lock.json ├── package.json └── server ├── Models ├── chats.js └── users.js ├── Routes ├── chat_router.js └── users_router.js ├── mongo_connection.js ├── package-lock.json ├── package.json ├── server.js └── socket_IO_handlers └── chat_app_handler.js /README.md: -------------------------------------------------------------------------------- 1 | 2 | # MERN Stack - Chat Application 3 | 4 | Webapp is live [here](http://yml-chat-app.herokuapp.com/) 5 | 6 | #### Technologies Stack 7 | - MERN 8 | - ReacJS 9 | - NodeJS 10 | - ExpressJS 11 | - MongoDB 12 | - Socket.io 13 | - Material UI 14 | 15 | #### To-Do List 16 | ##### Fronted 17 | - [x] Login Page. 18 | - [x] Chat window template. 19 | - [x] List of users(Conversations). 20 | - [x] Set active conversation. 21 | - [x] Render particular user's chats. 22 | - [x] Create new message form. 23 | - [x] Connect backend. 24 | - [x] Setup socket IO Client. 25 | - [x] Add Emotes. 26 | - [x] Change the color scheme. 27 | - [x] Fetch users fom db. 28 | - [x] Change the msg background and font colors. 29 | - [x] Set up socket IO 'msg-sent', 'msg-pending', and 'msg-not-sent' events. 30 | - [x] Separate icons for msg statuses. 31 | - [x] Responsiveness. 32 | - [x] Back Button in mobile view. 33 | - [x] Add date. 34 | - [x] Select a message. 35 | - [x] Dropdown on a message. 36 | - [x] Align checkboxes, online status dot! 37 | - [x] Delete a message. 38 | - [x] Delete a whole convo (functionality of 3 dots). 39 | - [x] Message notifications. 40 | - [ ] Forward a message. 41 | - [ ] Identify the type of user, i.e, Learner/Educator/Institute. 42 | - [ ] Create a group conversation. 43 | 44 | ##### Backend 45 | - [x] Create a Mongo DB cluster. 46 | - [x] Create placeholder users DB. 47 | - [x] Create placeholder chats DB. 48 | - [x] Setup Socket IO server. 49 | - [x] Online user communication. 50 | - [x] Implement Wait Queues for offline users. 51 | - [x] Connect to mongoDB cluster. 52 | - [x] Insert new Message into Database. 53 | - [x] Connect to users database. 54 | - [x] Add status field to the chats collection. 55 | - [x] Set up socket IO 'msg-sent', 'msg-pending', and 'msg-not-sent' events. 56 | - [x] Besides flushing messages to a newly logged in user, inform those messages' senders that these messages are now sent. 57 | - [x] Setup Wait queues for socket io events as well. 58 | - [x] Update messages' statuses on the database after flushing them. 59 | - [x] Delete a message. 60 | - [x] Delete a whole convo end-point. 61 | - [ ] Message notifications. 62 | - [ ] Forward a message. 63 | - [ ] Create a group functionality. 64 | - [ ] Add users to group. 65 | 66 | 67 | ##### Other (Or Common) 68 | - [x] Differentiate sent, pending and unset message. 69 | - [x] Differentiate between active and inactive(offline) users. 70 | - [ ] Differentiate read and unread messages. 71 | 72 | 73 | 74 | ##### Feel free to contibute and open any issues. 75 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.0", 7 | "@material-ui/icons": "^4.9.1", 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.5.0", 10 | "@testing-library/user-event": "^7.2.1", 11 | "axios": "^0.20.0", 12 | "emoji-picker-react": "^3.2.4", 13 | "react": "^16.13.1", 14 | "react-dom": "^16.13.1", 15 | "react-router-dom": "^5.2.0", 16 | "react-scripts": "^3.4.4", 17 | "socket.io-client": "^2.3.1", 18 | "uuid": "^8.3.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": "react-app" 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Subhash3/chat-app/c30ab81562aa83c384892340ab9f3519c7be432a/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Subhash3/chat-app/c30ab81562aa83c384892340ab9f3519c7be432a/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Subhash3/chat-app/c30ab81562aa83c384892340ab9f3519c7be432a/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/Apis/chatApi.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | // const URL = "http://localhost:3000/chat-app" 4 | const URL = "https://yml-chat-app.herokuapp.com/chat-app" 5 | 6 | export const chatAPI = axios.create({ 7 | baseURL: URL 8 | }) -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | :root{ 2 | --bg-color: #4745B1; 3 | /* --bg-color: rgb(6, 180, 99); */ 4 | --msg-text: white; 5 | /* --other-msg-bg: rgb(17, 13, 78); */ 6 | --other-msg-bg: #4745B1; 7 | /* --my-msg-bg: rgb(194, 100, 116); */ 8 | --my-msg-bg: white; 9 | /* --secondary-bg: #1b3650; */ 10 | --secondary-bg: #EEEEFF; 11 | --dark-secondary-bg: #07131d; 12 | } 13 | 14 | .App { 15 | text-align: center; 16 | } 17 | 18 | /* EEEEFF */ 19 | /* 4745B1 */ 20 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import ChatWindow from './components/ChatWindow/ChatWindow.jsx' 4 | import LoginForm from './components/LoginForm/LoginForm' 5 | import UsersProvider from './contexts/UsersProvider' 6 | import CurrentUserProvider from './contexts/CurrentUserProvider' 7 | import ActiveConversationProvider from './contexts/ActiveConversationProvider' 8 | import ChatDBProvider from './contexts/ChatDBProvider' 9 | import SocketProvider from './contexts/SocketProvider' 10 | import ViewProvider from './contexts/ViewProvider' 11 | import ActiveMessageOptionsProvider from './contexts/ActiveMessageOptionsProvider' 12 | import NewMsgsMapProvider from './contexts/NewMsgsMapProvider' 13 | // import SidebarRefProvider from './contexts/SidebarRefProvider' 14 | // import UserChatsProvider from './contexts/UserChatsProvider' 15 | import SelectedMessagesProvider from './contexts/SelectedMessagesProvider' 16 | import SelectionToolBarStatusProvider from './contexts/SelectionToolBarStatusProvider' 17 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' 18 | 19 | function App() { 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {/* */} 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
42 |
43 | {/*
*/} 44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | ); 52 | } 53 | 54 | export default App; 55 | -------------------------------------------------------------------------------- /client/src/components/ChatWindow/ChatWindow.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import UsersList from '../UsersList/UsersList' 3 | import LoggedInUserInfo from '../LoggedInUserInfo/LoggedInUserInfo' 4 | import UserChats from '../UserChats/UserChats' 5 | import NewMsgForm from '../NewMsgForm/NewMsgForm' 6 | import OtherUserInfo from '../OtherUserInfo/OtherUserInfo'; 7 | import { useSocket } from '../../contexts/SocketProvider' 8 | import { useActiveConversation } from '../../contexts/ActiveConversationProvider' 9 | import { useCurrentUser } from '../../contexts/CurrentUserProvider' 10 | import { useActiveMessageOptionsID } from '../../contexts/ActiveMessageOptionsProvider.jsx' 11 | // import { useSidebarRef } from '../../contexts/SidebarRefProvider' 12 | import { Redirect } from 'react-router-dom' 13 | import './ChatWindow.min.css' 14 | 15 | const ChatWindow = () => { 16 | const [currentUser] = useCurrentUser() 17 | const socket = useSocket() 18 | const [activeConversationID] = useActiveConversation() 19 | const [_msgIDWithActiveOptions, setMsgIDWithActiveOptions] = useActiveMessageOptionsID() 20 | 21 | // console.log("rendering CHAT_WINDOW") 22 | 23 | useEffect(() => { 24 | const chatWindowElement = document.querySelector('.chat-window') 25 | if (chatWindowElement) { 26 | chatWindowElement.addEventListener('click', () => { 27 | console.log("CHAT_WINDOW clicked") 28 | setMsgIDWithActiveOptions(null) 29 | }) 30 | } 31 | }, []) 32 | 33 | useEffect(() => { 34 | if (socket) 35 | socket.emit("logged-in", currentUser.id) 36 | }, [socket, currentUser]); 37 | 38 | return !currentUser ? : ( 39 |
40 |
41 | 42 |
43 | 44 |
45 |
46 | {activeConversationID && } 47 | 48 | 49 |
50 |
51 |
52 | ); 53 | } 54 | 55 | export default ChatWindow; 56 | -------------------------------------------------------------------------------- /client/src/components/ChatWindow/ChatWindow.min.css: -------------------------------------------------------------------------------- 1 | .chat-window{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);width:100%;height:100vh;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;margin:0}.chat-window .window-sidebar{background-color:var(--secondary-bg);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:25%;height:100%;min-width:270px;-webkit-transition:-webkit-transform 200ms ease-in-out;transition:-webkit-transform 200ms ease-in-out;transition:transform 200ms ease-in-out;transition:transform 200ms ease-in-out, -webkit-transform 200ms ease-in-out}.chat-window .window-sidebar .loggedin-user-info{width:100%;height:68px;background-color:var(--secondary-bg)}.chat-window .chat-box{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:75%;--other-users-info-height: 60px}.chat-window .chat-box .other-user-info{height:var(--other-users-info-height);background-color:var(--secondary-bg)}.chat-window .chat-box .chats{width:100%;height:calc( 90% - var(--other-users-info-height));-webkit-box-shadow:inset 1px 1px 5px var(--other-msg-bg);box-shadow:inset 1px 1px 5px var(--other-msg-bg)}@media screen and (max-width: 750px){.chat-window{height:100%}.chat-window .window-sidebar{position:absolute;z-index:10;-webkit-box-shadow:1px 1px 5px var(--other-msg-bg);box-shadow:1px 1px 5px var(--other-msg-bg);-webkit-transform:translateX(-110%);transform:translateX(-110%);width:100%}.chat-window .window-sidebar.active{-webkit-transform:translateX(0%);transform:translateX(0%)}.chat-window .chat-box{width:100%}.chat-window .chat-box .chats .toggle-sidebar{display:-ms-grid;display:grid}}@media screen and (max-width: 416px){.msg{max-width:200px}} 2 | -------------------------------------------------------------------------------- /client/src/components/ChatWindow/ChatWindow.scss: -------------------------------------------------------------------------------- 1 | .chat-window{ 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | width: 100%; 7 | height: 100vh; 8 | display: flex; 9 | flex-direction: row; 10 | margin: 0; 11 | // box-shadow: 0px 0px 3px black; 12 | // border: 1px solid black; 13 | 14 | .window-sidebar{ 15 | // border: 1px solid black; 16 | background-color: var(--secondary-bg); 17 | display: flex; 18 | flex-direction: column; 19 | width: 25%; 20 | height: 100%; 21 | min-width: 270px; 22 | transition: transform 200ms ease-in-out; 23 | // overflow-y: auto; 24 | 25 | .loggedin-user-info{ 26 | width: 100%; 27 | height: 68px; 28 | background-color: var(--secondary-bg); 29 | } 30 | } 31 | 32 | .chat-box{ 33 | flex-direction: column; 34 | // background-color: #e3edee; 35 | align-items: center; 36 | width: 75%; 37 | --other-users-info-height: 60px; 38 | 39 | .other-user-info{ 40 | height: var(--other-users-info-height); 41 | background-color: var(--secondary-bg); 42 | // border: 1px solid black; 43 | } 44 | 45 | .chats{ 46 | width: 100%; 47 | height: calc( 90% - var(--other-users-info-height) ); 48 | box-shadow: inset 1px 1px 5px var(--other-msg-bg); 49 | // border: 1px solid black; 50 | } 51 | } 52 | } 53 | 54 | @media screen and (max-width: 750px){ 55 | .chat-window{ 56 | // position: relative; 57 | height: 100%; 58 | 59 | .window-sidebar{ 60 | position: absolute; 61 | z-index: 10; 62 | box-shadow: 1px 1px 5px var(--other-msg-bg); 63 | transform: translateX(-110%); 64 | width: 100%; 65 | 66 | &.active{ 67 | transform: translateX(0%); 68 | } 69 | } 70 | 71 | .chat-box{ 72 | width: 100%; 73 | 74 | // .other-user-info{ 75 | // // justify-content: space-between; 76 | // // .image{ 77 | // // margin-left: 20%; 78 | // // } 79 | // } 80 | 81 | .chats{ 82 | .toggle-sidebar{ 83 | display: grid; 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | @media screen and (max-width: 416px){ 91 | .msg{ 92 | max-width: 200px; 93 | } 94 | } -------------------------------------------------------------------------------- /client/src/components/LoggedInUserInfo/LoggedInUserInfo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import User from '../User/User' 3 | import { useCurrentUser } from '../../contexts/CurrentUserProvider' 4 | import './LoggedInUserInfo.min.css' 5 | 6 | const LoggedInUserInfo = () => { 7 | const [currentUser] = useCurrentUser() 8 | return ( 9 |
10 | 11 |
12 | ); 13 | } 14 | 15 | export default LoggedInUserInfo; 16 | -------------------------------------------------------------------------------- /client/src/components/LoggedInUserInfo/LoggedInUserInfo.min.css: -------------------------------------------------------------------------------- 1 | .loggedin-user-info{display:-ms-grid;display:grid;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-shadow:0px 1px 10px gray;box-shadow:0px 1px 10px gray;background-color:white}.loggedin-user-info .user{border:none;background-color:transparent}.loggedin-user-info .user .image{--size: 35px;border:3px solid var(--bg-color)}.loggedin-user-info .user .name{font-family:'Courier New', Courier, monospace;font-size:18px}.loggedin-user-info .user:hover{-webkit-box-shadow:none;box-shadow:none} 2 | -------------------------------------------------------------------------------- /client/src/components/LoggedInUserInfo/LoggedInUserInfo.scss: -------------------------------------------------------------------------------- 1 | .loggedin-user-info{ 2 | display: grid; 3 | align-items: center; 4 | box-shadow: 0px 1px 10px gray; 5 | background-color: white; 6 | 7 | .user{ 8 | border: none; 9 | background-color: transparent; 10 | 11 | .image{ 12 | --size: 35px; 13 | border: 3px solid var(--bg-color); 14 | } 15 | 16 | .name{ 17 | font-family: 'Courier New', Courier, monospace; 18 | font-size: 18px; 19 | } 20 | 21 | &:hover{ 22 | box-shadow: none; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /client/src/components/LoginForm/LoginForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import { useCurrentUser } from '../../contexts/CurrentUserProvider.jsx' 3 | import './LoginForm.min.css' 4 | 5 | // const API = "localhost:3000/" 6 | 7 | const LoginForm = (props) => { 8 | const [formData, setFormData] = useState({ 9 | username: "", 10 | userID: "", 11 | }) 12 | const [error, setError] = useState("") 13 | const usernameInputRef = useRef() 14 | const [, setCurrentUser] = useCurrentUser() 15 | 16 | // console.log("In login component: ", { currentUser }) 17 | 18 | useEffect(() => { 19 | usernameInputRef.current.focus() 20 | }, []) 21 | 22 | const handleUsernameChange = (e) => { 23 | let newFormData = { ...formData } 24 | newFormData.username = e.target.value 25 | setFormData(newFormData) 26 | } 27 | 28 | const handleUserIDChange = (e) => { 29 | let newFormData = { ...formData } 30 | newFormData.userID = e.target.value 31 | setFormData(newFormData) 32 | } 33 | 34 | const resetForm = () => { 35 | let newFormData = { ...formData } 36 | newFormData.username = "" 37 | newFormData.password = "" 38 | setFormData(newFormData) 39 | } 40 | 41 | const handleSubmit = (e) => { 42 | e.preventDefault() 43 | if (!formData.username) { 44 | setError("username cannot be empty") 45 | } else if (!formData.userID) { 46 | setError("UserID cannot be empty") 47 | } else { 48 | let currentUserInfo = { 49 | name: formData.username, 50 | id: formData.userID 51 | } 52 | 53 | setCurrentUser(currentUserInfo) 54 | resetForm() 55 | setError("") 56 | props.history.push('/') 57 | } 58 | } 59 | 60 | return ( 61 |
62 |
Login
63 | 64 |
65 | 72 |
73 |
74 | 80 |
81 |
82 | 83 |
84 | 85 | ); 86 | } 87 | 88 | const ErrorMsg = ({ error }) => { 89 | const styles = { 90 | color: "red", 91 | fontStyle: "italic", 92 | fontSize: '12px', 93 | margin: '5px auto', 94 | textTransform: 'capitalize' 95 | } 96 | return ( 97 |

{error}

98 | ) 99 | } 100 | 101 | export default LoginForm; 102 | -------------------------------------------------------------------------------- /client/src/components/LoginForm/LoginForm.min.css: -------------------------------------------------------------------------------- 1 | .myform{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:300px;border:1px solid black;padding:20px;text-transform:uppercase;font-family:monospace}.myform .form-title{font-size:19px;display:inline-block;margin:0 auto}.myform .form-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:10px}.myform .form-group input{width:100%;padding:10px;outline:none}.myform .form-group input[type="submit"]{cursor:pointer;border:none;padding:10px;border-radius:5px;background:#3535b9;color:white;text-transform:uppercase} 2 | -------------------------------------------------------------------------------- /client/src/components/LoginForm/LoginForm.scss: -------------------------------------------------------------------------------- 1 | .myform{ 2 | position: absolute; 3 | left: 50%; 4 | top: 50%; 5 | transform: translate(-50%, -50%); 6 | display: flex; 7 | flex-direction: column; 8 | width: 300px; 9 | border: 1px solid black; 10 | padding: 20px; 11 | text-transform: uppercase; 12 | font-family: monospace; 13 | 14 | .form-title{ 15 | font-size: 19px; 16 | display: inline-block; 17 | margin: 0 auto; 18 | } 19 | 20 | .form-group{ 21 | display: flex; 22 | flex-direction: row; 23 | justify-content: space-between; 24 | align-items: center; 25 | padding: 10px; 26 | // border: 1px solid red; 27 | 28 | input{ 29 | width: 100%; 30 | padding: 10px; 31 | outline: none; 32 | 33 | &[type="submit"]{ 34 | cursor: pointer; 35 | border: none; 36 | padding: 10px; 37 | border-radius: 5px; 38 | background: rgb(53, 53, 185); 39 | color: white; 40 | text-transform: uppercase; 41 | } 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /client/src/components/NewMsgForm/NewMsgForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import { useCurrentUser } from '../../contexts/CurrentUserProvider' 3 | import { useActiveConversation } from '../../contexts/ActiveConversationProvider' 4 | import { useChatDB } from '../../contexts/ChatDBProvider' 5 | import { useSocket } from '../../contexts/SocketProvider' 6 | import { chatAPI } from '../../Apis/chatApi' 7 | import { useNewMessagesMap } from '../../contexts/NewMsgsMapProvider' 8 | import Picker from 'emoji-picker-react'; 9 | import SendIcon from '@material-ui/icons/Send'; 10 | import { v4 as uuid } from 'uuid' 11 | import './NewMsgForm.min.css' 12 | 13 | export const MSG_PENDING = "pending" 14 | export const MSG_SENT = "msg-sent" 15 | export const MSG_NOT_SENT = "msg-not-sent" 16 | 17 | const NewMsgForm = () => { 18 | const [newMsg, setNewMsg] = useState("") 19 | const [openEmojiPicker, setOpenEmojiPicker] = useState(false) 20 | const [chatDB, setChatDB] = useChatDB() 21 | const [newMsgsMap, setNewMsgMap] = useNewMessagesMap() 22 | const [currentUser] = useCurrentUser() 23 | const [activeConversationID] = useActiveConversation() 24 | const socket = useSocket() 25 | const inputRef = useRef() 26 | 27 | // console.log("rendering NEW_MSG_FORM") 28 | 29 | useEffect(() => { 30 | if (inputRef.current) { 31 | inputRef.current.focus() 32 | } 33 | 34 | return () => { 35 | 36 | } 37 | }, [activeConversationID, inputRef]) 38 | 39 | const onEmojiClick = (event, emojiObject) => { 40 | setNewMsg(newMsg + emojiObject.emoji); 41 | if (inputRef.current) { 42 | inputRef.current.focus() 43 | } 44 | }; 45 | 46 | const changeMessageState = (msgID, msgStatus) => { 47 | console.log("Changing the state of the message with ID: ", msgID) 48 | setChatDB((prevChatDB) => { 49 | return prevChatDB.map(chatObj => { 50 | if (chatObj.id === msgID) { 51 | return { ...chatObj, status: msgStatus } 52 | } 53 | return chatObj 54 | }) 55 | }) 56 | } 57 | 58 | const getFormattedDate = (date) => { 59 | let year = date.getFullYear(); 60 | 61 | let month = (1 + date.getMonth()).toString(); 62 | month = month.length > 1 ? month : '0' + month; 63 | 64 | let day = date.getDate().toString(); 65 | day = day.length > 1 ? day : '0' + day; 66 | 67 | return day + '/' + month + '/' + year; 68 | } 69 | 70 | const updateNewMsgCount = (senderID) => { 71 | console.log("Updating new msg count") 72 | // console.log({ senderID, activeConversationID }) 73 | // if (senderID == activeConversationID) { 74 | // return; 75 | // } 76 | 77 | let newCount 78 | console.log({ newMsgsMap }) 79 | if (senderID in newMsgsMap) { 80 | newCount = newMsgsMap[senderID] + 1 81 | } 82 | else { 83 | newCount = 1 84 | } 85 | 86 | console.log({ newMsgsMap, senderID, newCount }) 87 | setNewMsgMap({ ...newMsgsMap, [senderID]: newCount }) 88 | } 89 | 90 | const sendMessage = (msgBody) => { 91 | setOpenEmojiPicker(false) 92 | let msgObject = { 93 | id: uuid(), 94 | senderID: currentUser.id, 95 | receiverID: activeConversationID, 96 | msgBody, 97 | status: MSG_PENDING, 98 | time: new Date().toLocaleString().split(' ')[1], 99 | date: getFormattedDate(new Date()), 100 | } 101 | 102 | console.log("Sending message: ", msgObject) 103 | 104 | socket.emit('send-message', JSON.stringify(msgObject)) 105 | setChatDB([...chatDB, msgObject]) 106 | 107 | socket.on('message-sent', (id) => { 108 | console.log("[message-sent]: Message Has been sent") 109 | // console.log(chatDB) 110 | changeMessageState(id, MSG_SENT) 111 | }) 112 | 113 | socket.on('message-not-sent', (reason, id) => { 114 | console.log("[message-not-sent]: Message has been not sent. : " + reason) 115 | changeMessageState(id, MSG_NOT_SENT) 116 | }) 117 | 118 | socket.on('pending', (reason, id) => { 119 | console.log("[pending]: Message pending... : " + reason) 120 | changeMessageState(id, MSG_PENDING) 121 | }) 122 | 123 | socket.on('received-message', msbObjectString => { 124 | let msgObject = JSON.parse(msbObjectString) 125 | console.log("[received-message]: Received message:", msgObject) 126 | 127 | updateNewMsgCount(msgObject.senderID) 128 | // msgObject.id = uuid() 129 | getChatDB() 130 | }) 131 | 132 | socket.on('flush-messages', allMessagesStringified => { 133 | console.log("[flush-messages]: Got flushed messages") 134 | let allMessages = JSON.parse(allMessagesStringified) 135 | addFlushedMessages(allMessages) 136 | }) 137 | 138 | socket.on('deleted-msgs-ack', () => { 139 | console.log("[deleted-msgs-ack]:") 140 | getChatDB() 141 | }) 142 | } 143 | 144 | const addFlushedMessages = (allMessages) => { 145 | if (!allMessages) 146 | return 147 | 148 | let allMesssageObjects = [] 149 | 150 | allMessages.forEach(message => { 151 | allMesssageObjects.push(JSON.parse(message)) 152 | }) 153 | 154 | setChatDB([...chatDB, ...allMesssageObjects]) 155 | } 156 | 157 | const getChatDB = async () => { 158 | console.log("***Fetching chat database***") 159 | let userID = currentUser.id 160 | let response = await chatAPI.get(`/chats/${userID}`) 161 | console.log("***Got chat DB***") 162 | console.log(response.data) 163 | setChatDB(response.data) 164 | } 165 | 166 | const handleSubmit = (e) => { 167 | e.preventDefault() 168 | if (!newMsg) return 169 | sendMessage(newMsg) 170 | setNewMsg("") 171 | } 172 | 173 | const handleChange = (e) => { 174 | setNewMsg(e.target.value) 175 | } 176 | 177 | return !activeConversationID ? null : ( 178 |
182 | 189 |
{ setOpenEmojiPicker(!openEmojiPicker) }} 192 | > 193 | 😊 194 |
195 | { 196 | openEmojiPicker && 197 | 198 | } 199 | 200 | 201 | 202 | ); 203 | } 204 | 205 | export default NewMsgForm; 206 | -------------------------------------------------------------------------------- /client/src/components/NewMsgForm/NewMsgForm.min.css: -------------------------------------------------------------------------------- 1 | .new-msg-form{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-pack:distribute;justify-content:space-around;background-color:#fafafa;height:60px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:10px}.new-msg-form input{width:70%;padding:10px;outline:none}.new-msg-form input:focus{outline:none}.new-msg-form input:active{outline:none}.new-msg-form button{border:none;width:50px;padding:10px;background-color:transparent;color:var(--bg-color);border-radius:3px;cursor:pointer;-webkit-transition:background 200ms ease-in-out;transition:background 200ms ease-in-out}.new-msg-form button svg{width:100%;height:100%}.new-msg-form .emotes{width:50px;height:30px;font-size:25px;color:var(--bg-color);text-align:center;cursor:pointer}.new-msg-form .emoji-picker-react{position:absolute;bottom:100%;right:30px} 2 | -------------------------------------------------------------------------------- /client/src/components/NewMsgForm/NewMsgForm.scss: -------------------------------------------------------------------------------- 1 | .new-msg-form{ 2 | position: relative; 3 | display: flex; 4 | flex-direction: row; 5 | justify-content: space-around; 6 | background-color: #fafafa; 7 | height: 60px; 8 | align-items: center; 9 | padding: 10px; 10 | 11 | input{ 12 | width: 70%; 13 | padding: 10px; 14 | outline: none; 15 | 16 | &:focus{ 17 | outline: none; 18 | } 19 | &:active{ 20 | outline: none; 21 | } 22 | } 23 | button{ 24 | border: none; 25 | width: 50px; 26 | padding: 10px; 27 | background-color: transparent; 28 | color: var(--bg-color); 29 | border-radius: 3px; 30 | cursor: pointer; 31 | // border: 1px solid black; 32 | transition:background 200ms ease-in-out; 33 | 34 | // &:hover{ 35 | // background-color: rgb(13, 131, 76); 36 | // } 37 | // &:active{ 38 | // background-color: var(--bg-color); 39 | // } 40 | 41 | svg{ 42 | width: 100%; 43 | height: 100%; 44 | // transform: rotate(-10deg); 45 | } 46 | } 47 | 48 | .emotes{ 49 | // border: 1px solid black; 50 | width: 50px; 51 | height: 30px; 52 | font-size: 25px; 53 | color: var(--bg-color); 54 | text-align: center; 55 | cursor: pointer; 56 | } 57 | 58 | .emoji-picker-react{ 59 | position: absolute; 60 | bottom: 100%; 61 | right: 30px; 62 | } 63 | } -------------------------------------------------------------------------------- /client/src/components/OtherUserInfo/OtherUserInfo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useUsers } from '../../contexts/UsersProvider' 3 | import { useActiveConversation } from '../../contexts/ActiveConversationProvider' 4 | import { useCurrentUser } from '../../contexts/CurrentUserProvider' 5 | import MoreVertIcon from '@material-ui/icons/MoreVert'; 6 | import { toggleSidebar } from '../UserChats/UserChats' 7 | import { chatAPI } from '../../Apis/chatApi' 8 | import ArrowBackIcon from '@material-ui/icons/ArrowBack'; 9 | import { useSocket } from '../../contexts/SocketProvider' 10 | import { useView } from '../../contexts/ViewProvider' 11 | import { MOBILE_VIEW } from '../../contexts/ViewProvider' 12 | import './OtherUserInfo.min.css' 13 | 14 | const OtherUserInfo = () => { 15 | const [users] = useUsers() 16 | const [activeConversationID] = useActiveConversation() 17 | const [isSettingsMenuActive, setSettingsActive] = useState(false) 18 | const [view, _setView] = useView() 19 | 20 | const getOtherUserName = () => { 21 | for (let user of users) { 22 | // console.log("HIHIH: ", user.name) 23 | if (user.id === activeConversationID) { 24 | return user.name 25 | } 26 | } 27 | 28 | return activeConversationID 29 | } 30 | 31 | const toggleSettingsMenu = () => { 32 | setSettingsActive(!isSettingsMenuActive) 33 | } 34 | 35 | return ( 36 |
37 | {(view === MOBILE_VIEW) && (
41 | 42 |
) 43 | } 44 |
45 |
46 | {getOtherUserName()} 47 | {/*
{}
*/} 48 |
49 |
50 | 51 | 52 |
53 |
54 | ); 55 | } 56 | 57 | const SettingsMenu = ({ isActive }) => { 58 | const [activeConversationID] = useActiveConversation() 59 | const [currentUser] = useCurrentUser() 60 | const socket = useSocket() 61 | 62 | const deleteConversation = async () => { 63 | let id1 = currentUser.id 64 | let id2 = activeConversationID 65 | 66 | 67 | console.log({ activeConversationID, myID: currentUser.id }) 68 | console.log({ id1, id2 }) 69 | let response = await chatAPI.post('/delete-convo', { 70 | IDs: { id1, id2 } 71 | }) 72 | if (response.data.status === 1) { 73 | console.log("Deleted conversation!") 74 | console.log(`Emitting "deleted-msgs-ack to ${id2} and ${id1}`) 75 | socket.emit("deleted-msgs-ack", id2) 76 | socket.emit("deleted-msgs-ack", id1) 77 | } else { 78 | alert("Failed to delete conversation!") 79 | } 80 | } 81 | 82 | return ( 83 |
84 |
Block User
85 |
Delete Conversation
88 |
Some Shit
89 |
90 | ) 91 | } 92 | 93 | export default OtherUserInfo; 94 | -------------------------------------------------------------------------------- /client/src/components/OtherUserInfo/OtherUserInfo.min.css: -------------------------------------------------------------------------------- 1 | .other-user-info{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:5px 10px;-webkit-box-shadow:1px 1px 3px var(--other-msg-bg);box-shadow:1px 1px 3px var(--other-msg-bg);z-index:1}.other-user-info .back-button{cursor:pointer}.other-user-info .image{width:40px;height:40px;border:1px solid var(--other-msg-bg);border-radius:50%;margin:0 10px}.other-user-info .name{font-family:monospace;font-size:16px}.other-user-info .settings{margin:0 10px 0 auto;cursor:pointer;font-family:monospace}.other-user-info .settings .settings-menu{position:absolute;right:20px;background:white;-webkit-box-shadow:1px 1px 3px var(--other-msg-bg);box-shadow:1px 1px 3px var(--other-msg-bg);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;border-radius:7px;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:top right;transform-origin:top right;-webkit-transition:-webkit-transform 200ms ease-in-out;transition:-webkit-transform 200ms ease-in-out;transition:transform 200ms ease-in-out;transition:transform 200ms ease-in-out, -webkit-transform 200ms ease-in-out}.other-user-info .settings .settings-menu.active{-webkit-transform:scale(1);transform:scale(1)}.other-user-info .settings .settings-menu div{padding:13px 5px;border-bottom:1px solid var(--other-msg-bg)}.other-user-info .settings .settings-menu div:last-child{border:none} 2 | -------------------------------------------------------------------------------- /client/src/components/OtherUserInfo/OtherUserInfo.scss: -------------------------------------------------------------------------------- 1 | .other-user-info{ 2 | position: relative; 3 | display: flex; 4 | flex-direction: row; 5 | align-items: center; 6 | padding: 5px 10px; 7 | box-shadow: 1px 1px 3px var(--other-msg-bg); 8 | z-index: 1; 9 | 10 | .back-button{ 11 | // border: 1px solid black; 12 | cursor: pointer; 13 | } 14 | 15 | .image{ 16 | width: 40px; 17 | height: 40px; 18 | border: 1px solid var(--other-msg-bg); 19 | border-radius: 50%; 20 | margin: 0 10px; 21 | } 22 | 23 | .name{ 24 | font-family: monospace; 25 | font-size: 16px; 26 | } 27 | 28 | .settings{ 29 | margin: 0 10px 0 auto; 30 | cursor: pointer; 31 | font-family: monospace; 32 | 33 | .settings-menu{ 34 | position: absolute; 35 | right: 20px; 36 | background: white; 37 | box-shadow: 1px 1px 3px var(--other-msg-bg); 38 | display: flex; 39 | flex-direction: column; 40 | border-radius: 7px; 41 | transform: scale(0); 42 | transform-origin: top right; 43 | transition: transform 200ms ease-in-out; 44 | 45 | &.active{ 46 | transform: scale(1); 47 | } 48 | 49 | div{ 50 | padding: 13px 5px; 51 | border-bottom: 1px solid var(--other-msg-bg); 52 | 53 | &:last-child{ 54 | border: none; 55 | } 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /client/src/components/SelectionToolBar/SelectionToolBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DeleteIcon from '@material-ui/icons/Delete'; 3 | import ReplyIcon from '@material-ui/icons/Reply'; 4 | import { useCurrentUser } from '../../contexts/CurrentUserProvider' 5 | import { useChatDB } from '../../contexts/ChatDBProvider' 6 | import { useSelectionToolBarStatus } from '../../contexts/SelectionToolBarStatusProvider' 7 | import { useSelectedMessages } from '../../contexts/SelectedMessagesProvider' 8 | import { chatAPI } from '../../Apis/chatApi' 9 | import { useSocket } from '../../contexts/SocketProvider' 10 | import './SelectionToolBar.min.css' 11 | 12 | const SelectionToolBar = () => { 13 | const [_isToolbarActive, setToolbarActive] = useSelectionToolBarStatus() 14 | const [selectedMessages, setSelectedMessages] = useSelectedMessages() 15 | const [chatDB, setChatDB] = useChatDB() 16 | const [currentUser] = useCurrentUser() 17 | const socket = useSocket() 18 | 19 | const clearSelection = () => { 20 | setSelectedMessages(new Set()) 21 | } 22 | 23 | const closeToolBar = () => { 24 | setToolbarActive(false) 25 | clearSelection() 26 | } 27 | 28 | const getChatDB = async () => { 29 | console.log("***Fetching chat database***") 30 | let userID = currentUser.id 31 | let response = await chatAPI.get(`/chats/${userID}`) 32 | console.log("***Got chat DB***") 33 | console.log(response.data) 34 | setChatDB(response.data) 35 | } 36 | 37 | const deleteMessage = async () => { 38 | console.log("Deleting messages with IDs:", selectedMessages) 39 | // let response = await chatAPI.get(`/delete/${JSON.stringify([...selectedMessages])}`) 40 | // console.log("****************************************") 41 | // console.log(`SelectedMessages[0]: ${[...selectedMessages][0]}`) 42 | let msgObject = chatDB.filter(msg => msg.id === [...selectedMessages][0]) 43 | console.log(msgObject) 44 | let response = await chatAPI.post('/delete', { 45 | // stringfiedIDs: JSON.stringify([...selectedMessages]), 46 | messageIDs: [...selectedMessages], 47 | senderID: msgObject.senderID, 48 | receiverID: msgObject.receiverID 49 | 50 | }) 51 | console.log(response.data) 52 | closeToolBar() 53 | if (response.data.status === 1) { 54 | let msgObject = chatDB.filter(msg => msg.id === [...selectedMessages][0])[0] 55 | let receiverID = msgObject.receiverID 56 | let senderID = msgObject.senderID 57 | 58 | // console.log("****************************************") 59 | // console.log(msgObject) 60 | // Object.keys(msgObject).forEach(key => console.log(key, msgObject[key])) 61 | // console.log({ receiverID }) 62 | console.log("Deleted messages!") 63 | console.log(`Emitting "deleted-msgs-ack to ${receiverID} and ${senderID}`) 64 | socket.emit("deleted-msgs-ack", receiverID) 65 | socket.emit("deleted-msgs-ack", senderID) 66 | 67 | // getChatDB() 68 | } else { 69 | alert("Failed to delete messages!") 70 | } 71 | } 72 | 73 | return ( 74 |
75 |
78 | 79 |
80 |
81 | 82 |
83 |
Cancel
86 |
87 | ); 88 | } 89 | 90 | export default SelectionToolBar; 91 | -------------------------------------------------------------------------------- /client/src/components/SelectionToolBar/SelectionToolBar.min.css: -------------------------------------------------------------------------------- 1 | .selection-toolbar{position:fixed;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:75%;padding:20px 30px;margin:10px 0;background-color:var(--secondary-bg);-webkit-box-shadow:1px 1px 5px var(--other-msg-bg);box-shadow:1px 1px 5px var(--other-msg-bg);font-family:monospace;-webkit-transform:translateY(-72%) translateX(-10px);transform:translateY(-72%) translateX(-10px);z-index:99}.selection-toolbar div{cursor:pointer;margin:0 15px}.selection-toolbar .cancel{background-color:var(--other-msg-bg);padding:7px;border-radius:2px;color:white;margin-left:auto}.selection-toolbar .delete{color:#cc3b3b}.selection-toolbar .forward{-webkit-transform:rotateY(180deg);transform:rotateY(180deg)} 2 | -------------------------------------------------------------------------------- /client/src/components/SelectionToolBar/SelectionToolBar.scss: -------------------------------------------------------------------------------- 1 | .selection-toolbar 2 | { 3 | position: fixed; 4 | display: flex; 5 | flex-direction: row; 6 | // justify-content: space-between; 7 | align-items: center; 8 | width: 75%; 9 | padding: 20px 30px; 10 | margin: 10px 0; 11 | // top: 0; 12 | background-color: var(--secondary-bg); 13 | box-shadow: 1px 1px 5px var(--other-msg-bg); 14 | font-family: monospace; 15 | transform: translateY(-72%) translateX(-10px); 16 | z-index: 99; 17 | 18 | div{ 19 | cursor: pointer; 20 | margin: 0 15px; 21 | } 22 | 23 | .cancel{ 24 | background-color: var(--other-msg-bg); 25 | padding: 7px; 26 | border-radius: 2px; 27 | color: white; 28 | margin-left: auto; 29 | } 30 | 31 | .delete{ 32 | color: rgb(204, 59, 59); 33 | } 34 | 35 | .forward{ 36 | transform: rotateY(180deg); 37 | } 38 | } -------------------------------------------------------------------------------- /client/src/components/User/User.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './User.min.css' 3 | 4 | const User = ({ user, handleClick, active, online, newMsgCount }) => { 5 | // console.log(user, active) 6 | 7 | let className = `user ${active ? "active" : ""} ${online ? "online" : ""}` 8 | let username = user.name ? user.name : user.id 9 | return ( 10 |
11 |
12 |
{username}
13 | {(newMsgCount && newMsgCount !== 0) &&
{newMsgCount}
} 14 |
15 | ) 16 | } 17 | 18 | export default User; 19 | -------------------------------------------------------------------------------- /client/src/components/User/User.min.css: -------------------------------------------------------------------------------- 1 | .user{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:10px;cursor:pointer;-webkit-transition:background 100ms ease-in-out, -webkit-box-shadow 200ms ease-in-out;transition:background 100ms ease-in-out, -webkit-box-shadow 200ms ease-in-out;transition:background 100ms ease-in-out, box-shadow 200ms ease-in-out;transition:background 100ms ease-in-out, box-shadow 200ms ease-in-out, -webkit-box-shadow 200ms ease-in-out;color:#4745B1;background-color:white;margin:2px 0}.user:first-child{margin:0}.user:hover{-webkit-box-shadow:1px 1px 2px var(--other-msg-bg);box-shadow:1px 1px 2px var(--other-msg-bg)}.user .image{position:relative;--size: 30px;width:var(--size);height:var(--size);border:1px solid var(--other-msg-bg);border-radius:50%;background-color:white}.user .new-msg-count{display:-ms-grid;display:grid;margin-left:auto;margin-right:25px;background-color:var(--other-msg-bg);color:white;border-radius:50%;padding:2px 6px;font-weight:bold;font-family:monospace}.user .name{position:relative;font-size:15px;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace;margin-left:10px}.user.active{background-color:var(--other-msg-bg);color:white}.user.active.online::after{background-color:white}.user.active .image{border:none}.user.online::after{position:absolute;content:'';width:8px;height:8px;border-radius:50%;background-color:var(--bg-color);right:10px}.loggedin-user-info .user .new-msg-count{display:none} 2 | -------------------------------------------------------------------------------- /client/src/components/User/User.scss: -------------------------------------------------------------------------------- 1 | .user{ 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | padding: 10px; 6 | cursor: pointer; 7 | transition: background 100ms ease-in-out, 8 | box-shadow 200ms ease-in-out; 9 | color: #4745B1; 10 | background-color: white; 11 | margin: 2px 0; 12 | 13 | &:first-child{ 14 | margin: 0; 15 | } 16 | 17 | &:hover{ 18 | box-shadow: 1px 1px 2px var(--other-msg-bg); 19 | } 20 | 21 | .image{ 22 | position: relative; 23 | --size: 30px; 24 | width: var(--size); 25 | height: var(--size); 26 | border: 1px solid var(--other-msg-bg); 27 | border-radius: 50%; 28 | background-color: white; 29 | } 30 | 31 | .new-msg-count{ 32 | display: grid; 33 | margin-left: auto; 34 | margin-right: 25px; 35 | background-color: var(--other-msg-bg); 36 | color: white; 37 | border-radius: 50%; 38 | padding: 2px 6px; 39 | font-weight: bold; 40 | font-family: monospace; 41 | } 42 | 43 | .name { 44 | position: relative; 45 | font-size: 15px; 46 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 47 | margin-left: 10px; 48 | // text-transform: capitalize; 49 | } 50 | 51 | &.active{ 52 | background-color: var(--other-msg-bg); 53 | color: white; 54 | 55 | &.online{ 56 | &::after{ 57 | background-color: white; 58 | } 59 | } 60 | 61 | .image{ 62 | border: none; 63 | } 64 | } 65 | 66 | &.online{ 67 | &::after{ 68 | position: absolute; 69 | content: ''; 70 | width: 8px; 71 | height: 8px; 72 | border-radius: 50%; 73 | background-color: var(--bg-color); 74 | right: 10px; 75 | } 76 | } 77 | } 78 | 79 | .loggedin-user-info { 80 | .user{ 81 | .new-msg-count{ 82 | display: none; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /client/src/components/UserChats/UserChats.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useRef } from 'react'; 3 | import { useCurrentUser } from '../../contexts/CurrentUserProvider' 4 | import { useActiveConversation } from '../../contexts/ActiveConversationProvider' 5 | import { useChatDB } from '../../contexts/ChatDBProvider' 6 | import { useSocket } from '../../contexts/SocketProvider' 7 | import { chatAPI } from '../../Apis/chatApi' 8 | import CheckCircleIcon from '@material-ui/icons/CheckCircle' 9 | import CachedIcon from '@material-ui/icons/Cached' 10 | import CancelIcon from '@material-ui/icons/Cancel' 11 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; 12 | import { useActiveMessageOptionsID } from '../../contexts/ActiveMessageOptionsProvider.jsx' 13 | import SelectionToolBar from '../SelectionToolBar/SelectionToolBar' 14 | import { useSelectedMessages } from '../../contexts/SelectedMessagesProvider' 15 | import ContactsIcon from '@material-ui/icons/Contacts';// import ArrowRightAltIcon from '@material-ui/icons/ArrowRightAlt'; 16 | import { MSG_PENDING, MSG_SENT, MSG_NOT_SENT } from '../NewMsgForm/NewMsgForm' 17 | import { useSelectionToolBarStatus } from '../../contexts/SelectionToolBarStatusProvider' 18 | 19 | import './UserChats.min.css' 20 | 21 | // Probably not an efficient way to do this! 22 | export const toggleSidebar = (e) => { 23 | let sidebar = document.querySelector('.window-sidebar') 24 | // let toggleButton = document.querySelector('.toggle-sidebar') 25 | 26 | sidebar.classList.toggle('active') 27 | // toggleButton.classList.toggle('move-left') 28 | } 29 | 30 | const UserChats = () => { 31 | const [chatDB, setChatDB] = useChatDB() 32 | const [userChats, setUserChats] = useState([]) 33 | const [currentUser] = useCurrentUser() 34 | const [activeConversationID] = useActiveConversation() 35 | const [msgIDWithActiveOptions, setMsgIDWithActiveOptions] = useActiveMessageOptionsID() 36 | const [isToolbarActive, _setToolbarActive] = useSelectionToolBarStatus() 37 | 38 | const socket = useSocket() 39 | const chatsRef = useRef() 40 | let prevDate = undefined 41 | // const toggleSidebarRef = useRef() 42 | 43 | console.log("rendering USER_CHATS") 44 | // console.log({ userChats }) 45 | // console.log({ chatDB }) 46 | 47 | const getChatDB = async () => { 48 | console.log("***Fetching chat database***") 49 | let userID = currentUser.id 50 | let response = await chatAPI.get(`/chats/${userID}`) 51 | // console.log("***Got char DB***") 52 | // console.log(response.data) 53 | setChatDB(response.data) 54 | } 55 | 56 | const scrollToLatestMsg = () => { 57 | let chatsWindowProto = chatsRef.current.__proto__ 58 | let scrollTopMax 59 | if (!("scrollTopMax" in chatsWindowProto)) { 60 | // console.log("Hiiii:", chatsRef.current.scrollHeight, chatsRef.current.clientHeight) 61 | scrollTopMax = chatsRef.current.scrollHeight - chatsRef.current.clientHeight 62 | } else { 63 | scrollTopMax = chatsRef.current.scrollTopMax 64 | } 65 | chatsRef.current.scroll(0, scrollTopMax) 66 | } 67 | 68 | useEffect(() => { 69 | getChatDB() 70 | // toggleSidebarRef.current.addEventListener('click', toggleSidebar) 71 | }, []) 72 | 73 | useEffect(() => { 74 | scrollToLatestMsg() 75 | }, [userChats]) 76 | 77 | const extractConversations = () => { 78 | console.log("Extracting convos") 79 | // console.log("Chat DB Length: ", chatDB.length) 80 | let conversations = chatDB.filter(msgObject => { 81 | // console.log(msgObject.senderID, msgObject.receiverID, currentUser.id, activeConversationID, msgObject.msgBody) 82 | // if ((msgObject.senderID === currentUser.id && msgObject.receiverID === activeConversationID) 83 | // || (msgObject.receiverID === currentUser.id && msgObject.senderID === activeConversationID)) { 84 | // console.log("Meets") 85 | // } 86 | return (msgObject.senderID === currentUser.id && msgObject.receiverID === activeConversationID) 87 | || (msgObject.receiverID === currentUser.id && msgObject.senderID === activeConversationID) 88 | }) 89 | 90 | setUserChats(conversations) 91 | } 92 | 93 | useEffect(() => { 94 | extractConversations() 95 | }, [activeConversationID, chatDB, currentUser.id, socket]) 96 | 97 | 98 | console.log({ msgIDWithActiveOptions }) 99 | const toggleMenu = (msgID) => { 100 | console.log("\tInside toggleMenu") 101 | console.log("Message Id: ", msgID) 102 | console.log("Actoive message options: ", msgIDWithActiveOptions) 103 | if (msgID === msgIDWithActiveOptions) { 104 | setMsgIDWithActiveOptions(null) 105 | } else { 106 | setMsgIDWithActiveOptions(msgID) 107 | } 108 | 109 | return 110 | } 111 | 112 | return ( 113 |
114 | {isToolbarActive && } 115 | {activeConversationID ? (userChats.map(chatObject => { 116 | let returnElement = (prevDate === undefined || prevDate !== chatObject.date) 117 | ? ( 118 | <> 119 | 120 | toggleMenu(chatObject.id)} /> 121 | 122 | ) 123 | : ( toggleMenu(chatObject.id)} />) 124 | prevDate = chatObject.date 125 | return returnElement 126 | })) 127 | : } 128 |
129 | ); 130 | } 131 | 132 | const Message = ({ msgObject, toggleMenu }) => { 133 | const [currentUser] = useCurrentUser() 134 | const [isToolbarActive, setToolbarActive] = useSelectionToolBarStatus() 135 | const [msgIDWithActiveOptions, _setMsgIDWithActiveOptions] = useActiveMessageOptionsID() 136 | const [selectedMessages, setSelectedMessages] = useSelectedMessages() 137 | 138 | let isSender = (msgObject.senderID === currentUser.id) 139 | let msgStatus = msgObject.status 140 | 141 | console.log("rendering Message component") 142 | 143 | // console.log("selectedMessages: ", selectedMessages) 144 | 145 | const selectMessage = (msgID) => { 146 | if (selectedMessages.has(msgID)) { 147 | selectedMessages.delete(msgID) 148 | setSelectedMessages(selectedMessages) 149 | } 150 | else { 151 | setSelectedMessages(new Set([...selectedMessages, msgID])) 152 | } 153 | } 154 | 155 | return ( 156 |
157 | {isToolbarActive && 158 |
159 | selectMessage(msgObject.id)} /> 160 |
161 | } 162 |
163 | {/*
From: {msgObject.senderID}
*/} 164 |
{msgObject.msgBody}
165 |
{msgObject.time}
166 | {isSender && } 167 | 168 | 169 |
170 |
171 | ) 172 | } 173 | 174 | const MessageOptions = ({ msgObject, msgIDWithActiveOptions, msgID }) => { 175 | const [isToolbarActive, setToolbarActive] = useSelectionToolBarStatus() 176 | const [selectedMessages, setSelectedMessages] = useSelectedMessages() 177 | 178 | console.log("Rendering message options") 179 | console.log({ msgIDWithActiveOptions, msgID }) 180 | 181 | const forwardMessage = () => { 182 | console.log("Forwarding message with ID:", msgID) 183 | setToolbarActive(false) 184 | setSelectedMessages(new Set()) 185 | } 186 | 187 | const copyMessage = () => { 188 | // console.log("Copying message with ID:", msgID) 189 | let clipboardPromise = navigator.clipboard.writeText(msgObject.msgBody) 190 | clipboardPromise 191 | .then(() => { 192 | console.log("Copied message with ID:", msgID) 193 | }) 194 | .catch(() => { 195 | console.log("Error copying message with ID:", msgID) 196 | }) 197 | } 198 | 199 | const activateToolBar = () => { 200 | console.log("Activating toolbar!") 201 | setToolbarActive(!isToolbarActive) 202 | } 203 | 204 | 205 | return ( 206 |
207 |
Forward
208 |
Copy
209 |
More
210 |
211 | ) 212 | } 213 | 214 | const MsgStatusIcon = ({ status }) => { 215 | if (status === MSG_SENT) 216 | return 217 | else if (status === MSG_NOT_SENT) 218 | return 219 | else if (status === MSG_PENDING) 220 | return 221 | } 222 | 223 | const SelectAChat = () => { 224 | return ( 225 |
226 | Please select a conversation 227 |
228 | ) 229 | } 230 | 231 | // const datesDifference = (date1, date2) => { 232 | // return Math.abs((date1 - date2) / (1000 * 60 * 60 * 24)) 233 | // } 234 | 235 | const DateLabel = ({ date }) => { 236 | // let dateString = null 237 | return ( 238 |

{date}

239 | ) 240 | } 241 | 242 | export default UserChats; 243 | -------------------------------------------------------------------------------- /client/src/components/UserChats/UserChats.min.css: -------------------------------------------------------------------------------- 1 | .message-element{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.message-element.sender{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;margin-left:auto;margin-right:4px}.message-element.sender .select-box{margin:0px 7px}.msg{position:relative;max-width:250px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;background-color:var(--other-msg-bg);color:var(--msg-text);padding:5px 15px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;font-family:monospace;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;margin:0 0 4px 4px;border-bottom-left-radius:15px;border-top-right-radius:15px}.msg .msg-body{margin-bottom:3px;word-wrap:anywhere}.msg .time{margin-left:auto;margin-right:10px;font-size:10px}.msg.sender{background-color:var(--secondary-bg);margin-left:auto;margin-right:4px;border-top-right-radius:0;border-bottom-right-radius:15px;border-top-left-radius:15px;border-bottom-left-radius:0;color:var(--other-msg-bg);position:relative}.msg.sender .msg-options{-webkit-transform-origin:top right;transform-origin:top right}.msg.sender .msg-options{left:unset;right:100%}.msg.sender .msg-options-icon .MuiSvgIcon-root{fill:#4fd64f !important}.msg.sender .MuiSvgIcon-root{position:absolute;width:15px;height:15px;right:0;bottom:0}.msg.sender.msg-sent .MuiSvgIcon-root{fill:#4fd64f}.msg.sender.pending .MuiSvgIcon-root{fill:#949494}.msg.sender.msg-not-sent .MuiSvgIcon-root{fill:#ff3636}.msg .msg-options-icon{position:absolute;top:0;left:0;bottom:0;right:0;border-radius:inherit;background:rgba(0,0,0,0);-webkit-transition:background 200ms ease-in-out;transition:background 200ms ease-in-out}.msg .msg-options-icon .MuiSvgIcon-root{position:absolute;right:2px;top:0;opacity:0;-webkit-transition:opacity 200ms ease-in-out;transition:opacity 200ms ease-in-out;cursor:pointer}.msg:hover .msg-options-icon{background:rgba(0,0,0,0.4)}.msg:hover .msg-options-icon .MuiSvgIcon-root{opacity:1}.msg .msg-options{position:absolute;background:var(--secondary-bg);-webkit-box-shadow:1px 1px 5px var(--other-msg-bg);box-shadow:1px 1px 5px var(--other-msg-bg);padding:10px 0;color:black;left:100%;z-index:999;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;border-radius:7px;-webkit-transition:-webkit-transform 200ms ease-in-out;transition:-webkit-transform 200ms ease-in-out;transition:transform 200ms ease-in-out;transition:transform 200ms ease-in-out, -webkit-transform 200ms ease-in-out;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:top left;transform-origin:top left}.msg .msg-options.active{-webkit-transform:scale(1);transform:scale(1)}.msg .msg-options .option{border-bottom:1px solid var(--other-msg-bg);padding:10px 30px;cursor:pointer}.msg .msg-options .option:hover{opacity:0.7}.msg .msg-options .option:last-child{border:none}.chats{padding:40px 10px 10px 10px;position:relative;scroll-behavior:smooth;overflow-y:auto;overflow-x:hidden}.chats .toggle-sidebar{position:fixed;display:none;z-index:99;top:0;place-items:center;width:30px;height:30px;opacity:0.8;background-color:var(--other-msg-bg);-webkit-transform:translate(-10px, 60px);transform:translate(-10px, 60px);cursor:pointer;-webkit-transition:-webkit-transform 200ms ease-in-out;transition:-webkit-transform 200ms ease-in-out;transition:transform 200ms ease-in-out;transition:transform 200ms ease-in-out, -webkit-transform 200ms ease-in-out}.chats .toggle-sidebar .MuiSvgIcon-root{fill:white;width:30px;height:30px}.chats .toggle-sidebar.move-left{-webkit-transform:translate(260px, 60px);transform:translate(260px, 60px)}.select-chat{font-size:20px;font-family:monospace;position:absolute;top:50%;left:50%;width:300px;height:300px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);background-color:var(--bg-color);border-radius:50%;padding:50px;text-align:center;display:-ms-grid;display:grid;place-items:center;color:white}.date-label{font-size:12px;font-family:monospace;background-color:var(--secondary-bg);padding:5px 0;margin:5px 0 10px 0} 2 | -------------------------------------------------------------------------------- /client/src/components/UserChats/UserChats.scss: -------------------------------------------------------------------------------- 1 | .message-element{ 2 | position: relative; 3 | display: flex; 4 | flex-direction: row; 5 | // border: 1px solid red; 6 | width: fit-content; 7 | 8 | &.sender{ 9 | flex-direction: row-reverse; 10 | margin-left: auto; 11 | margin-right: 4px; 12 | 13 | .select-box{ 14 | margin: 0px 7px; 15 | } 16 | } 17 | } 18 | 19 | .msg{ 20 | position: relative; 21 | max-width: 250px; 22 | width: fit-content; 23 | height: fit-content; 24 | background-color: var(--other-msg-bg); 25 | color: var(--msg-text); 26 | padding: 5px 15px; 27 | display: flex; 28 | flex-direction: column; 29 | font-family: monospace; 30 | align-items: flex-start; 31 | margin: 0 0 4px 4px; 32 | border-bottom-left-radius: 15px; 33 | border-top-right-radius: 15px; 34 | 35 | .msg-body{ 36 | margin-bottom: 3px; 37 | word-wrap: anywhere; 38 | } 39 | 40 | .time{ 41 | margin-left: auto; 42 | margin-right: 10px; 43 | font-size: 10px; 44 | } 45 | 46 | &.sender{ 47 | background-color: var(--secondary-bg); 48 | margin-left: auto; 49 | margin-right: 4px; 50 | border-top-right-radius: 0; 51 | border-bottom-right-radius: 15px; 52 | border-top-left-radius: 15px; 53 | border-bottom-left-radius: 0; 54 | color: var(--other-msg-bg); 55 | position: relative; 56 | 57 | .msg-options{ 58 | transform-origin: top right; 59 | } 60 | 61 | .msg-options{ 62 | left: unset; 63 | right: 100%; 64 | } 65 | 66 | .msg-options-icon{ 67 | .MuiSvgIcon-root{ 68 | fill: rgb(79, 214, 79) !important; 69 | } 70 | } 71 | 72 | .MuiSvgIcon-root{ 73 | position: absolute; 74 | width: 15px; 75 | height: 15px; 76 | right: 0; 77 | bottom: 0; 78 | } 79 | 80 | &.msg-sent{ 81 | .MuiSvgIcon-root{ 82 | fill: rgb(79, 214, 79); 83 | } 84 | } 85 | 86 | &.pending{ 87 | .MuiSvgIcon-root{ 88 | fill: rgb(148, 148, 148); 89 | } 90 | } 91 | 92 | &.msg-not-sent{ 93 | .MuiSvgIcon-root{ 94 | fill: rgb(255, 54, 54); 95 | } 96 | } 97 | } 98 | 99 | .msg-options-icon{ 100 | position: absolute; 101 | top: 0; 102 | left: 0; 103 | bottom: 0; 104 | right: 0; 105 | border-radius: inherit; 106 | background: rgba(black, 0); 107 | transition: background 200ms ease-in-out; 108 | 109 | .MuiSvgIcon-root{ 110 | position: absolute; 111 | right: 2px; 112 | top: 0; 113 | opacity: 0; 114 | transition: opacity 200ms ease-in-out; 115 | cursor: pointer; 116 | } 117 | } 118 | 119 | &:hover{ 120 | .msg-options-icon{ 121 | background: rgba(black, 0.4); 122 | 123 | .MuiSvgIcon-root{ 124 | opacity: 1; 125 | } 126 | } 127 | } 128 | 129 | .msg-options{ 130 | position: absolute; 131 | background: var(--secondary-bg); 132 | box-shadow: 1px 1px 5px var(--other-msg-bg); 133 | padding: 10px 0; 134 | color: black; 135 | left: 100%; 136 | z-index: 999; 137 | display: flex; 138 | flex-direction: column; 139 | border-radius: 7px; 140 | transition: transform 200ms ease-in-out; 141 | transform: scale(0); 142 | transform-origin: top left; 143 | 144 | &.active{ 145 | transform: scale(1); 146 | } 147 | 148 | .option{ 149 | border-bottom: 1px solid var(--other-msg-bg); 150 | padding: 10px 30px; 151 | cursor: pointer; 152 | 153 | &:hover{ 154 | opacity: 0.7; 155 | } 156 | 157 | &:last-child{ 158 | border: none; 159 | } 160 | } 161 | } 162 | } 163 | 164 | .chats{ 165 | padding: 40px 10px 10px 10px; 166 | position: relative; 167 | // border: 3px solid yellow; 168 | scroll-behavior: smooth; 169 | overflow-y: auto; 170 | overflow-x: hidden; 171 | 172 | .toggle-sidebar{ 173 | position: fixed; 174 | display: none; 175 | // display: grid; 176 | z-index: 99; 177 | top: 0; 178 | place-items: center; 179 | width: 30px; 180 | height: 30px; 181 | opacity: 0.8; 182 | background-color: var(--other-msg-bg); 183 | transform: translate(-10px, 60px); 184 | cursor: pointer; 185 | transition: transform 200ms ease-in-out; 186 | 187 | .MuiSvgIcon-root{ 188 | fill: white; 189 | width: 30px; 190 | height: 30px; 191 | } 192 | 193 | &.move-left{ 194 | transform: translate(260px, 60px); 195 | } 196 | } 197 | } 198 | 199 | .select-chat{ 200 | font-size: 20px; 201 | font-family: monospace; 202 | position: absolute; 203 | top: 50%; 204 | left: 50%; 205 | width: 300px; 206 | height: 300px; 207 | transform: translate(-50%, -50%); 208 | background-color: var(--bg-color); 209 | border-radius: 50%; 210 | padding: 50px; 211 | text-align: center; 212 | display: grid; 213 | place-items: center; 214 | color: white; 215 | } 216 | 217 | .date-label{ 218 | font-size: 12px; 219 | font-family: monospace; 220 | background-color: var(--secondary-bg); 221 | padding: 5px 0; 222 | margin: 5px 0 10px 0; 223 | } -------------------------------------------------------------------------------- /client/src/components/UsersList/UsersList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useUsers } from '../../contexts/UsersProvider' 3 | import User from '../User/User.jsx' 4 | import { useActiveConversation } from '../../contexts/ActiveConversationProvider' 5 | import { useCurrentUser } from '../../contexts/CurrentUserProvider' 6 | import { useSocket } from '../../contexts/SocketProvider' 7 | import { chatAPI } from '../../Apis/chatApi' 8 | import { toggleSidebar } from '../UserChats/UserChats' 9 | import { useNewMessagesMap } from '../../contexts/NewMsgsMapProvider' 10 | import './UsersList.min.css' 11 | 12 | const UsersList = () => { 13 | const [users, setUsers] = useUsers() 14 | const [currentUser] = useCurrentUser() 15 | const [activeConversationID, setActiveConversationID] = useActiveConversation() 16 | const [onlineStatus, setOnlineStatus] = useState({}) 17 | const [newMsgsMap, setNewMsgMap] = useNewMessagesMap() 18 | const socket = useSocket() 19 | 20 | // console.log({ users }) 21 | // console.log("rendering USERS_LIST") 22 | 23 | const removeThisUserFromUsersList = (usersList, thisUser) => { 24 | let newUsersList = usersList.filter(user => user.id !== thisUser.id) 25 | setUsers(newUsersList) 26 | } 27 | 28 | const getUsers = async () => { 29 | try { 30 | let response = await chatAPI.get('/users') 31 | let usersList = response.data 32 | removeThisUserFromUsersList(usersList, currentUser) 33 | } catch (err) { 34 | console.log("Errot while getting users..", err) 35 | } 36 | } 37 | 38 | useEffect(() => { 39 | getUsers() 40 | }, [currentUser]) 41 | 42 | useEffect(() => { 43 | // Setup SocketIO events 44 | if (socket) { 45 | socket.on('online-statuses', (onlineStatusesString) => { 46 | let onlineStatuses = JSON.parse(onlineStatusesString) 47 | setOnlineStatus(onlineStatuses) 48 | console.log(`[online-statuses]: ${onlineStatuses}`) 49 | }) 50 | } 51 | 52 | }, [socket]) 53 | 54 | const handleClick = (e) => { 55 | console.log("Convo has been clicked", e.target) 56 | let userID = e.target.dataset.userId 57 | userID = userID 58 | console.log({ userID }) 59 | setActiveConversationID(userID) 60 | console.log({ activeConversationID }) 61 | setNewMsgMap({ ...newMsgsMap, [userID]: 0 }) 62 | toggleSidebar() // Actually, we should execute this statement in the mobile view. 63 | } 64 | 65 | return ( 66 |
67 | { 68 | (users.length === 0) ? ( 69 | 70 | ) : ( 71 | users.map(user => { 72 | return 80 | }) 81 | ) 82 | } 83 |
84 | ); 85 | } 86 | 87 | const LoadingUsers = () => { 88 | return ( 89 |
90 | ) 91 | } 92 | 93 | export default UsersList; 94 | -------------------------------------------------------------------------------- /client/src/components/UsersList/UsersList.min.css: -------------------------------------------------------------------------------- 1 | .users-list{overflow-y:auto;position:relative;height:100%}.users-list .loading-users{position:absolute;width:30px;height:30px;top:50%;left:45%;-webkit-transform:translateY(-50%);transform:translateY(-50%);border-bottom:none;border-top:none;border-radius:50%;-webkit-animation:loading 800ms cubic-bezier(0.69, -0.66, 0, 1.64) infinite alternate;animation:loading 800ms cubic-bezier(0.69, -0.66, 0, 1.64) infinite alternate}@-webkit-keyframes loading{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(720deg);transform:rotate(720deg)}}@keyframes loading{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(720deg);transform:rotate(720deg)}} 2 | -------------------------------------------------------------------------------- /client/src/components/UsersList/UsersList.scss: -------------------------------------------------------------------------------- 1 | .users-list{ 2 | overflow-y: auto; 3 | position: relative; 4 | // border: 4px solid yellow; 5 | height: 100%; 6 | 7 | .loading-users{ 8 | position: absolute; 9 | width: 30px; 10 | height: 30px; 11 | top: 50%; 12 | left: 45%; 13 | transform: translateY(-50%); 14 | // background-color:red; 15 | // border: 2px solid white; 16 | border-bottom: none; 17 | border-top: none; 18 | border-radius: 50%; 19 | animation: loading 800ms cubic-bezier(.69,-0.66,0,1.64) infinite alternate; 20 | } 21 | } 22 | 23 | @keyframes loading { 24 | from { 25 | transform: rotate(0deg); 26 | } 27 | to{ 28 | transform: rotate(720deg); 29 | } 30 | } -------------------------------------------------------------------------------- /client/src/contexts/ActiveConversationProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, createContext } from 'react'; 2 | 3 | const ActiveConversationContext = createContext() 4 | 5 | export const useActiveConversation = () => { 6 | return useContext(ActiveConversationContext) 7 | } 8 | 9 | const ActiveConversationProvider = ({ children }) => { 10 | const [activeConversationID, setActiveConversationID] = useState(null) 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | export default ActiveConversationProvider; 20 | -------------------------------------------------------------------------------- /client/src/contexts/ActiveMessageOptionsProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, createContext } from 'react'; 2 | 3 | const ActiveMessageOptionsContext = createContext() 4 | 5 | export const useActiveMessageOptionsID = () => { 6 | return useContext(ActiveMessageOptionsContext) 7 | } 8 | 9 | const ActiveMessageOptionsProvider = ({ children }) => { 10 | const [msgIDWithActiveOptions, setMsgIDWithActiveOptions] = useState(null) 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | export default ActiveMessageOptionsProvider; 20 | -------------------------------------------------------------------------------- /client/src/contexts/ChatDBProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, createContext } from 'react'; 2 | // import { useSessionStorage } from '../hooks/useSessionStorage' 3 | 4 | const ChatDBContext = createContext() 5 | 6 | export const useChatDB = () => { 7 | return useContext(ChatDBContext) 8 | } 9 | 10 | const ChatDBProvider = ({ children }) => { 11 | const [chatDB, setChatDB] = useState([]) 12 | 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | 20 | export default ChatDBProvider; 21 | 22 | 23 | // const messagesDB = [ 24 | // { 25 | // id: 1, 26 | // senderID: '2', 27 | // receiverID: '1', 28 | // msgBody: "ABCD", 29 | // time: '12:00PM', 30 | // }, 31 | // { 32 | // id: 2, 33 | // senderID: '2', 34 | // receiverID: '3', 35 | // msgBody: "EFGH", 36 | // time: '12:00PM', 37 | // }, 38 | // { 39 | // id: 3, 40 | // senderID: '1', 41 | // receiverID: '2', 42 | // msgBody: "IJKL", 43 | // time: '12:00PM', 44 | // }, 45 | // { 46 | // id: 4, 47 | // senderID: '4', 48 | // receiverID: '1', 49 | // msgBody: "MNOP", 50 | // time: '12:00PM', 51 | // }, 52 | // { 53 | // id: 1, 54 | // senderID: '2', 55 | // receiverID: '4', 56 | // msgBody: "QRST", 57 | // time: '12:00PM', 58 | // }, 59 | // { 60 | // id: 5, 61 | // senderID: '2', 62 | // receiverID: '3', 63 | // msgBody: "UVW", 64 | // time: '12:00PM', 65 | // }, 66 | // { 67 | // id: 6, 68 | // senderID: '1', 69 | // receiverID: '4', 70 | // msgBody: "XYZ", 71 | // time: '12:00PM', 72 | // }, 73 | // { 74 | // id: 7, 75 | // senderID: '5', 76 | // receiverID: '1', 77 | // msgBody: "Hell", 78 | // time: '12:00PM', 79 | // }, 80 | // ] -------------------------------------------------------------------------------- /client/src/contexts/CurrentUserProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext } from 'react'; 2 | import { useSessionStorage } from '../hooks/useSessionStorage' 3 | 4 | const currentUserContext = createContext() 5 | 6 | export const useCurrentUser = () => { 7 | return useContext(currentUserContext) 8 | } 9 | 10 | const CurrentUserProvider = ({ children }) => { 11 | const [currentUser, setCurrentUser] = useSessionStorage('currentUser', null) 12 | 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | 20 | export default CurrentUserProvider; 21 | -------------------------------------------------------------------------------- /client/src/contexts/NewMsgsMapProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, createContext } from 'react'; 2 | 3 | const NewMsgsMapContext = createContext() 4 | 5 | export const useNewMessagesMap = () => { 6 | return useContext(NewMsgsMapContext) 7 | } 8 | 9 | const NewMsgsMapProvider = ({ children }) => { 10 | const [newMsgsMap, setNewMsgMap] = useState({}) 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | export default NewMsgsMapProvider; 20 | -------------------------------------------------------------------------------- /client/src/contexts/SelectedMessagesProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, createContext } from 'react' 2 | 3 | const SelectedMessagesContext = createContext() 4 | 5 | export const useSelectedMessages = () => { 6 | return useContext(SelectedMessagesContext) 7 | } 8 | 9 | const SelectedMessagesProvider = ({ children }) => { 10 | const [selectedMessages, setSelectedMessages] = useState(new Set()) 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | export default SelectedMessagesProvider; -------------------------------------------------------------------------------- /client/src/contexts/SelectionToolBarStatusProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, createContext } from 'react'; 2 | 3 | const SelectionToolBarStatusContext = createContext() 4 | 5 | export const useSelectionToolBarStatus = () => { 6 | return useContext(SelectionToolBarStatusContext) 7 | } 8 | 9 | const SelectionToolBarStatusProvider = ({ children }) => { 10 | const [isToolbarActive, setToolbarActive] = useState(false) 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | export default SelectionToolBarStatusProvider; 20 | -------------------------------------------------------------------------------- /client/src/contexts/SidebarRefProvider.jsx: -------------------------------------------------------------------------------- 1 | // import React, { useContext, createContext, useRef } from 'react'; 2 | 3 | // const SidebarRefContext = createContext() 4 | 5 | // export const useSidebarRef = () => { 6 | // return useContext(SidebarRefContext) 7 | // } 8 | 9 | // const SidebarRefProvider = ({ children }) => { 10 | // // const sidebarRef = useRef() 11 | // const nice = 1 12 | 13 | // return ( 14 | // 15 | // {children} 16 | // 17 | // ); 18 | // } 19 | 20 | // export default SidebarRefProvider; 21 | -------------------------------------------------------------------------------- /client/src/contexts/SidebarStatusProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState } from 'react'; 2 | 3 | const SidebarStatusContext = createContext() 4 | 5 | export const useSidebarStatus = () => { 6 | return useContext(SidebarStatusContext) 7 | } 8 | 9 | const SidebarStatusProvider = ({ children }) => { 10 | const [sidebarActive, setSidebarActive] = useState(false) 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | export default SidebarStatusProvider; 20 | -------------------------------------------------------------------------------- /client/src/contexts/SocketProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, createContext, useState, useEffect } from 'react'; 2 | import socketIOClient from 'socket.io-client' 3 | 4 | const SocketContext = createContext() 5 | 6 | export const useSocket = () => { 7 | return useContext(SocketContext) 8 | } 9 | 10 | // const SOCKET_IO_SERVER = "http://localhost:3000" 11 | const SOCKET_IO_SERVER = "https://yml-chat-app.herokuapp.com/" 12 | 13 | const SocketProvider = ({ children }) => { 14 | const [socket, setSocket] = useState(undefined) 15 | 16 | useEffect(() => { 17 | setSocket(socketIOClient(SOCKET_IO_SERVER, { 18 | path: '/chat-app-socket.io' 19 | })) 20 | }, []) 21 | 22 | 23 | return ( 24 | 25 | {children} 26 | 27 | ); 28 | } 29 | 30 | export default SocketProvider; 31 | -------------------------------------------------------------------------------- /client/src/contexts/UserChatsProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, createContext } from 'react'; 2 | 3 | const UserChatsContext = createContext() 4 | 5 | export const useUserChats = () => { 6 | return useContext(UserChatsContext) 7 | } 8 | 9 | const UserChatsProvider = ({ children }) => { 10 | const [userChats, setUserChats] = useState([]) 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | } 18 | 19 | export default UserChatsProvider; 20 | -------------------------------------------------------------------------------- /client/src/contexts/UsersProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState } from 'react'; 2 | // import { useLocalStorage } from '../hooks/useLocalStorage' 3 | 4 | const usersContext = createContext() 5 | 6 | export const useUsers = () => { 7 | return useContext(usersContext) 8 | } 9 | 10 | const UsersProvider = ({ children }) => { 11 | const [users, setUsers] = useState([]) 12 | 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | 20 | export default UsersProvider; 21 | 22 | 23 | // const usersDB = [ 24 | // { id: '1', name: 'Shine' }, 25 | // { id: '2', name: 'Buncy' }, 26 | // { id: '3', name: 'Hanu' }, 27 | // { id: '4', name: 'Duddu' }, 28 | // { id: '5', name: 'Oju' }, 29 | // { id: '6', name: 'Bajji' }, 30 | // { id: '7', name: 'Frienduu' }, 31 | // ] -------------------------------------------------------------------------------- /client/src/contexts/ViewProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState } from 'react'; 2 | 3 | const viewContext = createContext() 4 | 5 | export const useView = () => { 6 | return useContext(viewContext) 7 | } 8 | 9 | export const WEB_VIEW = "web-view" 10 | export const MOBILE_VIEW = "mobile-view" 11 | 12 | const ViewProvider = ({ children }) => { 13 | let currentView 14 | if (window.innerWidth < 750) { 15 | currentView = MOBILE_VIEW 16 | } else { 17 | currentView = WEB_VIEW 18 | } 19 | 20 | const [view, setView] = useState(currentView) 21 | 22 | window.addEventListener('resize', () => { 23 | if (window.innerWidth < 750) { 24 | setView(MOBILE_VIEW) 25 | } else { 26 | setView(WEB_VIEW) 27 | } 28 | }) 29 | 30 | return ( 31 | 32 | {children} 33 | 34 | ); 35 | } 36 | 37 | export default ViewProvider; -------------------------------------------------------------------------------- /client/src/hooks/useLocalStorage.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | const PREFIX = 'YML_chat-app-' 4 | 5 | export function useLocalStorage(key, initialValue) { 6 | const prefixedKey = PREFIX + key 7 | const [value, setValue] = useState(() => { 8 | const jsonValue = localStorage.getItem(prefixedKey) 9 | if (jsonValue != null) return JSON.parse(jsonValue) 10 | if (typeof initialValue === 'function') { 11 | return initialValue() 12 | } else { 13 | return initialValue 14 | } 15 | }) 16 | 17 | useEffect(() => { 18 | localStorage.setItem(prefixedKey, JSON.stringify(value)) 19 | }, [prefixedKey, value]) 20 | 21 | return [value, setValue] 22 | } -------------------------------------------------------------------------------- /client/src/hooks/useSessionStorage.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | const PREFIX = 'YML_chat-app-' 4 | 5 | export function useSessionStorage(key, initialValue) { 6 | const prefixedKey = PREFIX + key 7 | const [value, setValue] = useState(() => { 8 | const jsonValue = sessionStorage.getItem(prefixedKey) 9 | if (jsonValue != null) return JSON.parse(jsonValue) 10 | if (typeof initialValue === 'function') { 11 | return initialValue() 12 | } else { 13 | return initialValue 14 | } 15 | }) 16 | 17 | useEffect(() => { 18 | sessionStorage.setItem(prefixedKey, JSON.stringify(value)) 19 | }, [prefixedKey, value]) 20 | 21 | return [value, setValue] 22 | } -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | *, *::before, *::after{ 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 10 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 11 | sans-serif; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | overflow-x: hidden; 15 | /* background-color: #40c57c; */ 16 | } 17 | 18 | code { 19 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 20 | monospace; 21 | } 22 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-regex": { 8 | "version": "4.1.0", 9 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 10 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 11 | "dev": true 12 | }, 13 | "ansi-styles": { 14 | "version": "3.2.1", 15 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 16 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 17 | "dev": true, 18 | "requires": { 19 | "color-convert": "^1.9.0" 20 | } 21 | }, 22 | "camelcase": { 23 | "version": "5.3.1", 24 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 25 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 26 | "dev": true 27 | }, 28 | "chalk": { 29 | "version": "2.4.2", 30 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 31 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 32 | "dev": true, 33 | "requires": { 34 | "ansi-styles": "^3.2.1", 35 | "escape-string-regexp": "^1.0.5", 36 | "supports-color": "^5.3.0" 37 | }, 38 | "dependencies": { 39 | "supports-color": { 40 | "version": "5.5.0", 41 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 42 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 43 | "dev": true, 44 | "requires": { 45 | "has-flag": "^3.0.0" 46 | } 47 | } 48 | } 49 | }, 50 | "cliui": { 51 | "version": "5.0.0", 52 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", 53 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", 54 | "dev": true, 55 | "requires": { 56 | "string-width": "^3.1.0", 57 | "strip-ansi": "^5.2.0", 58 | "wrap-ansi": "^5.1.0" 59 | } 60 | }, 61 | "color-convert": { 62 | "version": "1.9.3", 63 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 64 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 65 | "dev": true, 66 | "requires": { 67 | "color-name": "1.1.3" 68 | } 69 | }, 70 | "color-name": { 71 | "version": "1.1.3", 72 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 73 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 74 | "dev": true 75 | }, 76 | "concurrently": { 77 | "version": "5.3.0", 78 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-5.3.0.tgz", 79 | "integrity": "sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==", 80 | "dev": true, 81 | "requires": { 82 | "chalk": "^2.4.2", 83 | "date-fns": "^2.0.1", 84 | "lodash": "^4.17.15", 85 | "read-pkg": "^4.0.1", 86 | "rxjs": "^6.5.2", 87 | "spawn-command": "^0.0.2-1", 88 | "supports-color": "^6.1.0", 89 | "tree-kill": "^1.2.2", 90 | "yargs": "^13.3.0" 91 | } 92 | }, 93 | "date-fns": { 94 | "version": "2.16.1", 95 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", 96 | "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==", 97 | "dev": true 98 | }, 99 | "decamelize": { 100 | "version": "1.2.0", 101 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 102 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 103 | "dev": true 104 | }, 105 | "emoji-regex": { 106 | "version": "7.0.3", 107 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 108 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 109 | "dev": true 110 | }, 111 | "error-ex": { 112 | "version": "1.3.2", 113 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 114 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 115 | "dev": true, 116 | "requires": { 117 | "is-arrayish": "^0.2.1" 118 | } 119 | }, 120 | "escape-string-regexp": { 121 | "version": "1.0.5", 122 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 123 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 124 | "dev": true 125 | }, 126 | "find-up": { 127 | "version": "3.0.0", 128 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 129 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 130 | "dev": true, 131 | "requires": { 132 | "locate-path": "^3.0.0" 133 | } 134 | }, 135 | "function-bind": { 136 | "version": "1.1.1", 137 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 138 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 139 | "dev": true 140 | }, 141 | "get-caller-file": { 142 | "version": "2.0.5", 143 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 144 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 145 | "dev": true 146 | }, 147 | "has": { 148 | "version": "1.0.3", 149 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 150 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 151 | "dev": true, 152 | "requires": { 153 | "function-bind": "^1.1.1" 154 | } 155 | }, 156 | "has-flag": { 157 | "version": "3.0.0", 158 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 159 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 160 | "dev": true 161 | }, 162 | "hosted-git-info": { 163 | "version": "2.8.8", 164 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", 165 | "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", 166 | "dev": true 167 | }, 168 | "is-arrayish": { 169 | "version": "0.2.1", 170 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 171 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 172 | "dev": true 173 | }, 174 | "is-core-module": { 175 | "version": "2.0.0", 176 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", 177 | "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", 178 | "dev": true, 179 | "requires": { 180 | "has": "^1.0.3" 181 | } 182 | }, 183 | "is-fullwidth-code-point": { 184 | "version": "2.0.0", 185 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 186 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 187 | "dev": true 188 | }, 189 | "json-parse-better-errors": { 190 | "version": "1.0.2", 191 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 192 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", 193 | "dev": true 194 | }, 195 | "locate-path": { 196 | "version": "3.0.0", 197 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 198 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 199 | "dev": true, 200 | "requires": { 201 | "p-locate": "^3.0.0", 202 | "path-exists": "^3.0.0" 203 | } 204 | }, 205 | "lodash": { 206 | "version": "4.17.20", 207 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", 208 | "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", 209 | "dev": true 210 | }, 211 | "normalize-package-data": { 212 | "version": "2.5.0", 213 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 214 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 215 | "dev": true, 216 | "requires": { 217 | "hosted-git-info": "^2.1.4", 218 | "resolve": "^1.10.0", 219 | "semver": "2 || 3 || 4 || 5", 220 | "validate-npm-package-license": "^3.0.1" 221 | } 222 | }, 223 | "p-limit": { 224 | "version": "2.3.0", 225 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 226 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 227 | "dev": true, 228 | "requires": { 229 | "p-try": "^2.0.0" 230 | } 231 | }, 232 | "p-locate": { 233 | "version": "3.0.0", 234 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 235 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 236 | "dev": true, 237 | "requires": { 238 | "p-limit": "^2.0.0" 239 | } 240 | }, 241 | "p-try": { 242 | "version": "2.2.0", 243 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 244 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 245 | "dev": true 246 | }, 247 | "parse-json": { 248 | "version": "4.0.0", 249 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 250 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 251 | "dev": true, 252 | "requires": { 253 | "error-ex": "^1.3.1", 254 | "json-parse-better-errors": "^1.0.1" 255 | } 256 | }, 257 | "path-exists": { 258 | "version": "3.0.0", 259 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 260 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 261 | "dev": true 262 | }, 263 | "path-parse": { 264 | "version": "1.0.6", 265 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 266 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 267 | "dev": true 268 | }, 269 | "pify": { 270 | "version": "3.0.0", 271 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 272 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", 273 | "dev": true 274 | }, 275 | "read-pkg": { 276 | "version": "4.0.1", 277 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", 278 | "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", 279 | "dev": true, 280 | "requires": { 281 | "normalize-package-data": "^2.3.2", 282 | "parse-json": "^4.0.0", 283 | "pify": "^3.0.0" 284 | } 285 | }, 286 | "require-directory": { 287 | "version": "2.1.1", 288 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 289 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 290 | "dev": true 291 | }, 292 | "require-main-filename": { 293 | "version": "2.0.0", 294 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 295 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 296 | "dev": true 297 | }, 298 | "resolve": { 299 | "version": "1.18.1", 300 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", 301 | "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", 302 | "dev": true, 303 | "requires": { 304 | "is-core-module": "^2.0.0", 305 | "path-parse": "^1.0.6" 306 | } 307 | }, 308 | "rxjs": { 309 | "version": "6.6.3", 310 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", 311 | "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", 312 | "dev": true, 313 | "requires": { 314 | "tslib": "^1.9.0" 315 | } 316 | }, 317 | "semver": { 318 | "version": "5.7.1", 319 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 320 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 321 | "dev": true 322 | }, 323 | "set-blocking": { 324 | "version": "2.0.0", 325 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 326 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 327 | "dev": true 328 | }, 329 | "spawn-command": { 330 | "version": "0.0.2-1", 331 | "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", 332 | "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", 333 | "dev": true 334 | }, 335 | "spdx-correct": { 336 | "version": "3.1.1", 337 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", 338 | "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", 339 | "dev": true, 340 | "requires": { 341 | "spdx-expression-parse": "^3.0.0", 342 | "spdx-license-ids": "^3.0.0" 343 | } 344 | }, 345 | "spdx-exceptions": { 346 | "version": "2.3.0", 347 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", 348 | "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", 349 | "dev": true 350 | }, 351 | "spdx-expression-parse": { 352 | "version": "3.0.1", 353 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", 354 | "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", 355 | "dev": true, 356 | "requires": { 357 | "spdx-exceptions": "^2.1.0", 358 | "spdx-license-ids": "^3.0.0" 359 | } 360 | }, 361 | "spdx-license-ids": { 362 | "version": "3.0.6", 363 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", 364 | "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", 365 | "dev": true 366 | }, 367 | "string-width": { 368 | "version": "3.1.0", 369 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 370 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 371 | "dev": true, 372 | "requires": { 373 | "emoji-regex": "^7.0.1", 374 | "is-fullwidth-code-point": "^2.0.0", 375 | "strip-ansi": "^5.1.0" 376 | } 377 | }, 378 | "strip-ansi": { 379 | "version": "5.2.0", 380 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 381 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 382 | "dev": true, 383 | "requires": { 384 | "ansi-regex": "^4.1.0" 385 | } 386 | }, 387 | "supports-color": { 388 | "version": "6.1.0", 389 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", 390 | "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", 391 | "dev": true, 392 | "requires": { 393 | "has-flag": "^3.0.0" 394 | } 395 | }, 396 | "tree-kill": { 397 | "version": "1.2.2", 398 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", 399 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", 400 | "dev": true 401 | }, 402 | "tslib": { 403 | "version": "1.14.1", 404 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 405 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 406 | "dev": true 407 | }, 408 | "validate-npm-package-license": { 409 | "version": "3.0.4", 410 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 411 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 412 | "dev": true, 413 | "requires": { 414 | "spdx-correct": "^3.0.0", 415 | "spdx-expression-parse": "^3.0.0" 416 | } 417 | }, 418 | "which-module": { 419 | "version": "2.0.0", 420 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 421 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", 422 | "dev": true 423 | }, 424 | "wrap-ansi": { 425 | "version": "5.1.0", 426 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", 427 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", 428 | "dev": true, 429 | "requires": { 430 | "ansi-styles": "^3.2.0", 431 | "string-width": "^3.0.0", 432 | "strip-ansi": "^5.0.0" 433 | } 434 | }, 435 | "y18n": { 436 | "version": "4.0.0", 437 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", 438 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", 439 | "dev": true 440 | }, 441 | "yargs": { 442 | "version": "13.3.2", 443 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", 444 | "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", 445 | "dev": true, 446 | "requires": { 447 | "cliui": "^5.0.0", 448 | "find-up": "^3.0.0", 449 | "get-caller-file": "^2.0.1", 450 | "require-directory": "^2.1.1", 451 | "require-main-filename": "^2.0.0", 452 | "set-blocking": "^2.0.0", 453 | "string-width": "^3.0.0", 454 | "which-module": "^2.0.0", 455 | "y18n": "^4.0.0", 456 | "yargs-parser": "^13.1.2" 457 | } 458 | }, 459 | "yargs-parser": { 460 | "version": "13.1.2", 461 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", 462 | "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", 463 | "dev": true, 464 | "requires": { 465 | "camelcase": "^5.0.0", 466 | "decamelize": "^1.2.0" 467 | } 468 | } 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app", 3 | "version": "1.0.0", 4 | "description": "Chat application for 'theyoungminds.org'", 5 | "main": "index.js", 6 | "scripts": { 7 | "client": "cd client && npm start", 8 | "server": "cd server && npm run devStart", 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "build-client": "cd client && npm install && npm run build", 11 | "build-server": "cd server && npm install", 12 | "start": "cd server && npm start", 13 | "chat-app": "concurrently --names \"CLIENT,SERVER\" -c \"blue.bold,green.bold\" \"npm run client\" \"npm run server\"", 14 | "heroku-postbuild": "npm run build-client; npm run build-server" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/Subhash3/chat-app.git" 19 | }, 20 | "author": "Subhash Sarangi", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/Subhash3/chat-app/issues" 24 | }, 25 | "homepage": "https://github.com/Subhash3/chat-app#readme", 26 | "devDependencies": { 27 | "concurrently": "^5.3.0" 28 | } 29 | } -------------------------------------------------------------------------------- /server/Models/chats.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const chatsSchema = new Schema({ 5 | // _id: mongoose.Types.ObjectId, 6 | id: String, 7 | senderID: String, 8 | receiverID: String, 9 | msgBody: String, 10 | time: String, 11 | date: String, 12 | status: String, 13 | }) 14 | 15 | const Chat = mongoose.model('chats', chatsSchema) 16 | 17 | module.exports = { 18 | ChatsModel: Chat 19 | } -------------------------------------------------------------------------------- /server/Models/users.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const leanersSchema = new Schema({ 5 | _id: mongoose.Types.ObjectId, 6 | "profileOrdering": Object, 7 | "Music": Object, 8 | "Sports": Object, 9 | "Arts": Object, 10 | "Others": Object, 11 | "interests": Object, 12 | "email": String, 13 | "password": String, 14 | "educations": Object, 15 | "skills": Object, 16 | "accomplishments": Object, 17 | "workExperiences": Object, 18 | "internshipExperiences": Object, 19 | "volunteerExperiences": Object, 20 | "__v": Number, 21 | "country": String, 22 | "date": Object, 23 | "faculty": Object, 24 | "gender": String, 25 | "instituteName": Object, 26 | "name": String, 27 | "phone": String, 28 | "pincode": Number, 29 | "state": String, 30 | "bio": String, 31 | "summury": String, 32 | "imageUrl": String 33 | 34 | }) 35 | 36 | const Learner = mongoose.model('learners', leanersSchema) 37 | 38 | module.exports = { 39 | LearnersModel: Learner 40 | } -------------------------------------------------------------------------------- /server/Routes/chat_router.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { LearnersModel } = require('../Models/users') 3 | const { ChatsModel } = require('../Models/chats') 4 | 5 | const router = express.Router() 6 | const welcomeMessage = "Welcome to Shine's chat app!" 7 | const mongoError = "Couldn't fetch database!" 8 | 9 | const getListOfUsers = async () => { 10 | // let usersList = await LearnersModel.find({}, { 11 | // email: true, 12 | // name: true 13 | // }) 14 | let usersList = await LearnersModel.aggregate([ 15 | { 16 | "$project": { 17 | "email": true, 18 | "name": true, 19 | "id": "$email" 20 | } 21 | } 22 | ]) 23 | return usersList 24 | } 25 | 26 | const getChats = async (userID) => { 27 | let chats = await ChatsModel.find({ 28 | $or: [ 29 | { "senderID": userID }, 30 | { "receiverID": userID } 31 | ] 32 | }, { 33 | _id: false 34 | }) 35 | return chats 36 | } 37 | 38 | const deleteMessages = (messageIDs) => { 39 | let mongoPromise = ChatsModel.deleteMany({ 40 | 'id': { 41 | '$in': [...messageIDs] 42 | } 43 | }) 44 | // db.chats.deleteMany({id: {$in: ["95863fc1-10ca-4755-98b1-68497736fda8", "c30c3467-370a-4108-a3e6-e00466e22ad0"]}} ) 45 | 46 | return mongoPromise 47 | } 48 | 49 | 50 | const deleteWholeConvo = (id1, id2) => { 51 | let mongoPromise = ChatsModel.deleteMany({ 52 | '$or': [ 53 | { 54 | '$and': [ 55 | { "senderID": id1 }, 56 | { "receiverID": id2 } 57 | ] 58 | }, 59 | { 60 | '$and': [ 61 | { "senderID": id2 }, 62 | { "receiverID": id1 } 63 | ] 64 | } 65 | ] 66 | }) 67 | return mongoPromise 68 | } 69 | 70 | router.get('/', (req, res) => { 71 | res.send({ "msg": welcomeMessage }) 72 | }) 73 | 74 | router.get('/users', (req, res) => { 75 | getListOfUsers() 76 | .then(users => { 77 | res.send(users) 78 | }) 79 | .catch(err => { 80 | res.send(mongoError + err) 81 | }) 82 | }) 83 | 84 | router.get('/chats', (req, res) => { 85 | res.status(403).send("Access denied!") 86 | }) 87 | 88 | router.get('/chats/:userID', (req, res) => { 89 | let userID = req.params.userID 90 | console.log(`Hit! (/chat-app/chats/${userID})`) 91 | getChats(userID) 92 | .then(chats => { 93 | res.send(chats) 94 | }) 95 | .catch(err => { 96 | res.send(mongoError + err) 97 | }) 98 | }) 99 | 100 | router.post('/delete/', (req, res) => { 101 | let messageIDs = req.body.messageIDs 102 | console.log(messageIDs) 103 | // let senderID = req.body.senderID 104 | let receiverID = req.body.receiverID 105 | 106 | messageIDs.forEach(msgID => { 107 | console.log("Deleting message with ID: " + msgID) 108 | }) 109 | 110 | let mongoPromise = deleteMessages(messageIDs) 111 | mongoPromise 112 | .then((data) => { 113 | console.log(data) 114 | res.send({ 115 | message: `Deleted messages`, 116 | status: 1 117 | }) 118 | }) 119 | .catch(err => { 120 | console.log(err) 121 | res.send({ 122 | message: `Failed to delete messages`, 123 | status: -1 124 | }) 125 | }) 126 | 127 | }) 128 | 129 | router.post('/delete-convo', (req, res) => { 130 | let { id1, id2 } = req.body.IDs 131 | let mongoPromise = deleteWholeConvo(id1, id2) 132 | mongoPromise 133 | .then((data) => { 134 | console.log(data) 135 | res.send({ 136 | message: `Deleted Conversation`, 137 | status: 1 138 | }) 139 | }) 140 | .catch(err => { 141 | console.log(err) 142 | res.send({ 143 | message: `Failed to delete conversation`, 144 | status: -1 145 | }) 146 | }) 147 | }) 148 | 149 | module.exports = { 150 | chatRouter: router 151 | } -------------------------------------------------------------------------------- /server/Routes/users_router.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Subhash3/chat-app/c30ab81562aa83c384892340ab9f3519c7be432a/server/Routes/users_router.js -------------------------------------------------------------------------------- /server/mongo_connection.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | mongoose.Promise = global.Promise 4 | 5 | const connectToMongoDBAtlas = (URI, someMsg) => { 6 | console.log("[SERVER]: Connecting to MongoDB...: ", someMsg) 7 | 8 | let mongoConnection = mongoose.connect(URI, { 9 | useNewUrlParser: true, 10 | useUnifiedTopology: true, 11 | }) 12 | 13 | mongoose.connection.once('open', () => { 14 | console.log("[SERVER]: Connected to MongoDB.") 15 | }) 16 | 17 | mongoose.connection.on('error', (err) => { 18 | console.log("[SERVER]: MongoDB error!", err) 19 | }) 20 | 21 | return mongoConnection 22 | } 23 | 24 | module.exports = { 25 | connectToMongoDBAtlas 26 | } -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app-server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "after": { 17 | "version": "0.8.2", 18 | "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", 19 | "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" 20 | }, 21 | "array-flatten": { 22 | "version": "1.1.1", 23 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 24 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 25 | }, 26 | "arraybuffer.slice": { 27 | "version": "0.0.7", 28 | "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", 29 | "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" 30 | }, 31 | "async-limiter": { 32 | "version": "1.0.1", 33 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 34 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" 35 | }, 36 | "backo2": { 37 | "version": "1.0.2", 38 | "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", 39 | "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" 40 | }, 41 | "base64-arraybuffer": { 42 | "version": "0.1.4", 43 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", 44 | "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" 45 | }, 46 | "base64id": { 47 | "version": "2.0.0", 48 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", 49 | "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" 50 | }, 51 | "better-assert": { 52 | "version": "1.0.2", 53 | "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", 54 | "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", 55 | "requires": { 56 | "callsite": "1.0.0" 57 | } 58 | }, 59 | "bl": { 60 | "version": "2.2.1", 61 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", 62 | "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", 63 | "requires": { 64 | "readable-stream": "^2.3.5", 65 | "safe-buffer": "^5.1.1" 66 | } 67 | }, 68 | "blob": { 69 | "version": "0.0.5", 70 | "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", 71 | "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" 72 | }, 73 | "bluebird": { 74 | "version": "3.5.1", 75 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 76 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 77 | }, 78 | "body-parser": { 79 | "version": "1.19.0", 80 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 81 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 82 | "requires": { 83 | "bytes": "3.1.0", 84 | "content-type": "~1.0.4", 85 | "debug": "2.6.9", 86 | "depd": "~1.1.2", 87 | "http-errors": "1.7.2", 88 | "iconv-lite": "0.4.24", 89 | "on-finished": "~2.3.0", 90 | "qs": "6.7.0", 91 | "raw-body": "2.4.0", 92 | "type-is": "~1.6.17" 93 | } 94 | }, 95 | "bson": { 96 | "version": "1.1.5", 97 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", 98 | "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" 99 | }, 100 | "bytes": { 101 | "version": "3.1.0", 102 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 103 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 104 | }, 105 | "callsite": { 106 | "version": "1.0.0", 107 | "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", 108 | "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" 109 | }, 110 | "component-bind": { 111 | "version": "1.0.0", 112 | "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", 113 | "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" 114 | }, 115 | "component-emitter": { 116 | "version": "1.2.1", 117 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 118 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 119 | }, 120 | "component-inherit": { 121 | "version": "0.0.3", 122 | "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", 123 | "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" 124 | }, 125 | "content-disposition": { 126 | "version": "0.5.3", 127 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 128 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 129 | "requires": { 130 | "safe-buffer": "5.1.2" 131 | } 132 | }, 133 | "content-type": { 134 | "version": "1.0.4", 135 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 136 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 137 | }, 138 | "cookie": { 139 | "version": "0.4.0", 140 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 141 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 142 | }, 143 | "cookie-signature": { 144 | "version": "1.0.6", 145 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 146 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 147 | }, 148 | "core-util-is": { 149 | "version": "1.0.2", 150 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 151 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 152 | }, 153 | "cors": { 154 | "version": "2.8.5", 155 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 156 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 157 | "requires": { 158 | "object-assign": "^4", 159 | "vary": "^1" 160 | } 161 | }, 162 | "debug": { 163 | "version": "2.6.9", 164 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 165 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 166 | "requires": { 167 | "ms": "2.0.0" 168 | } 169 | }, 170 | "denque": { 171 | "version": "1.4.1", 172 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", 173 | "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" 174 | }, 175 | "depd": { 176 | "version": "1.1.2", 177 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 178 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 179 | }, 180 | "destroy": { 181 | "version": "1.0.4", 182 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 183 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 184 | }, 185 | "dotenv": { 186 | "version": "8.2.0", 187 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 188 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 189 | }, 190 | "ee-first": { 191 | "version": "1.1.1", 192 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 193 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 194 | }, 195 | "encodeurl": { 196 | "version": "1.0.2", 197 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 198 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 199 | }, 200 | "engine.io": { 201 | "version": "3.4.2", 202 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", 203 | "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", 204 | "requires": { 205 | "accepts": "~1.3.4", 206 | "base64id": "2.0.0", 207 | "cookie": "0.3.1", 208 | "debug": "~4.1.0", 209 | "engine.io-parser": "~2.2.0", 210 | "ws": "^7.1.2" 211 | }, 212 | "dependencies": { 213 | "cookie": { 214 | "version": "0.3.1", 215 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 216 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 217 | }, 218 | "debug": { 219 | "version": "4.1.1", 220 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 221 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 222 | "requires": { 223 | "ms": "^2.1.1" 224 | } 225 | }, 226 | "ms": { 227 | "version": "2.1.2", 228 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 229 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 230 | } 231 | } 232 | }, 233 | "engine.io-client": { 234 | "version": "3.4.4", 235 | "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.4.tgz", 236 | "integrity": "sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ==", 237 | "requires": { 238 | "component-emitter": "~1.3.0", 239 | "component-inherit": "0.0.3", 240 | "debug": "~3.1.0", 241 | "engine.io-parser": "~2.2.0", 242 | "has-cors": "1.1.0", 243 | "indexof": "0.0.1", 244 | "parseqs": "0.0.6", 245 | "parseuri": "0.0.6", 246 | "ws": "~6.1.0", 247 | "xmlhttprequest-ssl": "~1.5.4", 248 | "yeast": "0.1.2" 249 | }, 250 | "dependencies": { 251 | "component-emitter": { 252 | "version": "1.3.0", 253 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", 254 | "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" 255 | }, 256 | "debug": { 257 | "version": "3.1.0", 258 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 259 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 260 | "requires": { 261 | "ms": "2.0.0" 262 | } 263 | }, 264 | "parseqs": { 265 | "version": "0.0.6", 266 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", 267 | "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" 268 | }, 269 | "parseuri": { 270 | "version": "0.0.6", 271 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", 272 | "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" 273 | }, 274 | "ws": { 275 | "version": "6.1.4", 276 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", 277 | "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", 278 | "requires": { 279 | "async-limiter": "~1.0.0" 280 | } 281 | } 282 | } 283 | }, 284 | "engine.io-parser": { 285 | "version": "2.2.1", 286 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", 287 | "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", 288 | "requires": { 289 | "after": "0.8.2", 290 | "arraybuffer.slice": "~0.0.7", 291 | "base64-arraybuffer": "0.1.4", 292 | "blob": "0.0.5", 293 | "has-binary2": "~1.0.2" 294 | } 295 | }, 296 | "escape-html": { 297 | "version": "1.0.3", 298 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 299 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 300 | }, 301 | "etag": { 302 | "version": "1.8.1", 303 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 304 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 305 | }, 306 | "express": { 307 | "version": "4.17.1", 308 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 309 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 310 | "requires": { 311 | "accepts": "~1.3.7", 312 | "array-flatten": "1.1.1", 313 | "body-parser": "1.19.0", 314 | "content-disposition": "0.5.3", 315 | "content-type": "~1.0.4", 316 | "cookie": "0.4.0", 317 | "cookie-signature": "1.0.6", 318 | "debug": "2.6.9", 319 | "depd": "~1.1.2", 320 | "encodeurl": "~1.0.2", 321 | "escape-html": "~1.0.3", 322 | "etag": "~1.8.1", 323 | "finalhandler": "~1.1.2", 324 | "fresh": "0.5.2", 325 | "merge-descriptors": "1.0.1", 326 | "methods": "~1.1.2", 327 | "on-finished": "~2.3.0", 328 | "parseurl": "~1.3.3", 329 | "path-to-regexp": "0.1.7", 330 | "proxy-addr": "~2.0.5", 331 | "qs": "6.7.0", 332 | "range-parser": "~1.2.1", 333 | "safe-buffer": "5.1.2", 334 | "send": "0.17.1", 335 | "serve-static": "1.14.1", 336 | "setprototypeof": "1.1.1", 337 | "statuses": "~1.5.0", 338 | "type-is": "~1.6.18", 339 | "utils-merge": "1.0.1", 340 | "vary": "~1.1.2" 341 | } 342 | }, 343 | "finalhandler": { 344 | "version": "1.1.2", 345 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 346 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 347 | "requires": { 348 | "debug": "2.6.9", 349 | "encodeurl": "~1.0.2", 350 | "escape-html": "~1.0.3", 351 | "on-finished": "~2.3.0", 352 | "parseurl": "~1.3.3", 353 | "statuses": "~1.5.0", 354 | "unpipe": "~1.0.0" 355 | } 356 | }, 357 | "forwarded": { 358 | "version": "0.1.2", 359 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 360 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 361 | }, 362 | "fresh": { 363 | "version": "0.5.2", 364 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 365 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 366 | }, 367 | "has-binary2": { 368 | "version": "1.0.3", 369 | "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", 370 | "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", 371 | "requires": { 372 | "isarray": "2.0.1" 373 | }, 374 | "dependencies": { 375 | "isarray": { 376 | "version": "2.0.1", 377 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 378 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" 379 | } 380 | } 381 | }, 382 | "has-cors": { 383 | "version": "1.1.0", 384 | "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", 385 | "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" 386 | }, 387 | "http-errors": { 388 | "version": "1.7.2", 389 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 390 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 391 | "requires": { 392 | "depd": "~1.1.2", 393 | "inherits": "2.0.3", 394 | "setprototypeof": "1.1.1", 395 | "statuses": ">= 1.5.0 < 2", 396 | "toidentifier": "1.0.0" 397 | } 398 | }, 399 | "iconv-lite": { 400 | "version": "0.4.24", 401 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 402 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 403 | "requires": { 404 | "safer-buffer": ">= 2.1.2 < 3" 405 | } 406 | }, 407 | "indexof": { 408 | "version": "0.0.1", 409 | "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", 410 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" 411 | }, 412 | "inherits": { 413 | "version": "2.0.3", 414 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 415 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 416 | }, 417 | "ipaddr.js": { 418 | "version": "1.9.1", 419 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 420 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 421 | }, 422 | "isarray": { 423 | "version": "1.0.0", 424 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 425 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 426 | }, 427 | "kareem": { 428 | "version": "2.3.1", 429 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", 430 | "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" 431 | }, 432 | "media-typer": { 433 | "version": "0.3.0", 434 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 435 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 436 | }, 437 | "memory-pager": { 438 | "version": "1.5.0", 439 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 440 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 441 | "optional": true 442 | }, 443 | "merge-descriptors": { 444 | "version": "1.0.1", 445 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 446 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 447 | }, 448 | "methods": { 449 | "version": "1.1.2", 450 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 451 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 452 | }, 453 | "mime": { 454 | "version": "1.6.0", 455 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 456 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 457 | }, 458 | "mime-db": { 459 | "version": "1.44.0", 460 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 461 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 462 | }, 463 | "mime-types": { 464 | "version": "2.1.27", 465 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 466 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 467 | "requires": { 468 | "mime-db": "1.44.0" 469 | } 470 | }, 471 | "mongodb": { 472 | "version": "3.6.2", 473 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz", 474 | "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==", 475 | "requires": { 476 | "bl": "^2.2.1", 477 | "bson": "^1.1.4", 478 | "denque": "^1.4.1", 479 | "require_optional": "^1.0.1", 480 | "safe-buffer": "^5.1.2", 481 | "saslprep": "^1.0.0" 482 | } 483 | }, 484 | "mongoose": { 485 | "version": "5.10.9", 486 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.9.tgz", 487 | "integrity": "sha512-7dkr1d6Uyk87hELzoc6B7Zo7kkPTx8rKummk51Y0je2V2Ttsw0KFPwTp1G8JIbBta7Wpw8j15PJi0d33Ode2nw==", 488 | "requires": { 489 | "bson": "^1.1.4", 490 | "kareem": "2.3.1", 491 | "mongodb": "3.6.2", 492 | "mongoose-legacy-pluralize": "1.0.2", 493 | "mpath": "0.7.0", 494 | "mquery": "3.2.2", 495 | "ms": "2.1.2", 496 | "regexp-clone": "1.0.0", 497 | "safe-buffer": "5.2.1", 498 | "sift": "7.0.1", 499 | "sliced": "1.0.1" 500 | }, 501 | "dependencies": { 502 | "ms": { 503 | "version": "2.1.2", 504 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 505 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 506 | }, 507 | "safe-buffer": { 508 | "version": "5.2.1", 509 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 510 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 511 | } 512 | } 513 | }, 514 | "mongoose-legacy-pluralize": { 515 | "version": "1.0.2", 516 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 517 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 518 | }, 519 | "mpath": { 520 | "version": "0.7.0", 521 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", 522 | "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" 523 | }, 524 | "mquery": { 525 | "version": "3.2.2", 526 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", 527 | "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", 528 | "requires": { 529 | "bluebird": "3.5.1", 530 | "debug": "3.1.0", 531 | "regexp-clone": "^1.0.0", 532 | "safe-buffer": "5.1.2", 533 | "sliced": "1.0.1" 534 | }, 535 | "dependencies": { 536 | "debug": { 537 | "version": "3.1.0", 538 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 539 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 540 | "requires": { 541 | "ms": "2.0.0" 542 | } 543 | } 544 | } 545 | }, 546 | "ms": { 547 | "version": "2.0.0", 548 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 549 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 550 | }, 551 | "negotiator": { 552 | "version": "0.6.2", 553 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 554 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 555 | }, 556 | "object-assign": { 557 | "version": "4.1.1", 558 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 559 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 560 | }, 561 | "object-component": { 562 | "version": "0.0.3", 563 | "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", 564 | "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" 565 | }, 566 | "on-finished": { 567 | "version": "2.3.0", 568 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 569 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 570 | "requires": { 571 | "ee-first": "1.1.1" 572 | } 573 | }, 574 | "parseqs": { 575 | "version": "0.0.5", 576 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", 577 | "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", 578 | "requires": { 579 | "better-assert": "~1.0.0" 580 | } 581 | }, 582 | "parseuri": { 583 | "version": "0.0.5", 584 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", 585 | "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", 586 | "requires": { 587 | "better-assert": "~1.0.0" 588 | } 589 | }, 590 | "parseurl": { 591 | "version": "1.3.3", 592 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 593 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 594 | }, 595 | "path-to-regexp": { 596 | "version": "0.1.7", 597 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 598 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 599 | }, 600 | "process-nextick-args": { 601 | "version": "2.0.1", 602 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 603 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 604 | }, 605 | "proxy-addr": { 606 | "version": "2.0.6", 607 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 608 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 609 | "requires": { 610 | "forwarded": "~0.1.2", 611 | "ipaddr.js": "1.9.1" 612 | } 613 | }, 614 | "qs": { 615 | "version": "6.7.0", 616 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 617 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 618 | }, 619 | "range-parser": { 620 | "version": "1.2.1", 621 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 622 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 623 | }, 624 | "raw-body": { 625 | "version": "2.4.0", 626 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 627 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 628 | "requires": { 629 | "bytes": "3.1.0", 630 | "http-errors": "1.7.2", 631 | "iconv-lite": "0.4.24", 632 | "unpipe": "1.0.0" 633 | } 634 | }, 635 | "readable-stream": { 636 | "version": "2.3.7", 637 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 638 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 639 | "requires": { 640 | "core-util-is": "~1.0.0", 641 | "inherits": "~2.0.3", 642 | "isarray": "~1.0.0", 643 | "process-nextick-args": "~2.0.0", 644 | "safe-buffer": "~5.1.1", 645 | "string_decoder": "~1.1.1", 646 | "util-deprecate": "~1.0.1" 647 | } 648 | }, 649 | "regexp-clone": { 650 | "version": "1.0.0", 651 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 652 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 653 | }, 654 | "require_optional": { 655 | "version": "1.0.1", 656 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 657 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 658 | "requires": { 659 | "resolve-from": "^2.0.0", 660 | "semver": "^5.1.0" 661 | } 662 | }, 663 | "resolve-from": { 664 | "version": "2.0.0", 665 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 666 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 667 | }, 668 | "safe-buffer": { 669 | "version": "5.1.2", 670 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 671 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 672 | }, 673 | "safer-buffer": { 674 | "version": "2.1.2", 675 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 676 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 677 | }, 678 | "saslprep": { 679 | "version": "1.0.3", 680 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 681 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 682 | "optional": true, 683 | "requires": { 684 | "sparse-bitfield": "^3.0.3" 685 | } 686 | }, 687 | "semver": { 688 | "version": "5.7.1", 689 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 690 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 691 | }, 692 | "send": { 693 | "version": "0.17.1", 694 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 695 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 696 | "requires": { 697 | "debug": "2.6.9", 698 | "depd": "~1.1.2", 699 | "destroy": "~1.0.4", 700 | "encodeurl": "~1.0.2", 701 | "escape-html": "~1.0.3", 702 | "etag": "~1.8.1", 703 | "fresh": "0.5.2", 704 | "http-errors": "~1.7.2", 705 | "mime": "1.6.0", 706 | "ms": "2.1.1", 707 | "on-finished": "~2.3.0", 708 | "range-parser": "~1.2.1", 709 | "statuses": "~1.5.0" 710 | }, 711 | "dependencies": { 712 | "ms": { 713 | "version": "2.1.1", 714 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 715 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 716 | } 717 | } 718 | }, 719 | "serve-static": { 720 | "version": "1.14.1", 721 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 722 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 723 | "requires": { 724 | "encodeurl": "~1.0.2", 725 | "escape-html": "~1.0.3", 726 | "parseurl": "~1.3.3", 727 | "send": "0.17.1" 728 | } 729 | }, 730 | "setprototypeof": { 731 | "version": "1.1.1", 732 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 733 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 734 | }, 735 | "sift": { 736 | "version": "7.0.1", 737 | "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", 738 | "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" 739 | }, 740 | "sliced": { 741 | "version": "1.0.1", 742 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 743 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 744 | }, 745 | "socket.io": { 746 | "version": "2.3.0", 747 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", 748 | "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", 749 | "requires": { 750 | "debug": "~4.1.0", 751 | "engine.io": "~3.4.0", 752 | "has-binary2": "~1.0.2", 753 | "socket.io-adapter": "~1.1.0", 754 | "socket.io-client": "2.3.0", 755 | "socket.io-parser": "~3.4.0" 756 | }, 757 | "dependencies": { 758 | "debug": { 759 | "version": "4.1.1", 760 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 761 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 762 | "requires": { 763 | "ms": "^2.1.1" 764 | } 765 | }, 766 | "ms": { 767 | "version": "2.1.2", 768 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 769 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 770 | } 771 | } 772 | }, 773 | "socket.io-adapter": { 774 | "version": "1.1.2", 775 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", 776 | "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" 777 | }, 778 | "socket.io-client": { 779 | "version": "2.3.0", 780 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", 781 | "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", 782 | "requires": { 783 | "backo2": "1.0.2", 784 | "base64-arraybuffer": "0.1.5", 785 | "component-bind": "1.0.0", 786 | "component-emitter": "1.2.1", 787 | "debug": "~4.1.0", 788 | "engine.io-client": "~3.4.0", 789 | "has-binary2": "~1.0.2", 790 | "has-cors": "1.1.0", 791 | "indexof": "0.0.1", 792 | "object-component": "0.0.3", 793 | "parseqs": "0.0.5", 794 | "parseuri": "0.0.5", 795 | "socket.io-parser": "~3.3.0", 796 | "to-array": "0.1.4" 797 | }, 798 | "dependencies": { 799 | "base64-arraybuffer": { 800 | "version": "0.1.5", 801 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 802 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" 803 | }, 804 | "debug": { 805 | "version": "4.1.1", 806 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 807 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 808 | "requires": { 809 | "ms": "^2.1.1" 810 | } 811 | }, 812 | "isarray": { 813 | "version": "2.0.1", 814 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 815 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" 816 | }, 817 | "ms": { 818 | "version": "2.1.2", 819 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 820 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 821 | }, 822 | "socket.io-parser": { 823 | "version": "3.3.1", 824 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.1.tgz", 825 | "integrity": "sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ==", 826 | "requires": { 827 | "component-emitter": "~1.3.0", 828 | "debug": "~3.1.0", 829 | "isarray": "2.0.1" 830 | }, 831 | "dependencies": { 832 | "component-emitter": { 833 | "version": "1.3.0", 834 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", 835 | "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" 836 | }, 837 | "debug": { 838 | "version": "3.1.0", 839 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 840 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 841 | "requires": { 842 | "ms": "2.0.0" 843 | } 844 | }, 845 | "ms": { 846 | "version": "2.0.0", 847 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 848 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 849 | } 850 | } 851 | } 852 | } 853 | }, 854 | "socket.io-parser": { 855 | "version": "3.4.1", 856 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", 857 | "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", 858 | "requires": { 859 | "component-emitter": "1.2.1", 860 | "debug": "~4.1.0", 861 | "isarray": "2.0.1" 862 | }, 863 | "dependencies": { 864 | "debug": { 865 | "version": "4.1.1", 866 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 867 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 868 | "requires": { 869 | "ms": "^2.1.1" 870 | } 871 | }, 872 | "isarray": { 873 | "version": "2.0.1", 874 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 875 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" 876 | }, 877 | "ms": { 878 | "version": "2.1.2", 879 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 880 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 881 | } 882 | } 883 | }, 884 | "sparse-bitfield": { 885 | "version": "3.0.3", 886 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 887 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 888 | "optional": true, 889 | "requires": { 890 | "memory-pager": "^1.0.2" 891 | } 892 | }, 893 | "statuses": { 894 | "version": "1.5.0", 895 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 896 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 897 | }, 898 | "string_decoder": { 899 | "version": "1.1.1", 900 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 901 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 902 | "requires": { 903 | "safe-buffer": "~5.1.0" 904 | } 905 | }, 906 | "to-array": { 907 | "version": "0.1.4", 908 | "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", 909 | "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" 910 | }, 911 | "toidentifier": { 912 | "version": "1.0.0", 913 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 914 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 915 | }, 916 | "type-is": { 917 | "version": "1.6.18", 918 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 919 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 920 | "requires": { 921 | "media-typer": "0.3.0", 922 | "mime-types": "~2.1.24" 923 | } 924 | }, 925 | "unpipe": { 926 | "version": "1.0.0", 927 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 928 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 929 | }, 930 | "util-deprecate": { 931 | "version": "1.0.2", 932 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 933 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 934 | }, 935 | "utils-merge": { 936 | "version": "1.0.1", 937 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 938 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 939 | }, 940 | "vary": { 941 | "version": "1.1.2", 942 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 943 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 944 | }, 945 | "ws": { 946 | "version": "7.3.1", 947 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", 948 | "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" 949 | }, 950 | "xmlhttprequest-ssl": { 951 | "version": "1.5.5", 952 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", 953 | "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" 954 | }, 955 | "yeast": { 956 | "version": "0.1.2", 957 | "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", 958 | "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" 959 | } 960 | } 961 | } 962 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app-server", 3 | "version": "1.0.0", 4 | "description": "Chat app server", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js", 9 | "devStart": "nodemon server" 10 | }, 11 | "author": "shine", 12 | "license": "ISC", 13 | "dependencies": { 14 | "body-parser": "^1.19.0", 15 | "cors": "^2.8.5", 16 | "dotenv": "^8.2.0", 17 | "express": "^4.17.1", 18 | "mongoose": "^5.10.9", 19 | "socket.io": "^2.3.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const bodyParser = require("body-parser") 3 | const cors = require("cors") 4 | const http = require("http") 5 | const socketIO = require("socket.io") 6 | const dotenv = require("dotenv") 7 | const { chatRouter } = require('./Routes/chat_router') 8 | const { chatAppHandler } = require("./socket_IO_handlers/chat_app_handler") 9 | const { connectToMongoDBAtlas } = require('./mongo_connection') 10 | const path = require('path') 11 | // const { UsersModel } = require('./Models/users') 12 | // const { v4: uuid } = require("uuid") 13 | 14 | // Configure dotenv (.env) 15 | dotenv.config() 16 | 17 | // global variables 18 | const PORT = process.env.PORT || 3000 19 | const app = express() 20 | const server = http.Server(app) 21 | 22 | const chatIO = socketIO(server, { 23 | origins: '*:*', 24 | path: '/chat-app-socket.io' 25 | }, ['polling', 'websocket']) // I don't understand this part :( 26 | 27 | /* Express Middleware */ 28 | app.use(bodyParser.json()) // Middleware to parse search params 29 | app.use(cors()) // Middleware to resolve cors! 30 | 31 | // app.get('/', (req, res) => { 32 | // res.send("Hello!") 33 | // }) 34 | 35 | /*Custom Routes*/ 36 | app.use('/chat-app', chatRouter) // Router setup for chat-app 37 | 38 | /* Setup socketIO handlers */ 39 | chatAppHandler(chatIO) 40 | 41 | /*MongoDB*/ 42 | // let chatsDBURI = process.env.CHATS_MONGO_URI 43 | // let usersDBURI = process.env.USERS_MONGO_URI 44 | let YML_MONGO_URI = process.env.YML_MONGO_URI 45 | connectToMongoDBAtlas(YML_MONGO_URI, "chatsDB") 46 | // connectToMongoDBAtlas(chatsDBURI, "usersDB") 47 | 48 | if (process.env.NODE_ENV === 'production') { 49 | console.log("PRODUCTION ENVIROMENT DETECTED!") 50 | app.use(express.static('../client/build')) 51 | 52 | app.get('*', (req, res) => { 53 | res.sendFile(path.join(__dirname, '../', 'client', 'build', 'index.html')) 54 | }) 55 | } 56 | 57 | server.listen(PORT, function () { 58 | var host = server.address().address 59 | var port = server.address().port 60 | console.log("[SERVER]: Listening at http://<%s>:%s", host, port) 61 | }) 62 | 63 | /** SOCKET IO CHEATSHEET **/ 64 | // // send to current request socket client 65 | // socket.emit('message', "this is a test");// Hasn't changed 66 | 67 | // // sending to all clients, include sender 68 | // io.sockets.emit('message', "this is a test"); // Old way, still compatible 69 | // io.emit('message', 'this is a test');// New way, works only in 1.x 70 | 71 | // // sending to all clients except sender 72 | // socket.broadcast.emit('message', "this is a test");// Hasn't changed 73 | 74 | // // sending to all clients in 'game' room(channel) except sender 75 | // socket.broadcast.to('game').emit('message', 'nice game');// Hasn't changed 76 | 77 | // // sending to all clients in 'game' room(channel), include sender 78 | // io.sockets.in('game').emit('message', 'cool game');// Old way, DOES NOT WORK ANYMORE 79 | // io.in('game').emit('message', 'cool game');// New way 80 | // io.to('game').emit('message', 'cool game');// New way, "in" or "to" are the exact same: "And then simply use to or in (they are the same) when broadcasting or emitting:" from http://socket.io/docs/rooms-and-namespaces/ 81 | 82 | // // sending to individual socketid, socketid is like a room 83 | // io.sockets.socket(socketid).emit('message', 'for your eyes only');// Old way, DOES NOT WORK ANYMORE 84 | // socket.broadcast.to(socketid).emit('message', 'for your eyes only');// New way -------------------------------------------------------------------------------- /server/socket_IO_handlers/chat_app_handler.js: -------------------------------------------------------------------------------- 1 | const { ChatsModel } = require('../Models/chats') 2 | 3 | const chatAppHandler = (io) => { 4 | const MSG_PENDING = "pending" 5 | const MSG_SENT = "msg-sent" 6 | const MSG_NOT_SENT = "msg-not-sent" 7 | 8 | const WAIT_QUEUE_MSG_KEY = "messages" 9 | const WAIT_QUEUE_SOCKET_EVENTS_KEY = "socket-events" 10 | 11 | let userIDToSocketMap = {} 12 | let userIDToWaitQueueMap = {} 13 | let userIDToOnlineStatusMap = {} 14 | 15 | const getUserIDFromSocketID = (socketID) => { 16 | for (userID in userIDToSocketMap) { 17 | let userSocket = userIDToSocketMap[userID] 18 | if (userSocket.id === socketID) 19 | return userID 20 | } 21 | 22 | return null 23 | } 24 | 25 | const isUserOnline = (userID) => { 26 | console.log("isUserOnline") 27 | return (userID in userIDToSocketMap) 28 | } 29 | 30 | const initWaitQueue = (userID) => { 31 | userIDToWaitQueueMap[userID] = { 32 | [WAIT_QUEUE_MSG_KEY]: [], 33 | [WAIT_QUEUE_SOCKET_EVENTS_KEY]: [] 34 | } 35 | 36 | return 37 | } 38 | 39 | const addMessageToWaitQueue = (msgObjectString, userID) => { 40 | console.log("Adding message to wait queue...") 41 | if (!(userID in userIDToWaitQueueMap)) { 42 | console.log("\tUserID:", userID, "is not in wait queue") 43 | initWaitQueue(userID) 44 | } 45 | userIDToWaitQueueMap[userID][WAIT_QUEUE_MSG_KEY].push(msgObjectString) 46 | 47 | return 48 | } 49 | 50 | const addSocketEventToWaitQueue = (userID, socketEventParams) => { 51 | console.log("Adding socket event to wait queue...") 52 | if (!(userID in userIDToWaitQueueMap)) { 53 | // userIDToWaitQueueMap[userID] = [msgObjectString] 54 | initWaitQueue(userID) 55 | } 56 | userIDToWaitQueueMap[userID][WAIT_QUEUE_SOCKET_EVENTS_KEY].push(socketEventParams) 57 | 58 | return 59 | } 60 | 61 | 62 | const updateMessageStatusOnDatabase = (messagesIDs) => { 63 | // db.chats.update({id: {$in: ["1168fe5a-72e4-4506-8467-45060e3ecc46", "57524518-c329-4baa-9163-f94538c8b094"]}}, {$set: {status: "msg-sent"}}, {multi: 1}) 64 | console.log("Message Ids to update on database: ", messagesIDs) 65 | let mongoUpdatePromise = ChatsModel.updateMany( 66 | { 67 | 'id': { '$in': [...messagesIDs] } 68 | }, 69 | { 70 | '$set': { 71 | "status": "msg-sent" 72 | } 73 | } 74 | ) 75 | 76 | mongoUpdatePromise 77 | .then((data) => { 78 | // console.log("Mongo update worked.", data) 79 | console.log("Messages' statuses have been update on database") 80 | }) 81 | .catch(err => { 82 | console.log("Mongo update failed", err) 83 | }) 84 | 85 | return 86 | } 87 | 88 | const flushMessagesToUser = (socket, userID) => { 89 | console.log("[SOCKET.IO]: Flushing messages to user", userID) 90 | let allMessages = userIDToWaitQueueMap[userID][WAIT_QUEUE_MSG_KEY] 91 | let allMessagesStringified = JSON.stringify(allMessages) 92 | socket.emit('flush-messages', allMessagesStringified) 93 | 94 | // console.log("All Msgs: ", allMessages) 95 | if (!allMessages) return 96 | 97 | let messagesIDToChangeStatus = [] 98 | for (let msgString of allMessages) { 99 | let msg = JSON.parse(msgString) 100 | let senderID = msg.senderID 101 | 102 | messagesIDToChangeStatus.push(msg.id) 103 | 104 | let socketEventParams = ['message-sent', msg.id] 105 | if (isUserOnline(senderID)) { 106 | let senderSocket = userIDToSocketMap[msg.senderID] 107 | console.log(`\tEmitting [${socketEventParams[0]}] to ${senderID}`) 108 | senderSocket.emit(...socketEventParams) 109 | } else { 110 | addSocketEventToWaitQueue(senderID, socketEventParams) 111 | } 112 | } 113 | 114 | updateMessageStatusOnDatabase(messagesIDToChangeStatus) 115 | 116 | return 117 | } 118 | 119 | const flushSocketEventsToUser = (socket, userID) => { 120 | let allSocketEvents = userIDToWaitQueueMap[userID][WAIT_QUEUE_SOCKET_EVENTS_KEY] 121 | for (socketEvent of allSocketEvents) { 122 | socket.emit(...socketEvent) 123 | } 124 | 125 | return 126 | } 127 | 128 | const removeUserTraces = (socketID) => { 129 | let userID = getUserIDFromSocketID(socketID) 130 | if (!userID) 131 | return 132 | 133 | userIDToOnlineStatusMap[userID] = false 134 | delete userIDToSocketMap[userID] 135 | return 136 | } 137 | 138 | const insertIntoDB = (msgObject) => { 139 | // let chatObject = new ChatsModel(msgObject) 140 | // let dbPromise = chatObject.save() 141 | 142 | let dbPromise = ChatsModel.create(msgObject) 143 | return dbPromise 144 | } 145 | 146 | // listen on the connection event for incoming sockets 147 | io.on('connection', function (socket) { 148 | console.log('[SOCKET.IO]: A new client connected', socket.id); 149 | 150 | socket.on('logged-in', userID => { 151 | console.log(".on(logged-in)") 152 | console.log('\t[logged-in]: userID: ', userID) 153 | 154 | // Save his online status and socket 155 | userIDToSocketMap[userID] = socket 156 | userIDToOnlineStatusMap[userID] = true 157 | 158 | // Flush messages and socket_io events 159 | if (userID in userIDToWaitQueueMap) { 160 | flushMessagesToUser(socket, userID) 161 | flushSocketEventsToUser(socket, userID) 162 | } 163 | 164 | console.log('\t[logged-in]: Wait Queue: ', userIDToWaitQueueMap[userID]) 165 | // userIDToWaitQueueMap[userID] = {} 166 | initWaitQueue(userID) 167 | 168 | // Inform other users that he is online 169 | console.log("\tEmitting [online-statuses] to all users") 170 | io.emit('online-statuses', JSON.stringify(userIDToOnlineStatusMap)) 171 | }) 172 | 173 | socket.on('send-message', msgObjectString => { 174 | console.log(".on(send-message)") 175 | let msgObject = JSON.parse(msgObjectString) 176 | console.log("\t[send-message]: Received a new message.", msgObjectString) 177 | let { receiverID, id, senderID } = { ...msgObject } 178 | 179 | if (isUserOnline(receiverID)) { 180 | // console.log("\t[SOCKET.IO]: userIDToSockerMap: ", JSON.stringify(userIDToSocketMap)) 181 | console.log("\t[SOCKET.IO]: Reciever is online!", receiverID) 182 | msgObject.status = MSG_SENT 183 | 184 | insertIntoDB(msgObject) 185 | .then((data) => { 186 | // console.log("\t[SOCKET.IO]: Messages has been inserted", data) 187 | console.log("\t[SOCKET.IO]: Messages has been inserted") 188 | 189 | let receiverSocket = userIDToSocketMap[receiverID] 190 | console.log("\tEmitting [received-message] to", receiverID) 191 | receiverSocket.emit('received-message', msgObjectString) 192 | socket.emit('message-sent', id) 193 | }) 194 | .catch((error) => { 195 | console.log("\t[SOCKET.IO]: Error while inserting", error) 196 | console.log("\tEmitting [message-not-sent] to ", receiverID) 197 | socket.emit('message-not-sent', JSON.stringify(error), id) 198 | return 199 | }) 200 | } else { 201 | let reason = "Reciever is offline!" 202 | msgObject.status = MSG_PENDING // It is the default. But I'm just being explicit. 203 | addMessageToWaitQueue(msgObjectString, receiverID) 204 | 205 | insertIntoDB(msgObject) 206 | .then((data) => { 207 | console.log("\t[SOCKET.IO]: Messages has been inserted", data) 208 | console.log("\tEmitting [pending] to", senderID) 209 | socket.emit('pending', reason, id) 210 | }) 211 | .catch((error) => { 212 | console.log("\t[SOCKET.IO]: Error while inserting", error) 213 | console.log("\tEmitting [message-not-sent] to ", receiverID) 214 | socket.emit('message-not-sent', JSON.stringify(error), id) 215 | return 216 | }) 217 | } 218 | }) 219 | 220 | socket.on('deleted-msgs-ack', (receiverID) => { 221 | console.log(`.on(deleted-msg-ack), ${receiverID}`) 222 | if (isUserOnline(receiverID)) { 223 | console.log(`user: ${receiverID} is online`) 224 | let socket = userIDToSocketMap[receiverID] 225 | console.log(socket.id) 226 | 227 | console.log(`Emitting "deleted-msg-ack" to ${receiverID}`) 228 | socket.emit("deleted-msgs-ack") 229 | } else { 230 | addSocketEventToWaitQueue(receiverID, ["deleted-msgs-ack", receiverID]) 231 | } 232 | }) 233 | 234 | socket.on('delete-whole-convo', (id1, id2) => { 235 | deleteWholeConvo(id1, id2) 236 | }) 237 | 238 | socket.on('disconnect', (reason) => { 239 | console.log("Disconnected", socket.id, reason) 240 | removeUserTraces(socket.id) 241 | io.emit('online-statuses', JSON.stringify(userIDToOnlineStatusMap)) 242 | }) 243 | }); 244 | } 245 | 246 | module.exports = { 247 | chatAppHandler 248 | } --------------------------------------------------------------------------------