UNPKG

aethercall

Version:

A scalable WebRTC video calling API built with Node.js and OpenVidu

347 lines (313 loc) 12.6 kB
/** * Session Management Routes * HTTP endpoints for session operations */ const express = require('express'); const { body, param, query, validationResult } = require('express-validator'); const logger = require('../../../utils/logger'); module.exports = (dependencies) => { const router = express.Router(); const { openviduAPI, storage, tokenManager } = dependencies; /** * Validation middleware */ const handleValidationErrors = (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ error: 'Validation Error', details: errors.array(), requestId: req.context?.requestId }); } next(); }; /** * Authentication middleware */ const authenticateToken = async (req, res, next) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Unauthorized', message: 'Missing or invalid authorization header', requestId: req.context?.requestId }); } const token = authHeader.substring(7); const tokenData = tokenManager.validateAPIToken(token); req.auth = tokenData; next(); } catch (error) { res.status(401).json({ error: 'Unauthorized', message: error.message, requestId: req.context?.requestId }); } }; /** * GET /api/sessions * Get all active sessions */ router.get('/', authenticateToken, async (req, res, next) => { try { const sessions = await storage.getActiveSessions(); res.json({ success: true, data: sessions, count: sessions.length, requestId: req.context?.requestId }); } catch (error) { logger.error('Error fetching sessions:', error); next(error); } } ); /** * POST /api/sessions * Create a new session */ router.post('/', authenticateToken, [ body('mediaMode').optional().isIn(['ROUTED', 'RELAYED']), body('recordingMode').optional().isIn(['ALWAYS', 'MANUAL', 'NEVER']), body('customSessionId').optional().isString().isLength({ min: 1, max: 50 }), body('defaultRecordingProperties').optional().isObject(), body('metadata').optional().isObject() ], handleValidationErrors, async (req, res, next) => { try { const sessionOptions = { mediaMode: req.body.mediaMode || 'ROUTED', recordingMode: req.body.recordingMode || 'MANUAL', customSessionId: req.body.customSessionId, defaultRecordingProperties: req.body.defaultRecordingProperties || {}, metadata: req.body.metadata || {} }; // Create session in OpenVidu const openviduSession = await openviduAPI.createSession(sessionOptions); // Store session data locally const sessionData = { sessionId: openviduSession.sessionId, status: 'active', mediaMode: sessionOptions.mediaMode, recordingMode: sessionOptions.recordingMode, metadata: sessionOptions.metadata, createdBy: req.auth.clientId, connections: [], recordings: [], openviduData: openviduSession }; await storage.storeSession(openviduSession.sessionId, sessionData); logger.info(`Session created: ${openviduSession.sessionId}`, { clientId: req.auth.clientId, requestId: req.context?.requestId }); res.status(201).json({ success: true, data: { sessionId: openviduSession.sessionId, status: 'active', mediaMode: sessionOptions.mediaMode, recordingMode: sessionOptions.recordingMode, metadata: sessionOptions.metadata, createdAt: sessionData.createdAt }, requestId: req.context?.requestId }); } catch (error) { logger.error('Error creating session:', error); next(error); } } ); /** * GET /api/sessions/:sessionId * Get session details */ router.get('/:sessionId', authenticateToken, [ param('sessionId').isString().isLength({ min: 1 }) ], handleValidationErrors, async (req, res, next) => { try { const sessionId = req.params.sessionId; // Get session from local storage const sessionData = await storage.getSession(sessionId); if (!sessionData) { return res.status(404).json({ error: 'Not Found', message: 'Session not found', requestId: req.context?.requestId }); } // Get fresh data from OpenVidu try { const openviduSession = await openviduAPI.getSession(sessionId); sessionData.openviduData = openviduSession; // Update local storage await storage.updateSession(sessionId, { openviduData: openviduSession, lastSyncAt: new Date() }); } catch (openviduError) { // If OpenVidu session doesn't exist, mark as inactive if (openviduError.message.includes('404')) { await storage.updateSession(sessionId, { status: 'ended' }); sessionData.status = 'ended'; } } res.json({ success: true, data: { sessionId: sessionData.sessionId, status: sessionData.status, mediaMode: sessionData.mediaMode, recordingMode: sessionData.recordingMode, metadata: sessionData.metadata, connections: sessionData.connections || [], recordings: sessionData.recordings || [], createdAt: sessionData.createdAt, updatedAt: sessionData.updatedAt }, requestId: req.context?.requestId }); } catch (error) { logger.error('Error fetching session:', error); next(error); } } ); /** * PATCH /api/sessions/:sessionId * Update session metadata */ router.patch('/:sessionId', authenticateToken, [ param('sessionId').isString().isLength({ min: 1 }), body('metadata').optional().isObject() ], handleValidationErrors, async (req, res, next) => { try { const sessionId = req.params.sessionId; const updates = {}; if (req.body.metadata) { updates.metadata = req.body.metadata; } // Update local storage await storage.updateSession(sessionId, updates); logger.info(`Session updated: ${sessionId}`, { updates, clientId: req.auth.clientId, requestId: req.context?.requestId }); res.json({ success: true, message: 'Session updated successfully', requestId: req.context?.requestId }); } catch (error) { logger.error('Error updating session:', error); next(error); } } ); /** * DELETE /api/sessions/:sessionId * Close a session */ router.delete('/:sessionId', authenticateToken, [ param('sessionId').isString().isLength({ min: 1 }) ], handleValidationErrors, async (req, res, next) => { try { const sessionId = req.params.sessionId; // Close session in OpenVidu try { await openviduAPI.closeSession(sessionId); } catch (openviduError) { // Session might already be closed in OpenVidu logger.warn(`OpenVidu session ${sessionId} was not found for closure:`, openviduError.message); } // Update local storage await storage.updateSession(sessionId, { status: 'ended', endedAt: new Date() }); logger.info(`Session closed: ${sessionId}`, { clientId: req.auth.clientId, requestId: req.context?.requestId }); res.json({ success: true, message: 'Session closed successfully', requestId: req.context?.requestId }); } catch (error) { logger.error('Error closing session:', error); next(error); } } ); /** * POST /api/sessions/:sessionId/force-disconnect/:connectionId * Force disconnect a connection from a session */ router.post('/:sessionId/force-disconnect/:connectionId', authenticateToken, [ param('sessionId').isString().isLength({ min: 1 }), param('connectionId').isString().isLength({ min: 1 }) ], handleValidationErrors, async (req, res, next) => { try { const { sessionId, connectionId } = req.params; // This would require additional OpenVidu API calls // For now, we'll just update our local storage const sessionData = await storage.getSession(sessionId); if (!sessionData) { return res.status(404).json({ error: 'Not Found', message: 'Session not found', requestId: req.context?.requestId }); } // Remove connection from session data if (sessionData.connections) { sessionData.connections = sessionData.connections.filter( conn => conn.connectionId !== connectionId ); await storage.updateSession(sessionId, { connections: sessionData.connections }); } logger.info(`Connection ${connectionId} disconnected from session ${sessionId}`, { clientId: req.auth.clientId, requestId: req.context?.requestId }); res.json({ success: true, message: 'Connection disconnected successfully', requestId: req.context?.requestId }); } catch (error) { logger.error('Error disconnecting connection:', error); next(error); } } ); return router; };