Files
Puechberty Arthur 739fa54719 feat: add settings page for user display name and implement local storage hook
feat: create SocketContext for managing socket connections

feat: implement useLocalStorage hook for persistent state management

feat: set up SQLite database with room management functions

feat: add utility functions for generating room IDs and formatting dates
2026-03-30 23:13:20 +02:00

228 lines
6.3 KiB
JavaScript

const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const { Server } = require('socket.io');
const Database = require('better-sqlite3');
const path = require('path');
const dev = process.env.NODE_ENV !== 'production';
const hostname = 'localhost';
const port = parseInt(process.env.PORT || '3000', 10);
const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();
// Connexion à la base de données SQLite
const dbPath = path.join(process.cwd(), 'visio.db');
const db = new Database(dbPath);
// Fonction pour nettoyer les salles inactives dans la DB
function cleanupInactiveRoomsInDB() {
const stmt = db.prepare(`
UPDATE rooms SET is_active = 0
WHERE is_active = 1
AND datetime(last_activity, '+5 minutes') < datetime('now')
`);
const result = stmt.run();
if (result.changes > 0) {
console.log(`Nettoyage: ${result.changes} salle(s) inactive(s) fermée(s)`);
}
return result.changes;
}
// Stockage des salles et participants en mémoire
const rooms = new Map();
const roomTimers = new Map();
// Durée d'inactivité avant fermeture (5 minutes)
const INACTIVITY_TIMEOUT = 5 * 60 * 1000;
app.prepare().then(() => {
// Nettoyer les salles inactives au démarrage
console.log('Nettoyage des salles inactives au démarrage...');
cleanupInactiveRoomsInDB();
// Lancer un nettoyage périodique toutes les minutes
setInterval(() => {
cleanupInactiveRoomsInDB();
}, 60 * 1000);
const httpServer = createServer((req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
});
const io = new Server(httpServer, {
cors: {
origin: '*',
methods: ['GET', 'POST']
}
});
// Fonction pour réinitialiser le timer d'inactivité
function resetRoomTimer(roomId) {
if (roomTimers.has(roomId)) {
clearTimeout(roomTimers.get(roomId));
}
const timer = setTimeout(() => {
const room = rooms.get(roomId);
if (room && room.participants.size === 0) {
console.log(`Fermeture de la salle ${roomId} pour inactivité`);
io.to(roomId).emit('room-closed');
rooms.delete(roomId);
roomTimers.delete(roomId);
// Marquer la salle comme fermée dans la DB
fetch(`http://localhost:${port}/api/rooms/${roomId}`, {
method: 'DELETE'
}).catch(console.error);
}
}, INACTIVITY_TIMEOUT);
roomTimers.set(roomId, timer);
}
io.on('connection', (socket) => {
console.log('Nouvelle connexion:', socket.id);
let currentRoom = null;
let currentUser = null;
socket.on('join-room', ({ roomId, userName }) => {
currentRoom = roomId;
currentUser = { id: socket.id, name: userName, videoOff: false, muted: false };
// Rejoindre la room Socket.io
socket.join(roomId);
// Initialiser ou récupérer la salle
if (!rooms.has(roomId)) {
rooms.set(roomId, {
participants: new Map()
});
}
const room = rooms.get(roomId);
// Envoyer la liste des participants existants au nouveau (avec leur état audio/vidéo)
const existingUsers = Array.from(room.participants.values());
socket.emit('existing-users', { users: existingUsers });
// Ajouter le nouveau participant
room.participants.set(socket.id, currentUser);
// Notifier les autres de la nouvelle connexion
socket.to(roomId).emit('user-joined', {
userId: socket.id,
userName: userName
});
// Réinitialiser le timer d'inactivité
resetRoomTimer(roomId);
console.log(`${userName} a rejoint la salle ${roomId}`);
});
socket.on('offer', ({ offer, to }) => {
const room = rooms.get(currentRoom);
const fromUser = room?.participants.get(socket.id);
socket.to(to).emit('offer', {
offer,
from: socket.id,
fromName: fromUser?.name || 'Inconnu'
});
});
socket.on('answer', ({ answer, to }) => {
socket.to(to).emit('answer', {
answer,
from: socket.id
});
});
socket.on('ice-candidate', ({ candidate, to }) => {
socket.to(to).emit('ice-candidate', {
candidate,
from: socket.id
});
});
socket.on('name-change', ({ name }) => {
if (currentRoom && currentUser) {
currentUser.name = name;
const room = rooms.get(currentRoom);
if (room) {
room.participants.set(socket.id, currentUser);
}
socket.to(currentRoom).emit('user-name-changed', {
userId: socket.id,
newName: name
});
}
});
socket.on('video-toggle', ({ videoOff }) => {
if (currentRoom && currentUser) {
currentUser.videoOff = videoOff;
const room = rooms.get(currentRoom);
if (room) {
room.participants.set(socket.id, currentUser);
}
socket.to(currentRoom).emit('user-video-toggle', {
userId: socket.id,
videoOff
});
}
});
socket.on('audio-toggle', ({ muted }) => {
if (currentRoom && currentUser) {
currentUser.muted = muted;
const room = rooms.get(currentRoom);
if (room) {
room.participants.set(socket.id, currentUser);
}
socket.to(currentRoom).emit('user-audio-toggle', {
userId: socket.id,
muted
});
}
});
// Répondre aux pings des clients pour la détection de déconnexion
socket.on('ping-server', () => {
socket.emit('pong-server');
});
socket.on('disconnect', () => {
if (currentRoom) {
const room = rooms.get(currentRoom);
if (room) {
room.participants.delete(socket.id);
// Notifier les autres
socket.to(currentRoom).emit('user-left', {
userId: socket.id
});
console.log(`Utilisateur ${socket.id} a quitté la salle ${currentRoom}`);
// Si la salle est vide, démarrer le timer de fermeture
if (room.participants.size === 0) {
resetRoomTimer(currentRoom);
}
}
}
});
});
httpServer.listen(port, () => {
console.log(`> Serveur prêt sur http://${hostname}:${port}`);
});
});