Skip to content

Commit 951f0a9

Browse files
committed
feat: implement real-time chat application with WebSocket backend and React frontend
1 parent 9286f87 commit 951f0a9

3 files changed

Lines changed: 225 additions & 134 deletions

File tree

be/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"description": "",
1414
"dependencies": {
1515
"@types/express": "^5.0.5",
16+
"@types/node": "^22.0.0",
1617
"@types/ws": "^8.18.1",
1718
"express": "^5.1.0",
1819
"typescript": "^5.8.2",

be/src/index.ts

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,98 +6,96 @@ import { createServer } from 'http';
66
const app = express();
77
const port = process.env.PORT || 8080;
88

9-
// Serve static files from the frontend dist directory
109
const frontendPath = path.resolve(__dirname, '../../fe/dist');
11-
console.log("Frontend Path:", frontendPath);
1210
app.use(express.static(frontendPath));
13-
14-
// Catch-all route for SPA - serve index.html for any route
15-
app.use((req, res) => {
11+
app.use((_req, res) => {
1612
res.sendFile(path.resolve(frontendPath, 'index.html'));
1713
});
1814

19-
// Create HTTP server and attach WebSocket
2015
const server = createServer(app);
2116
const wss = new WebSocketServer({ server });
2217

2318
interface User {
2419
socket: WebSocket;
2520
room: string;
21+
username: string;
2622
}
2723

2824
interface Message {
2925
message: string;
26+
username: string;
3027
timestamp: number;
3128
}
3229

3330
let allSockets: User[] = [];
34-
// Store message history per room (in-memory)
35-
let roomMessages: Map<string, Message[]> = new Map();
31+
const roomMessages: Map<string, Message[]> = new Map();
3632

37-
wss.on("connection", (socket) => {
33+
function broadcast(room: string, payload: object, exclude?: WebSocket) {
34+
const data = JSON.stringify(payload);
35+
allSockets.forEach((user) => {
36+
if (user.room === room && user.socket !== exclude) {
37+
user.socket.send(data);
38+
}
39+
});
40+
}
3841

39-
socket.on("message", (message) => {
42+
wss.on('connection', (socket) => {
43+
socket.on('message', (data) => {
4044
try {
41-
const parsedMessage = JSON.parse(message.toString());
45+
const parsed = JSON.parse(data.toString());
4246

43-
if (parsedMessage.type == "join") {
44-
const roomId = parsedMessage.payload.roomId;
45-
console.log("User joined room " + roomId);
47+
if (parsed.type === 'join') {
48+
const { roomId, username } = parsed.payload;
49+
allSockets.push({ socket, room: roomId, username });
4650

47-
allSockets.push({
48-
socket,
49-
room: roomId
50-
});
51-
52-
// Send message history to the newly joined user
51+
// Send history to the new user
5352
const history = roomMessages.get(roomId) || [];
54-
if (history.length > 0) {
55-
socket.send(JSON.stringify({
56-
type: "history",
57-
payload: {
58-
messages: history.map(m => m.message)
59-
}
60-
}));
61-
}
53+
socket.send(JSON.stringify({
54+
type: 'history',
55+
payload: { messages: history },
56+
}));
57+
58+
// Notify others in the room
59+
broadcast(roomId, {
60+
type: 'system',
61+
payload: { message: `${username} joined the room` },
62+
}, socket);
6263
}
6364

64-
if (parsedMessage.type == "chat") {
65-
console.log("user wants to chat");
66-
const currentUserRoom = allSockets.find((x) => x.socket == socket)?.room;
67-
68-
if (currentUserRoom) {
69-
const message = parsedMessage.payload.message;
70-
71-
// Store message in room history
72-
if (!roomMessages.has(currentUserRoom)) {
73-
roomMessages.set(currentUserRoom, []);
74-
}
75-
roomMessages.get(currentUserRoom)!.push({
76-
message,
77-
timestamp: Date.now()
78-
});
79-
80-
// Broadcast to all users in the room
81-
allSockets.forEach((user) => {
82-
if (user.room == currentUserRoom) {
83-
user.socket.send(message);
84-
}
85-
})
65+
if (parsed.type === 'chat') {
66+
const currentUser = allSockets.find((x) => x.socket === socket);
67+
if (!currentUser) return;
68+
69+
const msg: Message = {
70+
message: parsed.payload.message,
71+
username: currentUser.username,
72+
timestamp: Date.now(),
73+
};
74+
75+
if (!roomMessages.has(currentUser.room)) {
76+
roomMessages.set(currentUser.room, []);
8677
}
78+
roomMessages.get(currentUser.room)!.push(msg);
79+
80+
broadcast(currentUser.room, { type: 'chat', payload: msg });
8781
}
8882
} catch (e) {
89-
console.error("Error processing message:", e);
83+
console.error('Error processing message:', e);
9084
}
9185
});
9286

93-
socket.on("close", () => {
94-
allSockets = allSockets.filter((x) => x.socket != socket);
87+
socket.on('close', () => {
88+
const user = allSockets.find((x) => x.socket === socket);
89+
if (user) {
90+
allSockets = allSockets.filter((x) => x.socket !== socket);
91+
broadcast(user.room, {
92+
type: 'system',
93+
payload: { message: `${user.username} left the room` },
94+
});
95+
}
9596
});
97+
});
9698

97-
})
98-
99-
// Start server
10099
server.listen(port, () => {
101-
console.log(`Server is running on port ${port}`);
100+
console.log(`Server running on port ${port}`);
102101
});
103-

0 commit comments

Comments
 (0)