Skip to content

Commit

Permalink
Merge pull request #69 from CS3219-AY2425S1/aiden_semaphores
Browse files Browse the repository at this point in the history
Added semaphores for editor
  • Loading branch information
lynnetteeee authored Nov 8, 2024
2 parents 8fe1de0 + 5944ac7 commit 4fd48d4
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 30 deletions.
78 changes: 57 additions & 21 deletions code-websocket/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@ const server = new WebSocket.Server({ port: process.env.PORT || 8081 });
// Store the code for each session ID
const sessionData = {};
const activeUsers = {};
const typingStatus = {}; // Store typing status for each session to track who is typing

server.on('connection', (socket, request) => {
const urlParts = request.url.split('/');
const sessionID = urlParts.pop().split('?')[0];
const userID = new URLSearchParams(request.url.split('?')[1]).get('userID');

if (!sessionID || !userID) {
console.error(`Invalid connection attempt: sessionID or userID is missing`);
socket.close();
return;
}

// Initialize session data if it doesn't exist
if (!sessionData[sessionID]) {
sessionData[sessionID] = { code: '' };
Expand All @@ -34,15 +42,38 @@ server.on('connection', (socket, request) => {
const parsedMessage = JSON.parse(message);
console.log(`Received message from user ${userID} in session ${sessionID}:`, parsedMessage);

if (parsedMessage.type === 'code') {
// Update the stored code for this session
sessionData[sessionID].code = parsedMessage.content;

// Broadcast the updated code to all clients in the same session
broadcastToSession(sessionID, {
type: 'code',
content: parsedMessage.content
}, socket);
switch (parsedMessage.type) {
case 'code':
// Update the stored code for this session
sessionData[sessionID].code = parsedMessage.content;

// Broadcast the updated code to all clients in the same session
broadcastToSession(sessionID, {
type: 'code',
content: parsedMessage.content
}, socket);
break;

case 'typingStarted':
// Broadcast typingStarted event to lock other users
if (!typingStatus[sessionID]) {
typingStatus[sessionID] = userID;
broadcastToSession(sessionID, { type: 'typingStarted', userID });
console.log(`User ${userID} started typing in session ${sessionID}`);
}
break;

case 'typingEnded':
// Broadcast typingEnded event to unlock editor for others
if (typingStatus[sessionID] === userID) {
delete typingStatus[sessionID];
broadcastToSession(sessionID, { type: 'typingEnded', userID });
console.log(`User ${userID} stopped typing in session ${sessionID}`);
}
break;

default:
console.warn('Unknown message type:', parsedMessage.type);
}
});

Expand All @@ -57,36 +88,41 @@ server.on('connection', (socket, request) => {
const remainingUsers = Object.keys(activeUsers[sessionID]);
console.log(`Remaining users in session ${sessionID}: ${remainingUsers.length > 0 ? remainingUsers.join(', ') : 'None'}`);


broadcastToSession(sessionID, {
type: 'userDisconnected',
userID,
});

// Remove typing lock if the user was typing
if (typingStatus[sessionID] === userID) {
delete typingStatus[sessionID];
broadcastToSession(sessionID, { type: 'typingEnded', userID });
console.log(`Typing lock released for session ${sessionID}`);
}

// Clean up session data if no users remain
if (remainingUsers.length === 0) {
console.log(`All users disconnected from session ${sessionID}. Removing session data.`);

// Remove the session data and active users entry for this session
delete sessionData[sessionID];
delete activeUsers[sessionID];
delete typingStatus[sessionID];
}
});

// Handle errors
socket.on('error', (error) => {
console.error(`Socket error for user ${userID} in session ${sessionID}:`, error);
});


});


function broadcastToSession(sessionID, message, excludeSocket = null) {
Object.values(activeUsers[sessionID]).forEach((clientSocket) => {
if (clientSocket.readyState === WebSocket.OPEN && clientSocket !== excludeSocket) {
clientSocket.send(JSON.stringify(message));
}
});
if (activeUsers[sessionID]) {
Object.values(activeUsers[sessionID]).forEach((clientSocket) => {
if (clientSocket.readyState === WebSocket.OPEN && clientSocket !== excludeSocket) {
clientSocket.send(JSON.stringify(message));
}
});
}
}

console.log(`WebSocket server is running on ws://localhost:${process.env.PORT || 8081}`);
console.log(`WebSocket server is running on ws://localhost:${process.env.PORT || 8081}`);
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export class CollaborativeEditorComponent implements OnInit, OnDestroy {
language: 'javascript',
paths: {
vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.0/min/vs/'
}
},
readOnly: false,
};
code: string = '';
line: number = 1;
Expand All @@ -43,6 +44,9 @@ export class CollaborativeEditorComponent implements OnInit, OnDestroy {

private editor!: monaco.editor.IStandaloneCodeEditor;

isLocked: boolean = false;
private typingTimeout: any;

constructor(
private webSocketService: WebSocketService,
private cdr: ChangeDetectorRef,
Expand All @@ -64,23 +68,49 @@ export class CollaborativeEditorComponent implements OnInit, OnDestroy {
this.addNotification(`User ${message.userID} connected`);
} else if (message.type === 'userDisconnected') {
this.addNotification(`User ${message.userID} disconnected`);
} else if (message.type === 'typingStarted') {
if (message.userID !== this.userId) {
this.addNotification(`User ${message.userID} is typing...`);
this.isLocked = true;
this.updateEditorLock();
}
} else if (message.type === 'typingEnded') {
if (message.userID !== this.userId) {
this.isLocked = false;
this.updateEditorLock();
this.clearNotification();
}
}
}
});
}

updateEditorLock() {
this.editorOptions = { ...this.editorOptions, readOnly: this.isLocked };
this.cdr.detectChanges();
}

addNotification(message: string) {
this.notifications.push(message);
// Auto-remove notification after a few seconds
setTimeout(() => {
this.notifications.shift();
this.cdr.detectChanges();
}, 3000);
this.notifications = [message];
this.cdr.detectChanges();
}

clearNotification() {
this.notifications = [];
this.cdr.detectChanges();
}

onEditorChange(content: string) {
// Send updated content to the WebSocket server
this.webSocketService.sendMessage({ type: 'code', content });
if (!this.isLocked) {
this.webSocketService.sendMessage({ type: 'code', content });

// Send typing status messages
this.webSocketService.sendMessage({ type: 'typingStarted', userID: this.userId });
clearTimeout(this.typingTimeout);
this.typingTimeout = setTimeout(() => {
this.webSocketService.sendMessage({ type: 'typingEnded', userID: this.userId });
}, 2000); // Adjust the timeout as necessary
}
}

onEditorInit(editor: monaco.editor.IStandaloneCodeEditor) {
Expand Down Expand Up @@ -127,6 +157,7 @@ export class CollaborativeEditorComponent implements OnInit, OnDestroy {
if (this.messageSubscription) {
this.messageSubscription.unsubscribe();
}
clearTimeout(this.typingTimeout);
this.webSocketService.disconnect();
}
}

0 comments on commit 4fd48d4

Please sign in to comment.