recoder-code
Version:
Complete AI-powered development platform with ML model training, plugin registry, real-time collaboration, monitoring, infrastructure automation, and enterprise deployment capabilities
345 lines (344 loc) • 10.6 kB
JavaScript
"use strict";
/**
* Session Manager for Collaboration Service
* Manages real-time collaboration sessions and user connections
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionManager = void 0;
const events_1 = require("events");
class SessionManager extends events_1.EventEmitter {
constructor(io) {
super();
this.sessions = new Map();
this.userSessions = new Map(); // userId -> sessionIds
this.socketSessions = new Map(); // socketId -> sessionId
this.io = io;
this.setupSocketHandlers();
this.startCleanupInterval();
}
/**
* Create a new collaboration session
*/
createSession(documentId, initiatorUserId) {
const sessionId = this.generateSessionId();
const session = {
id: sessionId,
documentId,
users: new Map(),
operations: [],
documentVersion: 0,
createdAt: new Date(),
lastActivity: new Date()
};
this.sessions.set(sessionId, session);
this.emit('sessionCreated', {
sessionId,
documentId,
initiatorUserId
});
return session;
}
/**
* Join a user to a collaboration session
*/
joinSession(sessionId, user, socketId) {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
const sessionUser = {
userId: user.id,
username: user.username,
socketId,
color: this.generateUserColor(user.id),
lastActivity: new Date()
};
// Add user to session
session.users.set(user.id, sessionUser);
session.lastActivity = new Date();
// Track user sessions
if (!this.userSessions.has(user.id)) {
this.userSessions.set(user.id, new Set());
}
this.userSessions.get(user.id).add(sessionId);
// Track socket sessions
this.socketSessions.set(socketId, sessionId);
// Join socket room
this.io.sockets.sockets.get(socketId)?.join(sessionId);
// Notify other users
this.io.to(sessionId).emit('userJoined', {
sessionId,
user: {
userId: user.id,
username: user.username,
color: sessionUser.color
}
});
this.emit('userJoined', {
sessionId,
userId: user.id,
username: user.username
});
return true;
}
/**
* Remove a user from a session
*/
leaveSession(sessionId, userId, socketId) {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
const sessionUser = session.users.get(userId);
if (!sessionUser) {
return false;
}
// Remove user from session
session.users.delete(userId);
session.lastActivity = new Date();
// Update user sessions tracking
const userSessionSet = this.userSessions.get(userId);
if (userSessionSet) {
userSessionSet.delete(sessionId);
if (userSessionSet.size === 0) {
this.userSessions.delete(userId);
}
}
// Remove socket session tracking
if (socketId) {
this.socketSessions.delete(socketId);
// Leave socket room
this.io.sockets.sockets.get(socketId)?.leave(sessionId);
}
// Notify other users
this.io.to(sessionId).emit('userLeft', {
sessionId,
userId
});
this.emit('userLeft', {
sessionId,
userId
});
// Clean up empty session
if (session.users.size === 0) {
this.sessions.delete(sessionId);
this.emit('sessionClosed', {
sessionId,
documentId: session.documentId
});
}
return true;
}
/**
* Add an operation to a session
*/
addOperation(sessionId, operation, userId) {
const session = this.sessions.get(sessionId);
if (!session) {
return { success: false };
}
const sessionUser = session.users.get(userId);
if (!sessionUser) {
return { success: false };
}
const operationId = this.generateOperationId();
session.operations.push({
operation,
userId,
timestamp: new Date(),
operationId
});
session.documentVersion++;
session.lastActivity = new Date();
sessionUser.lastActivity = new Date();
this.emit('operationAdded', {
sessionId,
operationId,
operation,
userId,
documentVersion: session.documentVersion
});
return {
success: true,
operationId,
documentVersion: session.documentVersion
};
}
/**
* Update user cursor position
*/
updateCursor(sessionId, userId, cursor) {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
const sessionUser = session.users.get(userId);
if (!sessionUser) {
return false;
}
sessionUser.cursor = cursor;
sessionUser.lastActivity = new Date();
// Broadcast cursor update to other users
this.io.to(sessionId).emit('cursorUpdate', {
sessionId,
userId,
cursor
});
return true;
}
/**
* Update user selection
*/
updateSelection(sessionId, userId, selection) {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
const sessionUser = session.users.get(userId);
if (!sessionUser) {
return false;
}
sessionUser.selection = selection;
sessionUser.lastActivity = new Date();
// Broadcast selection update to other users
this.io.to(sessionId).emit('selectionUpdate', {
sessionId,
userId,
selection
});
return true;
}
/**
* Get session information
*/
getSession(sessionId) {
return this.sessions.get(sessionId);
}
/**
* Get all sessions for a document
*/
getDocumentSessions(documentId) {
return Array.from(this.sessions.values()).filter(session => session.documentId === documentId);
}
/**
* Get sessions for a user
*/
getUserSessions(userId) {
const sessionIds = this.userSessions.get(userId);
if (!sessionIds) {
return [];
}
return Array.from(sessionIds)
.map(sessionId => this.sessions.get(sessionId))
.filter(session => session !== undefined);
}
/**
* Get session by socket ID
*/
getSessionBySocket(socketId) {
const sessionId = this.socketSessions.get(socketId);
return sessionId ? this.sessions.get(sessionId) : undefined;
}
/**
* Setup Socket.IO event handlers
*/
setupSocketHandlers() {
this.io.on('connection', (socket) => {
socket.on('disconnect', () => {
this.handleSocketDisconnect(socket.id);
});
socket.on('ping', () => {
socket.emit('pong');
});
});
}
/**
* Handle socket disconnection
*/
handleSocketDisconnect(socketId) {
const sessionId = this.socketSessions.get(socketId);
if (!sessionId) {
return;
}
const session = this.sessions.get(sessionId);
if (!session) {
return;
}
// Find user by socket ID
for (const [userId, sessionUser] of session.users.entries()) {
if (sessionUser.socketId === socketId) {
this.leaveSession(sessionId, userId, socketId);
break;
}
}
}
/**
* Start cleanup interval for inactive sessions
*/
startCleanupInterval() {
setInterval(() => {
this.cleanupInactiveSessions();
}, 5 * 60 * 1000); // Clean up every 5 minutes
}
/**
* Clean up inactive sessions
*/
cleanupInactiveSessions() {
const now = new Date();
const inactiveThreshold = 30 * 60 * 1000; // 30 minutes
for (const [sessionId, session] of this.sessions.entries()) {
const inactiveTime = now.getTime() - session.lastActivity.getTime();
if (inactiveTime > inactiveThreshold) {
// Close inactive session
for (const userId of session.users.keys()) {
this.leaveSession(sessionId, userId);
}
}
}
}
/**
* Generate unique session ID
*/
generateSessionId() {
return `session_${Date.now()}_${Math.random().toString(36).substring(2)}`;
}
/**
* Generate unique operation ID
*/
generateOperationId() {
return `op_${Date.now()}_${Math.random().toString(36).substring(2)}`;
}
/**
* Generate a unique color for a user
*/
generateUserColor(userId) {
const colors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8',
'#F7DC6F', '#BB8FCE', '#85C1E9', '#F8C471', '#82E0AA'
];
// Create a simple hash of the user ID to assign consistent colors
let hash = 0;
for (let i = 0; i < userId.length; i++) {
hash = userId.charCodeAt(i) + ((hash << 5) - hash);
}
return colors[Math.abs(hash) % colors.length];
}
/**
* Get statistics about active sessions
*/
getStats() {
const totalSessions = this.sessions.size;
const totalUsers = this.userSessions.size;
const activeSessions = Array.from(this.sessions.values()).filter(session => session.users.size > 0).length;
const averageUsersPerSession = activeSessions > 0
? Array.from(this.sessions.values()).reduce((sum, session) => sum + session.users.size, 0) / activeSessions
: 0;
return {
totalSessions,
totalUsers,
averageUsersPerSession: Math.round(averageUsersPerSession * 100) / 100,
activeSessions
};
}
}
exports.SessionManager = SessionManager;