UNPKG

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
"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;