Skip to content

Commit e1a8828

Browse files
committed
Cherry-pick Chat features
1 parent bc90b55 commit e1a8828

File tree

4 files changed

+107
-33
lines changed

4 files changed

+107
-33
lines changed

Backend/CollabService/utils/roomManager.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ function manageRoom(ws, roomId, userId, type) {
66

77
switch (type) {
88
case "join":
9-
9+
1010
// userIds -> track users in room
1111
// matchedUserIds -> check authorized users
1212
if (!rooms[roomId]) {
1313
rooms[roomId] = { sockets: [], userIds: [], matchedUserIds: []};
1414
}
1515

1616
console.log(`BEFORE room Info: userIds[${rooms[roomId].userIds}] || matchedusers[${rooms[roomId].matchedUserIds}]`)
17-
17+
1818
const numOfMatchedUsers = rooms[roomId].matchedUserIds.length
1919

2020
// max 2 authorized users per room
@@ -23,7 +23,7 @@ function manageRoom(ws, roomId, userId, type) {
2323
rooms[roomId].userIds.push(userId);
2424
rooms[roomId].matchedUserIds.push(userId);
2525
}
26-
26+
2727
if (numOfMatchedUsers === 2){
2828
if (!rooms[roomId].matchedUserIds.includes(userId)){
2929
console.log(`User ${userId} is denied access to room ${roomId}`);
@@ -36,7 +36,7 @@ function manageRoom(ws, roomId, userId, type) {
3636
rooms[roomId].userIds.push(userId);
3737
}
3838
}
39-
39+
4040

4141
// set websocket roomId
4242
ws.roomId = roomId;
@@ -101,9 +101,13 @@ function manageRoom(ws, roomId, userId, type) {
101101
}
102102
}
103103

104+
function getRoom(roomId) {
105+
return rooms[roomId];
106+
}
107+
104108
function getUsersInRoom(roomId){
105109
return rooms[roomId]?.userIds || [];
106110
}
107111

108-
module.exports = { manageRoom, getUsersInRoom };
112+
module.exports = { manageRoom, getRoom, getUsersInRoom };
109113

Backend/CollabService/websocket/roomManagerSocket.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const WebSocket = require('ws');
2-
const { manageRoom, getUsersInRoom} = require('../utils/roomManager');
2+
const { manageRoom, getUsersInRoom, getRoom} = require('../utils/roomManager');
33

44
function setupWebSocket(server) {
55
const wss = new WebSocket.Server({ server });
@@ -19,13 +19,18 @@ function setupWebSocket(server) {
1919
// remove user from room with roomId
2020
manageRoom(ws, data.roomId, data.userId, "leave");
2121
break;
22-
case 'requestUserList':
22+
case 'requestUserList':
2323
const users = getUsersInRoom(data.roomId);
2424
ws.send(JSON.stringify({
2525
type: 'usersListUpdate',
2626
users
2727
}));
2828
break;
29+
case 'sendMessage':
30+
// send message to all users in room
31+
console.log('got message ', data.message);
32+
broadcastMessage(data.roomId, data.message);
33+
break;
2934
default:
3035
console.error('Unknown message type');
3136
}
@@ -36,6 +41,17 @@ function setupWebSocket(server) {
3641
});
3742
});
3843

44+
function broadcastMessage(roomId, message) {
45+
const room = getRoom(roomId)
46+
if (room) {
47+
room.sockets.forEach((client) => {
48+
if (client.readyState === WebSocket.OPEN) {
49+
client.send(JSON.stringify({ type: 'newMessage', message: message }));
50+
}
51+
})
52+
}
53+
}
54+
3955
}
4056

4157
module.exports = { setupWebSocket };
Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,54 @@
1-
import React from 'react'
2-
import { Card, ListGroup, ListGroupItem } from 'react-bootstrap'
1+
import React, { useState } from 'react';
2+
import { Accordion, ListGroup, ListGroupItem, Form, Button } from 'react-bootstrap';
33

