Merge branch 'main' into fuhrmanator/issue78

This commit is contained in:
Christopher (Cris) Fuhrman 2025-01-10 11:01:29 -05:00 committed by GitHub
commit e0ac770230
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 2283 additions and 3187 deletions

2
.gitignore vendored
View file

@ -73,7 +73,7 @@ web_modules/
.yarn-integrity
# dotenv environment variable files
.env
server/.env
.env.development.local
.env.test.local
.env.production.local

View file

@ -1,2 +1 @@
**/node_modules
.env

2
client/.env Normal file
View file

@ -0,0 +1,2 @@
VITE_BACKEND_URL=http://localhost:4400
VITE_AZURE_BACKEND_URL=http://localhost:4400

View file

@ -1,7 +1,2 @@
global.import = {
meta: {
env: {
VITE_BACKEND_URL: 'https://ets-glitch-backend.glitch.me/'
}
}
};
process.env.VITE_BACKEND_URL = 'http://localhost:4000/';
process.env.VITE_BACKEND_SOCKET_URL = 'https://ets-glitch-backend.glitch.me/';

5224
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,11 +5,11 @@ import { ENV_VARIABLES } from '../../constants';
jest.mock('socket.io-client');
jest.mock('../../constants', () => ({
ENV_VARIABLES: {
VITE_BACKEND_URL: 'https://ets-glitch-backend.glitch.me/'
}
}));
// jest.mock('../../constants', () => ({
// ENV_VARIABLES: {
// VITE_BACKEND_SOCKET_URL: 'https://ets-glitch-backend.glitch.me/'
// }
// }));
describe('WebSocketService', () => {
let mockSocket: Partial<Socket>;
@ -29,13 +29,13 @@ describe('WebSocketService', () => {
});
test('connect should initialize socket connection', () => {
WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
expect(io).toHaveBeenCalled();
expect(WebsocketService['socket']).toBe(mockSocket);
});
test('disconnect should terminate socket connection', () => {
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
expect(WebsocketService['socket']).toBeTruthy();
WebsocketService.disconnect();
expect(mockSocket.disconnect).toHaveBeenCalled();
@ -43,7 +43,7 @@ describe('WebSocketService', () => {
});
test('createRoom should emit create-room event', () => {
WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
WebsocketService.createRoom();
expect(mockSocket.emit).toHaveBeenCalledWith('create-room');
});
@ -52,7 +52,7 @@ describe('WebSocketService', () => {
const roomName = 'testRoom';
const question = { id: 1, text: 'Sample Question' };
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
WebsocketService.nextQuestion(roomName, question);
expect(mockSocket.emit).toHaveBeenCalledWith('next-question', { roomName, question });
});
@ -61,7 +61,7 @@ describe('WebSocketService', () => {
const roomName = 'testRoom';
const questions = [{ id: 1, text: 'Sample Question' }];
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
WebsocketService.launchStudentModeQuiz(roomName, questions);
expect(mockSocket.emit).toHaveBeenCalledWith('launch-student-mode', {
roomName,
@ -72,7 +72,7 @@ describe('WebSocketService', () => {
test('endQuiz should emit end-quiz event with correct parameters', () => {
const roomName = 'testRoom';
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
WebsocketService.endQuiz(roomName);
expect(mockSocket.emit).toHaveBeenCalledWith('end-quiz', { roomName });
});
@ -81,7 +81,7 @@ describe('WebSocketService', () => {
const enteredRoomName = 'testRoom';
const username = 'testUser';
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
WebsocketService.joinRoom(enteredRoomName, username);
expect(mockSocket.emit).toHaveBeenCalledWith('join-room', { enteredRoomName, username });
});

View file

@ -56,7 +56,7 @@ const MultipleChoiceQuestion: React.FC<Props> = (props) => {
(choice.isCorrect ? '✅' : '❌')}
<div className={`circle ${selected}`}>{alphabet[i]}</div>
<div className={`answer-text ${selected}`}>
{formatLatex(choice.text.text)}
<div dangerouslySetInnerHTML={{ __html: formatLatex(choice.text.text) }} />
</div>
</Button>
{choice.feedback && showAnswer && (

View file

@ -1,7 +1,11 @@
// constants.tsx
const ENV_VARIABLES = {
MODE: 'production',
VITE_BACKEND_URL: process.env.VITE_BACKEND_URL || ""
VITE_BACKEND_URL: process.env.VITE_BACKEND_URL || "",
VITE_BACKEND_SOCKET_URL: process.env.VITE_BACKEND_SOCKET_URL || "",
};
console.log(`ENV_VARIABLES.VITE_BACKEND_URL=${ENV_VARIABLES.VITE_BACKEND_URL}`);
console.log(`ENV_VARIABLES.VITE_BACKEND_SOCKET_URL=${ENV_VARIABLES.VITE_BACKEND_SOCKET_URL}`);
export { ENV_VARIABLES };

View file

@ -34,7 +34,8 @@ const JoinRoom: React.FC = () => {
}, []);
const handleCreateSocket = () => {
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
console.log(`JoinRoom: handleCreateSocket: ${ENV_VARIABLES.VITE_BACKEND_SOCKET_URL}`);
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
socket.on('join-success', () => {
setIsWaitingForTeacher(true);
@ -63,10 +64,10 @@ const JoinRoom: React.FC = () => {
socket.on('connect_error', (error) => {
switch (error.message) {
case 'timeout':
setConnectionError("Le serveur n'est pas disponible");
setConnectionError("JoinRoom: timeout: Le serveur n'est pas disponible");
break;
case 'websocket error':
setConnectionError("Le serveur n'est pas disponible");
setConnectionError("JoinRoom: websocket error: Le serveur n'est pas disponible");
break;
}
setIsConnecting(false);

View file

@ -30,6 +30,7 @@ import {
FileDownload,
Add,
Upload,
FolderCopy,
ContentCopy,
Edit,
Share,
@ -413,8 +414,8 @@ const Dashboard: React.FC = () => {
<IconButton
color="primary"
onClick={handleDuplicateFolder}
disabled={selectedFolderId == ''} // cannot action on all
> <ContentCopy /> </IconButton>
disabled={selectedFolder == ''} // cannot action on all
> <FolderCopy /> </IconButton>
</Tooltip>
<Tooltip title="Supprimer dossier" placement="top">

View file

@ -22,7 +22,6 @@
.quizEditor .editSection {
width: 100%;
height: 78vh;
display: flex;
}

View file

@ -49,6 +49,7 @@ const ManageRoom: React.FC = () => {
setQuiz(quiz as QuizType);
if (!socket) {
console.log(`no socket in ManageRoom, creating one.`);
createWebSocketRoom();
}
@ -80,15 +81,16 @@ const ManageRoom: React.FC = () => {
};
const createWebSocketRoom = () => {
console.log('Creating WebSocket room...');
setConnectingError('');
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
socket.on('connect', () => {
webSocketService.createRoom();
});
socket.on('connect_error', (error) => {
setConnectingError('Erreur lors de la connexion... Veuillez réessayer');
console.error('WebSocket connection error:', error);
console.error('ManageRoom: WebSocket connection error:', error);
});
socket.on('create-success', (roomName: string) => {
setRoomName(roomName);

View file

@ -246,7 +246,7 @@ class ApiService {
const result: AxiosResponse = await axios.post(url, body, { headers: headers });
if (result.status !== 200) {
throw new Error(`La supression du compte a échoué. Status: ${result.status}`);
throw new Error(`La suppression du compte a échoué. Status: ${result.status}`);
}
return true;
@ -328,7 +328,8 @@ class ApiService {
if (axios.isAxiosError(error)) {
const err = error as AxiosError;
const data = err.response?.data as { error: string } | undefined;
return data?.error || 'Erreur serveur inconnue lors de la requête.';
const url = err.config?.url || 'URL inconnue';
return data?.error || `Erreur serveur inconnue lors de la requête (${url}).`;
}
return `Une erreur inattendue s'est produite.`

View file

@ -21,14 +21,23 @@ class WebSocketService {
private socket: Socket | null = null;
connect(backendUrl: string): Socket {
// console.log(backendUrl);
this.socket = io(`${backendUrl}`, {
console.log(`WebSocketService.connect('${backendUrl}')`);
// // Ensure the URL uses wss: if the URL starts with https:
// const protocol = backendUrl.startsWith('https:') ? 'wss:' : 'ws:';
// console.log(`WebSocketService.connect: protocol=${protocol}`);
// const url = backendUrl.replace(/^http(s):/, protocol);
// console.log(`WebSocketService.connect: changed url=${url}`);
const url = backendUrl || window.location.host;
this.socket = io(url, {
transports: ['websocket'],
reconnectionAttempts: 1
});
return this.socket;
}
disconnect() {
if (this.socket) {
@ -74,15 +83,15 @@ class WebSocketService {
// idQuestion: string
) {
if (this.socket) {
this.socket?.emit('submit-answer',
// {
// answer: answer,
// roomName: roomName,
// username: username,
// idQuestion: idQuestion
// }
answerData
);
this.socket?.emit('submit-answer',
// {
// answer: answer,
// roomName: roomName,
// username: username,
// idQuestion: idQuestion
// }
answerData
);
}
}
}

View file

@ -3,7 +3,7 @@
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ES2020",
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */

View file

@ -3,22 +3,39 @@ import react from '@vitejs/plugin-react-swc';
import pluginChecker from 'vite-plugin-checker';
import EnvironmentPlugin from 'vite-plugin-environment';
// Filter out environment variables with invalid identifiers
const filteredEnv = Object.keys(process.env).reduce((acc, key) => {
// Only include environment variables with valid JavaScript identifiers
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
acc[key] = process.env[key];
}
return acc;
}, {});
// https://vitejs.dev/config/
export default defineConfig({
base: "/",
plugins: [
react(),
pluginChecker({ typescript: true }),
EnvironmentPlugin('all'),
EnvironmentPlugin(filteredEnv),
],
preview: {
port: 5173,
strictPort: true
},
server: {
port: 5173,
strictPort: true,
host: true,
origin: "http://0.0.0.0:5173",
port: 5173,
strictPort: true,
host: true,
origin: "http://0.0.0.0:5173",
},
build: {
sourcemap: true, // Enable source maps
rollupOptions: {
output: {
sourcemapExcludeSources: true, // Exclude sources from source maps
},
},
},
});

View file

@ -1,12 +1,14 @@
version: '3'
services:
services:
frontend:
image: fuhrmanator/evaluetonsavoir-frontend:latest
container_name: frontend
ports:
- "5173:5173"
environment:
VITE_BACKEND_URL: "http://localhost:4400"
# don't define VITE_BACKEND_SOCKET_URL so it will default to window.location.host
# VITE_BACKEND_SOCKET_URL: ""
restart: always
backend:

View file

@ -1,7 +1,7 @@
// Import API
const express = require("express");
const http = require("http");
const dotenv = require('dotenv')
const dotenv = require('dotenv');
// Import Sockets
const { setupWebsocket } = require("./socket/socket");
@ -39,9 +39,9 @@ module.exports.images = imagesControllerInstance;
const userRouter = require('./routers/users.js');
const folderRouter = require('./routers/folders.js');
const quizRouter = require('./routers/quiz.js');
const imagesRouter = require('./routers/images.js')
const imagesRouter = require('./routers/images.js');
// Setup environement
// Setup environment
dotenv.config();
const errorHandler = require("./middleware/errorHandler.js");
@ -50,7 +50,7 @@ const app = express();
const cors = require("cors");
const bodyParser = require('body-parser');
const configureServer = (httpServer) => {
const configureServer = (httpServer, isDev) => {
return new Server(httpServer, {
path: "/socket.io",
cors: {
@ -58,14 +58,22 @@ const configureServer = (httpServer) => {
methods: ["GET", "POST"],
credentials: true,
},
secure: !isDev, // true for https, false for http
});
};
// Start sockets
const server = http.createServer(app);
// Start sockets (depending on the dev or prod environment)
let server = http.createServer(app);
let isDev = process.env.NODE_ENV === 'development';
console.log(`Environnement: ${process.env.NODE_ENV} (${isDev ? 'dev' : 'prod'})`);
const io = configureServer(server);
console.log(`server.io configured: ${io.secure ? 'secure' : 'not secure'}`);
setupWebsocket(io);
console.log(`Websocket setup with on() listeners.`);
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
@ -76,22 +84,20 @@ app.use('/api/folder', folderRouter);
app.use('/api/quiz', quizRouter);
app.use('/api/image', imagesRouter);
app.use(errorHandler)
app.use(errorHandler);
// Start server
async function start() {
const port = process.env.PORT || 4400;
// Check DB connection
await db.connect()
await db.connect();
db.getConnection();
console.log(`Connexion MongoDB établie`);
server.listen(port, () => {
console.log(`Serveur écoutant sur le port ${port}`);
});
}
start();

View file

@ -21,6 +21,7 @@
"socket.io-client": "^4.7.2"
},
"devDependencies": {
"cross-env": "^7.0.3",
"jest": "^29.7.0",
"jest-mock": "^29.7.0",
"nodemon": "^3.0.1",
@ -1231,9 +1232,9 @@
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
},
"node_modules/@types/cors": {
"version": "2.8.15",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.15.tgz",
"integrity": "sha512-n91JxbNLD8eQIuXDIChAN1tCKNWCEgpceU9b7ZMbFA+P+Q4yIeh80jizFLEvolRPc1ES0VdwFlGv+kJTSirogw==",
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
"dependencies": {
"@types/node": "*"
}
@ -2046,9 +2047,9 @@
"dev": true
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@ -2102,6 +2103,24 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.1"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
},
"engines": {
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -2286,16 +2305,16 @@
}
},
"node_modules/engine.io": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
"integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
@ -2347,19 +2366,19 @@
}
},
"node_modules/engine.io/node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": {
"ms": "2.1.2"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@ -2371,9 +2390,9 @@
}
},
"node_modules/engine.io/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/error-ex": {
"version": "1.3.2",
@ -2496,16 +2515,16 @@
}
},
"node_modules/express": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@ -5092,15 +5111,15 @@
}
},
"node_modules/socket.io": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz",
"integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.5.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},

View file

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "node app.js",
"dev": "nodemon app.js",
"dev": "cross-env NODE_ENV=development nodemon app.js",
"test": "jest --colors"
},
"keywords": [],
@ -25,6 +25,7 @@
"socket.io-client": "^4.7.2"
},
"devDependencies": {
"cross-env": "^7.0.3",
"jest": "^29.7.0",
"jest-mock": "^29.7.0",
"nodemon": "^3.0.1",

View file

@ -68,7 +68,7 @@ const setupWebsocket = (io) => {
});
socket.on("next-question", ({ roomName, question }) => {
console.log("next-question", roomName, question);
// console.log("next-question", roomName, question);
socket.to(roomName).emit("next-question", question);
});