Team Ai
Apppublic

sanket3280/code-execution

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
spectator.socket.js349 linesDownload Raw Back to sockets
1const SpectatorSession = require('../models/SpectatorSession');2const Challenge = require('../models/Challenge');3const redis = require('../utils/redis');4 5/**6 * Initialize spectator WebSocket handlers7 * @param {SocketIO.Server} io - Socket.IO server instance8 */9function initializeSpectatorSocket(io) {10  io.on('connection', (socket) => {11    // Spectator joins a challenge room12    socket.on('spectator:join', async (data) => {13      try {14        const { challengeId, spectatorId, role } = data;15        16        // Validate challenge exists and is active17        const challenge = await Challenge.findById(challengeId);18        if (!challenge) {19          socket.emit('error:challengeNotFound', {20            error: 'CHALLENGE_NOT_FOUND',21            message: 'Challenge not found'22          });23          return;24        }25 26        // Check if challenge has started27        if (new Date() < new Date(challenge.startDate)) {28          socket.emit('error:challengeNotStarted', {29            error: 'CHALLENGE_NOT_STARTED',30            message: 'This challenge has not started yet',31            startDate: challenge.startDate32          });33          return;34        }35 36        // Join challenge room37        const roomName = `challenge:${challengeId}:spectators`;38        socket.join(roomName);39 40        // Get client IP41        const ipAddress = socket.handshake.headers['x-forwarded-for'] || 42                         socket.handshake.address || 43                         'unknown';44        const userAgent = socket.handshake.headers['user-agent'] || 'unknown';45 46        // Create spectator session47        await SpectatorSession.create({48          challengeId,49          spectatorId,50          role,51          socketId: socket.id,52          ipAddress,53          userAgent,54          watchingParticipants: [],55          isActive: true56        });57 58        // Get participant count59        const participantCount = challenge.participants.length;60        const activeParticipants = challenge.participants.filter(p => {61          // Consider active if last activity within 5 minutes62          return p.lastSubmission && 63                 (Date.now() - new Date(p.lastSubmission).getTime() < 5 * 60 * 1000);64        }).length;65 66        // Confirm join67        socket.emit('spectator:joined', {68          success: true,69          spectatorId,70          participantCount,71          activeParticipants,72          permissions: {73            viewAllParticipants: true,74            flagActivities: role === 'creator'75          }76        });77      } catch (error) {78        console.error('Error in spectator:join:', error);79        socket.emit('error:unauthorized', {80          error: 'UNAUTHORIZED',81          message: 'Failed to join spectator room'82        });83      }84    });85 86    // Spectator watches a specific participant87    socket.on('spectator:watchParticipant', async (data) => {88      try {89        const { challengeId, spectatorId, participantId, problemId } = data;90 91        // Leave all previous participant rooms first92        const rooms = Array.from(socket.rooms);93        rooms.forEach(room => {94          if (room.startsWith('participant:') && room !== socket.id) {95            socket.leave(room);96          }97        });98 99        // Update spectator session100        await SpectatorSession.findOneAndUpdate(101          { socketId: socket.id, spectatorId },102          {103            $set: {104              watchingParticipants: [{105                participantId,106                startedAt: new Date()107              }]108            },109            lastActivity: new Date()110          }111        );112 113        // Join participant-specific room for updates114        const participantRoom = `participant:${participantId}:${problemId}`;115        socket.join(participantRoom);116        console.log('๐Ÿ‘๏ธ Spectator joined room:', {117          spectatorSocketId: socket.id,118          room: participantRoom,119          participantId,120          problemId121        });122 123        // Get current code from Redis or database124        try {125          const cacheKey = `code:${participantId}:${problemId}`;126          const cached = await redis.get(cacheKey);127          128          if (cached) {129            socket.emit('spectator:codeSnapshot', {130              participantId,131              problemId,132              code: cached.code,133              language: cached.language,134              lastUpdate: cached.lastUpdate,135              linesOfCode: cached.code.split('\n').length,136              participantInfo: {137                username: cached.username || 'Unknown',138                isActive: true139              }140            });141          }142        } catch (err) {143          console.log('Redis error, will fetch from database:', err.message);144        }145      } catch (error) {146        console.error('Error in spectator:watchParticipant:', error);147      }148    });149 150    // Spectator stops watching a participant151    socket.on('spectator:stopWatching', async (data) => {152      try {153        const { spectatorId, participantId } = data;154 155        // Leave participant room156        const rooms = Array.from(socket.rooms);157        rooms.forEach(room => {158          if (room.includes(`participant:${participantId}`)) {159            socket.leave(room);160          }161        });162 163        // Update spectator session164        await SpectatorSession.findOneAndUpdate(165          { socketId: socket.id, spectatorId },166          {167            $pull: {168              watchingParticipants: { participantId }169            },170            lastActivity: new Date()171          }172        );173      } catch (error) {174        console.error('Error in spectator:stopWatching:', error);175      }176    });177 178    // Spectator requests code snapshot179    socket.on('spectator:requestSnapshot', async (data) => {180      try {181        const { participantId, problemId } = data;182 183        // Get code from Redis184        const cacheKey = `code:${participantId}:${problemId}`;185        const cached = await redis.get(cacheKey);186        187        if (cached) {188          socket.emit('spectator:codeSnapshot', {189            participantId,190            problemId,191            code: cached.code,192            language: cached.language,193            lastUpdate: cached.lastUpdate,194            linesOfCode: cached.code.split('\n').length195          });196        }197      } catch (error) {198        console.error('Error in spectator:requestSnapshot:', error);199      }200    });201 202    // Spectator leaves203    socket.on('spectator:leave', async (data) => {204      try {205        const { challengeId, spectatorId } = data;206 207        // Mark session as inactive208        await SpectatorSession.findOneAndUpdate(209          { socketId: socket.id, spectatorId },210          { isActive: false }211        );212 213        // Leave all rooms214        const roomName = `challenge:${challengeId}:spectators`;215        socket.leave(roomName);216      } catch (error) {217        console.error('Error in spectator:leave:', error);218      }219    });220 221    // Participant code update (emitted by participants)222    socket.on('participant:codeUpdate', async (data) => {223      try {224        const { challengeId, participantId, problemId, code, changeType, delta, language } = data;225        console.log('๐ŸŸก SERVER RECEIVED participant:codeUpdate', {226          socketId: socket.id,227          participantId,228          problemId,229          codeLength: code?.length,230          timestamp: new Date().toISOString()231        });232 233        // IMMEDIATELY broadcast to spectators watching this participant (NO CACHING)234        const participantRoom = `participant:${participantId}:${problemId}`;235        236        // Get all sockets in the room237        const socketsInRoom = await io.in(participantRoom).fetchSockets();238        console.log('๐Ÿ“ข Broadcasting to room:', participantRoom, {239          socketsInRoom: socketsInRoom.length,240          socketIds: socketsInRoom.map(s => s.id)241        });242        243        io.to(participantRoom).emit('spectator:codeUpdate', {244          participantId,245          problemId,246          code,247          changeType: changeType || 'typing',248          timestamp: Date.now(),249          delta: delta || { linesAdded: 0, charactersAdded: 0 },250          language: language || 'javascript'251        });252 253        // Broadcast activity to spectators254        const challengeRoom = `challenge:${challengeId}:spectators`;255        io.to(challengeRoom).emit('spectator:participantActivity', {256          participantId,257          activity: 'typing',258          problemId,259          timestamp: Date.now()260        });261      } catch (error) {262        console.error('Error in participant:codeUpdate:', error);263      }264    });265 266    // Participant activity update267    socket.on('participant:activityUpdate', async (data) => {268      try {269        const { challengeId, participantId, activity, problemId } = data;270 271        // Update activity in Redis272        const activityKey = `participant:${participantId}:${challengeId}:activity`;273        await redis.set(activityKey, {274          currentProblem: { problemId },275          lastActivity: Date.now(),276          activity277        }, 60 * 60);278 279        // Broadcast to spectators280        const challengeRoom = `challenge:${challengeId}:spectators`;281        io.to(challengeRoom).emit('spectator:participantActivity', {282          participantId,283          activity,284          problemId,285          timestamp: Date.now()286        });287      } catch (error) {288        console.error('Error in participant:activityUpdate:', error);289      }290    });291 292    // Participant joins challenge293    socket.on('participant:join', async (data) => {294      try {295        const { challengeId, participantId, problemId } = data;296 297        // Broadcast to spectators298        const challengeRoom = `challenge:${challengeId}:spectators`;299        io.to(challengeRoom).emit('spectator:participantListUpdate', {300          action: 'joined',301          participant: {302            userId: participantId,303            currentProblem: problemId,304            isActive: true305          },306          timestamp: Date.now()307        });308      } catch (error) {309        console.error('Error in participant:join:', error);310      }311    });312 313    // Participant leaves314    socket.on('participant:leave', async (data) => {315      try {316        const { challengeId, participantId } = data;317 318        // Broadcast to spectators319        const challengeRoom = `challenge:${challengeId}:spectators`;320        io.to(challengeRoom).emit('spectator:participantListUpdate', {321          action: 'left',322          participant: {323            userId: participantId,324            isActive: false325          },326          timestamp: Date.now()327        });328      } catch (error) {329        console.error('Error in participant:leave:', error);330      }331    });332 333    // Handle disconnect334    socket.on('disconnect', async () => {335      try {336        // Mark all sessions with this socket as inactive337        await SpectatorSession.updateMany(338          { socketId: socket.id },339          { isActive: false }340        );341      } catch (error) {342        console.error('Error in disconnect:', error);343      }344    });345  });346}347 348module.exports = { initializeSpectatorSocket };349