├── README.md
├── public
├── img
│ └── icons8-chat-64.png
├── js
│ └── script.js
├── index.html
├── chat.html
└── css
│ └── style.css
├── utils
├── messages.js
└── users.js
├── package.json
├── app.js
└── .gitignore
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/img/icons8-chat-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frozen-dev71/Real-Time-Chat-Application/main/public/img/icons8-chat-64.png
--------------------------------------------------------------------------------
/utils/messages.js:
--------------------------------------------------------------------------------
1 | // dependencies
2 | const moment = require('moment-timezone');
3 |
4 | function formatMessage(username, text) {
5 | return {
6 | username,
7 | text,
8 | // time: moment().format('h:mm a'),
9 | time: moment().tz('Asia/Dhaka').format('h:mm a'),
10 | };
11 | }
12 |
13 | module.exports = formatMessage;
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ChatApplication",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node app.js"
9 | },
10 | "keywords": [],
11 | "author": "Mehedi Islam Ripon",
12 | "license": "ISC",
13 | "dependencies": {
14 | "express": "^4.17.1",
15 | "moment-timezone": "^0.5.33",
16 | "socket.io": "^4.1.1"
17 | },
18 | "engines": {
19 | "node": ">=10.0.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/utils/users.js:
--------------------------------------------------------------------------------
1 | const users = [];
2 |
3 | // join user to chat
4 | function userJoin(id, username, room) {
5 | const user = { id, username, room };
6 |
7 | users.push(user);
8 |
9 | return user;
10 | }
11 |
12 | // get current user
13 | function getCurrentUser(id) {
14 | return users.find((user) => user.id === id);
15 | }
16 |
17 | // user leaves chat
18 | function userLeave(id) {
19 | const index = users.findIndex((user) => user.id === id);
20 |
21 | if (index !== -1) {
22 | return users.splice(index, 1)[0];
23 | }
24 | }
25 |
26 | // get room users
27 | function getRoomUsers(room) {
28 | return users.filter((user) => user.room === room);
29 | }
30 |
31 | module.exports = {
32 | userJoin,
33 | getCurrentUser,
34 | userLeave,
35 | getRoomUsers,
36 | };
37 |
--------------------------------------------------------------------------------
/public/js/script.js:
--------------------------------------------------------------------------------
1 | const chatForm = document.getElementById('chat-form');
2 | const chatMessages = document.querySelector('.chat-messages');
3 | const roomName = document.getElementById('room-name');
4 | const userList = document.getElementById('users');
5 |
6 | // get username and room from URl
7 | const { username, room } = Qs.parse(location.search, {
8 | ignoreQueryPrefix: true,
9 | });
10 |
11 | const socket = io();
12 |
13 | // Join chatroom
14 | socket.emit('joinRoom', { username, room });
15 |
16 | // get room and users
17 | socket.on('roomUsers', ({ room, users }) => {
18 | outputRoomName(room);
19 | outputUsers(users);
20 | });
21 |
22 | // message from server
23 | socket.on('message', (message) => {
24 | // console.log(message);
25 | outputMessage(message);
26 |
27 | // scroll down
28 | chatMessages.scrollTop = chatMessages.scrollHeight;
29 | });
30 |
31 | // message submit
32 | chatForm.addEventListener('submit', (e) => {
33 | e.preventDefault();
34 |
35 | // het message text
36 | const msg = e.target.elements.msg.value;
37 |
38 | // emit message to server
39 | socket.emit('chatMessage', msg);
40 |
41 | // clear input
42 | e.target.elements.msg.value = '';
43 | e.target.elements.msg.focus();
44 | });
45 |
46 | // output msg to DOM
47 | function outputMessage(message) {
48 | const div = document.createElement('div');
49 | div.classList.add('message');
50 | div.innerHTML = `
${message.username} ${message.time}
51 |
52 | ${message.text}
53 |
`;
54 |
55 | document.querySelector('.chat-messages').appendChild(div);
56 | }
57 |
58 | // add room name to DOM
59 | function outputRoomName(room) {
60 | roomName.innerHTML = room;
61 | }
62 |
63 | // add users to DOM
64 | function outputUsers(users) {
65 | userList.innerHTML = `
66 | ${users.map((user) => `${user.username}`).join('')}
67 | `;
68 | }
69 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | // dependencies
2 | const express = require('express');
3 | const http = require('http');
4 | const socketIo = require('socket.io');
5 | const path = require('path');
6 | const formatMessage = require('./utils/messages');
7 | const {
8 | userJoin,
9 | getCurrentUser,
10 | userLeave,
11 | getRoomUsers,
12 | } = require('./utils/users');
13 |
14 | const app = express();
15 | const server = http.createServer(app);
16 | const io = socketIo(server);
17 |
18 | // set static file
19 | app.use(express.static(path.join(__dirname, 'public')));
20 |
21 | const botName = 'XeroxChat Bot';
22 |
23 | // run when client connects
24 | io.on('connection', (socket) => {
25 | socket.on('joinRoom', ({ username, room }) => {
26 | const user = userJoin(socket.id, username, room);
27 |
28 | socket.join(user.room);
29 |
30 | // welcome current user
31 | socket.emit('message', formatMessage(botName, 'Welcome to XeroxChat!'));
32 |
33 | // broadcast when a user connects
34 | socket.broadcast
35 | .to(user.room)
36 | .emit(
37 | 'message',
38 | formatMessage(botName, `${user.username} has joined the chat!`)
39 | );
40 |
41 | // send users and room info
42 | io.to(user.room).emit('roomUsers', {
43 | room: user.room,
44 | users: getRoomUsers(user.room),
45 | });
46 | });
47 |
48 | // listen for chatMessage
49 | socket.on('chatMessage', (msg) => {
50 | const user = getCurrentUser(socket.id);
51 |
52 | io.to(user.room).emit('message', formatMessage(user.username, msg));
53 | });
54 |
55 | // runs when clients disconnects
56 | socket.on('disconnect', () => {
57 | const user = userLeave(socket.id);
58 |
59 | if (user) {
60 | io.to(user.room).emit(
61 | 'message',
62 | formatMessage(botName, `${user.username} has left the chat!`)
63 | );
64 |
65 | // send users and room info
66 | io.to(user.room).emit('roomUsers', {
67 | room: user.room,
68 | users: getRoomUsers(user.room),
69 | });
70 | }
71 | });
72 | });
73 |
74 | const PORT = process.env.PORT || 3000;
75 | server.listen(PORT, () => {
76 | console.log(`🎯 Server is running on PORT: ${PORT}`);
77 | });
78 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
18 |
19 | Chat Application by Mehedi
20 |
21 |
22 |
23 |
28 |
29 |
52 |
53 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/public/chat.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
17 |
18 | Chat Application by Mehedi
19 |
20 |
21 |
22 |
28 |
29 |
35 |
36 |
37 |
38 |
50 |
51 |
52 |
53 |
58 |
59 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Node ###
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # TypeScript v1 declaration files
46 | typings/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variables file
76 | .env
77 | .env.test
78 | .env*.local
79 |
80 | # parcel-bundler cache (https://parceljs.org/)
81 | .cache
82 | .parcel-cache
83 |
84 | # Next.js build output
85 | .next
86 |
87 | # Nuxt.js build / generate output
88 | .nuxt
89 | dist
90 |
91 | # Storybook build outputs
92 | .out
93 | .storybook-out
94 | storybook-static
95 |
96 | # rollup.js default build output
97 | dist/
98 |
99 | # Gatsby files
100 | .cache/
101 | # Comment in the public line in if your project uses Gatsby and not Next.js
102 | # https://nextjs.org/blog/next-9-1#public-directory-support
103 | # public
104 |
105 | # vuepress build output
106 | .vuepress/dist
107 |
108 | # Serverless directories
109 | .serverless/
110 |
111 | # FuseBox cache
112 | .fusebox/
113 |
114 | # DynamoDB Local files
115 | .dynamodb/
116 |
117 | # TernJS port file
118 | .tern-port
119 |
120 | # Stores VSCode versions used for testing VSCode extensions
121 | .vscode-test
122 |
123 | # Temporary folders
124 | tmp/
125 | temp/
126 |
127 | ### VisualStudioCode ###
128 | .vscode/*
129 | !.vscode/settings.json
130 | !.vscode/tasks.json
131 | !.vscode/launch.json
132 | !.vscode/extensions.json
133 | *.code-workspace
134 |
135 | ### VisualStudioCode Patch ###
136 | # Ignore all local history of files
137 | .history
138 | .ionide
139 |
140 | ### Windows ###
141 | # Windows thumbnail cache files
142 | Thumbs.db
143 | Thumbs.db:encryptable
144 | ehthumbs.db
145 | ehthumbs_vista.db
146 |
147 | # Dump file
148 | *.stackdump
149 |
150 | # Folder config file
151 | [Dd]esktop.ini
152 |
153 | # Recycle Bin used on file shares
154 | $RECYCLE.BIN/
155 |
156 | # Windows Installer files
157 | *.cab
158 | *.msi
159 | *.msix
160 | *.msm
161 | *.msp
162 |
163 | # Windows shortcuts
164 | *.lnk
--------------------------------------------------------------------------------
/public/css/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
2 |
3 | :root {
4 | --dark-color-a: #667aff;
5 | --dark-color-b: #7386ff;
6 | --light-color: #e6e9ff;
7 | --success-color: #5cb85c;
8 | --error-color: #d9534f;
9 | }
10 |
11 | * {
12 | box-sizing: border-box;
13 | margin: 0;
14 | padding: 0;
15 | }
16 |
17 | body {
18 | font-family: 'Roboto', sans-serif;
19 | font-size: 16px;
20 | background: #1b1b32;
21 | margin: 20px;
22 | }
23 |
24 | ul {
25 | list-style: none;
26 | }
27 |
28 | a {
29 | text-decoration: none;
30 | }
31 |
32 | .btn {
33 | cursor: pointer;
34 | padding: 5px 15px;
35 | border: none;
36 | border-radius: 5px;
37 | background: #b1d8eb;
38 | color: #3f5163;
39 | border: none;
40 | font-size: 17px;
41 | }
42 |
43 | /* Chat Page */
44 | .chat-header .btn {
45 | cursor: pointer;
46 | padding: 5px 15px;
47 | border: none;
48 | border-radius: 25px;
49 | background: #b1d8eb;
50 | color: #3f5163;
51 | border: none;
52 | font-size: 17px;
53 | }
54 |
55 | .chat-container {
56 | max-width: 1100px;
57 | margin: 30px auto;
58 | overflow: hidden;
59 | }
60 |
61 | .chat-header {
62 | background: #3b3b4f;
63 | color: #fff;
64 | /* border-top-left-radius: 5px;
65 | border-top-right-radius: 5px; */
66 | padding: 15px;
67 | border-radius: 5px 5px 0px 0px;
68 | display: flex;
69 | align-items: center;
70 | justify-content: space-between;
71 | }
72 |
73 | .chat-main {
74 | display: grid;
75 | grid-template-columns: 1fr 3fr;
76 | }
77 |
78 | .chat-sidebar {
79 | background: #323244;
80 | color: #fff;
81 | padding: 20px 20px 60px;
82 | overflow-y: scroll;
83 | }
84 |
85 | .chat-sidebar h2 {
86 | font-size: 20px;
87 | background: rgba(0, 0, 0, 0.1);
88 | padding: 10px;
89 | margin-bottom: 20px;
90 | }
91 |
92 | .chat-sidebar h3 {
93 | margin-bottom: 15px;
94 | }
95 |
96 | .chat-sidebar ul li {
97 | padding: 10px 0;
98 | }
99 |
100 | .chat-messages {
101 | padding: 30px;
102 | max-height: 500px;
103 | overflow-y: scroll;
104 | background: #34495e;
105 | min-height: 68vh;
106 | }
107 |
108 | .chat-messages .message {
109 | padding: 10px;
110 | margin-bottom: 15px;
111 | background-color: #aac0c1;
112 | border-radius: 5px;
113 | overflow-wrap: break-word;
114 | }
115 |
116 | .chat-messages .message .meta {
117 | font-size: 15px;
118 | font-weight: bold;
119 | color: #34495e;
120 | opacity: 0.7;
121 | margin-bottom: 7px;
122 | }
123 |
124 | .chat-messages .message .meta span {
125 | color: #777;
126 | }
127 |
128 | .chat-form-container {
129 | padding: 20px 30px;
130 | background-color: #3b3b4f;
131 | border-radius: 0 0 5px 5px;
132 | }
133 |
134 | textarea:focus,
135 | input:focus {
136 | outline: none;
137 | }
138 |
139 | .chat-form-container input[type='text'] {
140 | font-size: 16px;
141 | padding: 0px 20px;
142 | border: none;
143 | height: 40px;
144 | width: 86%;
145 | background: #d9edf7;
146 | border-radius: 18px;
147 | }
148 |
149 | /* Join Page */
150 | .join-container {
151 | max-width: 500px;
152 | margin: 80px auto;
153 | color: #fff;
154 | }
155 |
156 | .join-header {
157 | text-align: center;
158 | padding: 20px;
159 | background: #3b3b4f;
160 | border-top-left-radius: 5px;
161 | border-top-right-radius: 5px;
162 | }
163 |
164 | .join-main {
165 | padding: 30px 40px;
166 | background: #323244;
167 | }
168 |
169 | .join-main p {
170 | margin-bottom: 20px;
171 | }
172 |
173 | .join-main .form-control {
174 | margin-bottom: 20px;
175 | }
176 |
177 | .join-main label {
178 | display: block;
179 | margin-bottom: 8px;
180 | }
181 |
182 | .join-main input[type='text'] {
183 | font-size: 16px;
184 | padding: 20px;
185 | height: 40px;
186 | border-radius: 5px;
187 | border: none;
188 | background: #d9edf7;
189 | width: 100%;
190 | }
191 |
192 | .join-main select {
193 | font-size: 16px;
194 | padding: 10px;
195 | height: 40px;
196 | width: 100%;
197 | border: none;
198 | border-radius: 5px;
199 | color: #757575;
200 | background: #d9edf7;
201 | }
202 |
203 | .join-main .btn {
204 | margin-top: 20px;
205 | width: 30%;
206 | padding: 10px;
207 | border: none;
208 | border-radius: 5px;
209 | color: #34495e;
210 | background: #b1d8eb;
211 | }
212 |
213 | @media (max-width: 700px) {
214 | .chat-main {
215 | display: block;
216 | }
217 |
218 | .chat-sidebar {
219 | display: none;
220 | }
221 | }
222 |
223 | @media only screen and (max-width: 426px) {
224 | .join-main .btn {
225 | width: 46%;
226 | padding: 8px;
227 | }
228 | }
229 |
230 | @media only screen and (max-width: 1138px) {
231 | .chat-form-container input[type='text'] {
232 | width: 60%;
233 | }
234 | }
235 |
236 | @media only screen and (max-width: 464px) {
237 | .chat-form-container input[type='text'] {
238 | width: 100%;
239 | }
240 | }
241 |
242 | .send-button {
243 | cursor: pointer;
244 | padding: 10px 30px;
245 | border: none;
246 | border-radius: 5px;
247 | background: #b1d8eb;
248 | color: #3f5163;
249 | border: none;
250 | border-radius: 25px;
251 | font-size: 17px;
252 | margin-left: 20px;
253 | }
254 |
255 | @media only screen and (max-width: 464px) {
256 | .send-button {
257 | margin-left: 0px;
258 | margin-top: 10px;
259 | }
260 | .chat-messages {
261 | min-height: 65vh;
262 | }
263 | }
264 |
--------------------------------------------------------------------------------