-
Notifications
You must be signed in to change notification settings - Fork 275
add solution #125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add solution #125
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,25 @@ | ||
| 'use strict'; | ||
| /* eslint-disable no-console */ | ||
|
|
||
| const express = require('express'); | ||
| const http = require('http'); | ||
| const { Server } = require('socket.io'); | ||
| const path = require('path'); | ||
| const { setupSocketHandlers } = require('./socket/socketHandlers'); | ||
|
|
||
| const app = express(); | ||
| const server = http.createServer(app); | ||
| const io = new Server(server); | ||
|
|
||
| const PORT = process.env.PORT || 3000; | ||
|
|
||
| app.use(express.static(path.join(__dirname, 'public'))); | ||
|
|
||
| app.get('/', (req, res) => { | ||
| res.sendFile(path.join(__dirname, 'public', 'index.html')); | ||
| }); | ||
|
|
||
| setupSocketHandlers(io); | ||
|
|
||
| server.listen(PORT, () => { | ||
| console.log(`Server listening on port:${PORT}`); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| const rooms = { | ||
| general: { name: 'General', messages: [] }, | ||
| tech: { name: 'Technology', messages: [] }, | ||
| random: { name: 'Random', messages: [] }, | ||
| }; | ||
|
|
||
| function getAllRooms() { | ||
| return rooms; | ||
| } | ||
|
|
||
| function getRoomsList() { | ||
| return Object.entries(rooms).map(([id, data]) => ({ id, name: data.name })); | ||
| } | ||
|
|
||
| function getRoom(roomId) { | ||
| return rooms[roomId]; | ||
| } | ||
|
|
||
| function createRoom(roomName) { | ||
| const roomId = `room_${Date.now()}`; | ||
|
|
||
| rooms[roomId] = { name: roomName, messages: [] }; | ||
|
|
||
| return roomId; | ||
| } | ||
|
|
||
| function renameRoom(roomId, newName) { | ||
| if (rooms[roomId]) { | ||
| rooms[roomId].name = newName; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
Comment on lines
+27
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| function deleteRoom(roomId) { | ||
| if (rooms[roomId] && roomId !== 'general') { | ||
| delete rooms[roomId]; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| function addMessage(roomId, message) { | ||
| if (rooms[roomId]) { | ||
| rooms[roomId].messages.push(message); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| function roomExists(roomId) { | ||
| return !!rooms[roomId]; | ||
| } | ||
|
|
||
| module.exports = { | ||
| getAllRooms, | ||
| getRoomsList, | ||
| getRoom, | ||
| createRoom, | ||
| renameRoom, | ||
| deleteRoom, | ||
| addMessage, | ||
| roomExists, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| const users = {}; | ||
|
|
||
| function createUser(socketId, username) { | ||
| users[socketId] = { username, currentRoom: null }; | ||
| } | ||
|
Comment on lines
+3
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While the functions in this module are clear, adding JSDoc comments would improve overall documentation and long-term maintainability. This is especially helpful as a project grows or when other developers need to quickly understand the module's API. For example: /**
* Creates a new user or updates an existing one.
* @param {string} socketId - The unique socket ID for the user's connection.
* @param {string} username - The display name for the user.
*/
function createUser(socketId, username) { ... }Applying this practice to all exported functions would be beneficial. |
||
|
|
||
| function getUser(socketId) { | ||
| return users[socketId]; | ||
| } | ||
|
|
||
| function getUserRoom(socketId) { | ||
| const user = users[socketId]; | ||
|
|
||
| return user ? user.currentRoom : null; | ||
| } | ||
|
|
||
| function setUserRoom(socketId, roomId) { | ||
| if (users[socketId]) { | ||
| users[socketId].currentRoom = roomId; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| function removeUser(socketId) { | ||
| const user = users[socketId]; | ||
|
|
||
| if (user) { | ||
| delete users[socketId]; | ||
|
|
||
| return user; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| function userExists(socketId) { | ||
| return !!users[socketId]; | ||
| } | ||
|
|
||
| module.exports = { | ||
| createUser, | ||
| getUser, | ||
| getUserRoom, | ||
| setUserRoom, | ||
| removeUser, | ||
| userExists, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Socket.io Chat</title> | ||
| <script src="https://cdn.tailwindcss.com"></script> | ||
| <style> | ||
| ::-webkit-scrollbar { | ||
| width: 8px; | ||
| } | ||
| ::-webkit-scrollbar-track { | ||
| background: #2d3748; | ||
| } | ||
| ::-webkit-scrollbar-thumb { | ||
| background: #4a5568; | ||
| border-radius: 4px; | ||
| } | ||
| ::-webkit-scrollbar-thumb:hover { | ||
| background: #718096; | ||
| } | ||
| .system-message { | ||
| color: #a0aec0; | ||
| font-style: italic; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body class="bg-gray-800 text-white font-sans"> | ||
| <!-- Username Modal Overlay --> | ||
| <div | ||
| id="username-modal" | ||
| class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50" | ||
| > | ||
| <div class="bg-gray-700 p-8 rounded-lg shadow-xl text-center"> | ||
| <h2 class="text-2xl font-bold mb-4">Enter Your Username</h2> | ||
| <form id="username-form"> | ||
| <input | ||
| id="username-input" | ||
| type="text" | ||
| placeholder="Your cool name..." | ||
| class="w-full bg-gray-800 border border-gray-600 rounded-md px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" | ||
| required | ||
| /> | ||
| <button | ||
| type="submit" | ||
| class="mt-4 w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-md transition duration-300" | ||
| > | ||
| Enter Chat | ||
| </button> | ||
| </form> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Main Chat Interface --> | ||
| <div id="chat-container" class="hidden h-screen w-screen flex"> | ||
| <!-- Sidebar for Rooms --> | ||
| <div class="w-1/4 bg-gray-900 flex flex-col p-4 border-r border-gray-700"> | ||
| <div class="flex-shrink-0 mb-4"> | ||
| <h1 class="text-2xl font-bold">Chat Rooms</h1> | ||
| <p class="text-sm text-gray-400"> | ||
| Welcome, <span id="display-username" class="font-bold"></span>! | ||
| </p> | ||
| </div> | ||
|
|
||
| <div id="room-list" class="flex-grow overflow-y-auto mb-4"> | ||
| <!-- Room items will be populated by JS --> | ||
| </div> | ||
|
|
||
| <div class="flex-shrink-0 space-y-2"> | ||
| <button | ||
| id="create-room-btn" | ||
| class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-md text-sm" | ||
| > | ||
| Create Room | ||
| </button> | ||
| <button | ||
| id="rename-room-btn" | ||
| class="w-full bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded-md text-sm" | ||
| > | ||
| Rename Current | ||
| </button> | ||
| <button | ||
| id="delete-room-btn" | ||
| class="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-md text-sm" | ||
| > | ||
| Delete Current | ||
| </button> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Main Chat Area --> | ||
| <div class="w-3/4 flex flex-col"> | ||
| <div id="chat-header" class="p-4 bg-gray-700 border-b border-gray-600"> | ||
| <h2 id="current-room-name" class="text-xl font-semibold"> | ||
| No room selected | ||
| </h2> | ||
| </div> | ||
| <!-- Messages Display --> | ||
| <div | ||
| id="messages" | ||
| class="flex-grow p-4 overflow-y-auto bg-gray-800 flex flex-col space-y-4" | ||
| > | ||
| <!-- Messages will be populated by JS --> | ||
| </div> | ||
|
|
||
| <!-- Message Input Form --> | ||
| <div class="p-4 bg-gray-700 border-t border-gray-600"> | ||
| <form id="message-form" class="flex space-x-4"> | ||
| <input | ||
| id="message-input" | ||
| type="text" | ||
| placeholder="Type a message..." | ||
| autocomplete="off" | ||
| class="flex-grow bg-gray-800 border border-gray-600 rounded-md px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" | ||
| /> | ||
| <button | ||
| type="submit" | ||
| class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-md" | ||
| > | ||
| Send | ||
| </button> | ||
| </form> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
Comment on lines
+55
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While the current structure using
Using these tags provides more meaning to the document structure for both developers and assistive technologies. |
||
|
|
||
| <script src="/socket.io/socket.io.js"></script> | ||
| <script type="module" src="js/main.js"></script> | ||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| function initializeAuth(state) { | ||
| const usernameModal = document.getElementById('username-modal'); | ||
| const usernameForm = document.getElementById('username-form'); | ||
| const usernameInput = document.getElementById('username-input'); | ||
| const displayUsername = document.getElementById('display-username'); | ||
| const chatContainer = document.getElementById('chat-container'); | ||
|
|
||
| if (state.username) { | ||
| showChatInterface(); | ||
| } | ||
|
|
||
| usernameForm.addEventListener('submit', (e) => { | ||
| e.preventDefault(); | ||
|
|
||
| const newUsername = usernameInput.value.trim(); | ||
|
|
||
| if (newUsername) { | ||
| state.username = newUsername; | ||
| window.localStorage.setItem('chat_username', state.username); | ||
| showChatInterface(); | ||
| } | ||
| }); | ||
|
Comment on lines
+12
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The form submission logic is functional, but it could be improved to provide better user feedback. Currently, if a user submits an empty or whitespace-only username, the form does nothing, which can be slightly confusing. It's also possible to click the submit button multiple times before the UI transitions. Consider adding a disabled state to the button upon submission to prevent multiple clicks and provide immediate feedback that an action is in progress. For example: usernameForm.addEventListener('submit', (e) => {
e.preventDefault();
const submitButton = e.target.querySelector('button[type="submit"]');
const newUsername = usernameInput.value.trim();
if (newUsername) {
submitButton.disabled = true;
submitButton.textContent = 'Entering...';
state.username = newUsername;
window.localStorage.setItem('chat_username', state.username);
showChatInterface();
}
}); |
||
|
|
||
| function showChatInterface() { | ||
| displayUsername.textContent = state.username; | ||
| usernameModal.classList.add('hidden'); | ||
| chatContainer.classList.remove('hidden'); | ||
| chatContainer.classList.add('flex'); | ||
| state.socket.emit('set username', state.username); | ||
| } | ||
| } | ||
|
|
||
| export { initializeAuth }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { initializeAuth } from './auth.js'; | ||
| import { initializeSocket } from './socket.js'; | ||
| import { initializeUI } from './ui.js'; | ||
|
|
||
| document.addEventListener('DOMContentLoaded', () => { | ||
| const socket = window.io(); | ||
|
|
||
| const state = { | ||
| socket, | ||
| username: window.localStorage.getItem('chat_username'), | ||
| currentRoomId: null, | ||
| }; | ||
|
Comment on lines
+8
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of a shared, mutable |
||
|
|
||
| initializeAuth(state); | ||
| initializeSocket(state); | ||
| initializeUI(state); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for handling the
rooms listevent is not robust enough to handle modifications (rename) or deletions of the current room by another user. This leads to two issues:A better approach would be to check if
state.currentRoomIdstill exists in the updatedroomsarray. If not, the client should automatically join a default room. If it does, you should refresh the chat header to reflect any potential name change.