4-
const Chat = () => {
4+
const Chat = ({ currentUser, messages, sendMessage }) => {
5+
const [text, setText] = useState('');
6+
7+
const handleSend = () => {
8+
console.log("I am sending")
9+
if (text.trim()) {
10+
sendMessage(text);
11+
setText('');
12+
}
13+
};
514

6-
const messages = ["Hello!", "Nice to meet you!"]
715
return (
8-
<Card className='mt-3'>
9-
<Card.Header>Chat</Card.Header>
10-
<Card.Body>
11-
<ListGroup>
12-
{messages.map((msg, idx) => (
13-
<ListGroupItem key={idx}>{msg}</ListGroupItem>
14-
))}
15-
</ListGroup>
16-
</Card.Body>
17-
</Card>
18-
)
19-
}
16+
<Accordion defaultActiveKey="0" className='mt-3'>
17+
<Accordion.Item eventKey="0">
18+
<Accordion.Header>
19+
Chat
20+
</Accordion.Header>
21+
<Accordion.Body>
22+
<ListGroup>
23+
{messages.map((msg, idx) => (
24+
<ListGroupItem
25+
key={idx}
26+
style={{
27+
backgroundColor: msg.sender === currentUser ? "#d1e7dd" : "#50d7da",
28+
textAlign: msg.sender === currentUser ? "right" : "left"
29+
}}
30+
>
31+
{msg.text}
32+
</ListGroupItem>
33+
))}
34+
</ListGroup>
35+
<Form className="mt-3" onSubmit={(e) => { e.preventDefault(); handleSend(); }}>
36+
<Form.Group controlId="messageInput">
37+
<Form.Control
38+
type="text"
39+
placeholder="Type a message..."
40+
value={text}
41+
onChange={(e) => setText(e.target.value)}
42+
/>
43+
</Form.Group>
44+
<Button variant="primary" type="submit" className="mt-2">
45+
Send
46+
</Button>
47+
</Form>
48+
</Accordion.Body>
49+
</Accordion.Item>
50+
</Accordion>
51+
);
52+
};
2053

21-
export default Chat
54+
export default Chat;

Frontend/src/components/collab/CollaborationSpace.jsx

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ const CollaborationSpace = () => {
2020
const [yDoc, setYDoc] = useState(null);
2121
const [provider, setProvider] = useState(null);
2222
const [code, setCode] = useState('');
23-
const [users, setUsers] = useState([]); // Track users in the room
24-
const [userId, setUserId] = useState(""); // Current user
25-
const [language, setLanguage] = useState("python"); // Set default language to python
26-
const [output, setOutput] = useState("");
23+
const [users, setUsers] = useState([]); // track users in the room
24+
const [userId, setUserId] = useState(""); // current user
25+
const [language, setLanguage] = useState("python") // set default language to python
26+
const [output, setOutput] = useState("")
27+
const [messages, setMessages] = useState([])
2728

29+
// use https://emkc.org/api/v2/piston/runtimes to GET other languages
2830
const LANGUAGEVERSIONS = {
2931
"python" : "3.10.0",
3032
"java" : "15.0.2",
@@ -65,6 +67,10 @@ const CollaborationSpace = () => {
6567
};
6668
}, []);
6769

70+
useEffect(() => {
71+
console.log("Messages state updated:", messages);
72+
}, [messages]);
73+
6874
const initiateWebSocket = (userId) => {
6975
if (websocketRef.current) return; // Prevent duplicate connections
7076

@@ -93,22 +99,32 @@ const CollaborationSpace = () => {
9399
setShowAccessDeniedToast(true);
94100
setLoading(false);
95101
break;
102+
case 'newMessage':
103+
console.log("adding message", data.message)
104+
setMessages((prevMessages) => [...prevMessages, data.message]);
105+
break;
96106
default:
97107
console.log("No messages received from room management server");
98108
break;
99109
}
100110
};
101111

112+
// create a Yjs document for collaboration
102113
const doc = new Y.Doc();
103114
setYDoc(doc);
104115

116+
// create websocket provider to synchronize the document
105117
const wsProvider = new WebsocketProvider("ws://localhost:1234", roomId, doc);
106118
setProvider(wsProvider);
107119

120+
// Create a shared type in Yjs for collaborative code editing
108121
const yText = doc.getText('monacoEditor');
122+
123+
// Update monaco editor with Yjs changes
109124
yText.observe(() => {
110125
setCode(yText.toString());
111126
});
127+
112128
return () => {
113129
// clean up for room management
114130
wsProvider.destroy();
@@ -155,6 +171,11 @@ const CollaborationSpace = () => {
155171
yText.insert(0, value);
156172
};
157173

174+
const sendMessage = (text) => {
175+
const message = {text, sender: userId};
176+
websocketRef.current.send(JSON.stringify({ type: 'sendMessage', roomId: roomId, message: message}));
177+
}
178+
158179
if (loading) {
159180
return (
160181
<div style={{ textAlign: 'center' }}>
@@ -170,10 +191,10 @@ const CollaborationSpace = () => {
170191
<div style={{ textAlign: 'center' }}>
171192
{showAccessDeniedToast ? (
172193
<ToastContainer className="p-3" position="top-center" style={{ zIndex: 1 }}>
173-
<Toast
174-
onClose={handleCloseToast}
175-
show={showAccessDeniedToast}
176-
delay={3000}
194+
<Toast
195+
onClose={handleCloseToast}
196+
show={showAccessDeniedToast}
197+
delay={3000}
177198
autohide
178199
bg="danger"
179200
>
@@ -192,7 +213,7 @@ const CollaborationSpace = () => {
192213
</Col>
193214
<Col md={4}>
194215
<QuestionDisplay/>
195-
<Chat/>
216+
<Chat currentUser={userId} messages={messages} sendMessage={sendMessage}> </Chat>
196217
</Col>
197218
</Row>
198219
</Container>

0 commit comments

Comments
 (0)