no library for socket.io
This commit is contained in:
MathieuSevignyLavallee 2024-11-15 17:46:01 -05:00
parent 0b2552bdff
commit b608793ac3
8 changed files with 738 additions and 39 deletions

View file

@ -1,5 +1,8 @@
js_import njs/main.js; js_import njs/main.js;
js_set $room_cache_check main.checkCache;
js_set $room_cache_set main.setCache;
map $http_upgrade $connection_upgrade { map $http_upgrade $connection_upgrade {
default upgrade; default upgrade;
'' close; '' close;
@ -26,26 +29,28 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
} }
# DNS resolver for Docker's internal DNS
resolver 127.0.0.11 valid=30s;
# Game WebSocket routing # Game WebSocket routing
location ~ /api/room/([^/]+)/socket { location ~/api/room/([^/]+)/socket {
set $room_id $1; set $room_id $1;
js_content main.routeWebSocket;
}
proxy_pass http://room_$room_id:4500; # WebSocket proxy location
location @websocket_proxy {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Timeouts
proxy_connect_timeout 7m; proxy_connect_timeout 7m;
proxy_send_timeout 7m; proxy_send_timeout 7m;
proxy_read_timeout 7m; proxy_read_timeout 7m;
proxy_buffering off; proxy_buffering off;
proxy_pass $proxy_target;
} }
location / { location / {
@ -54,4 +59,4 @@ server {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
} }
} }

View file

@ -1,9 +1,8 @@
const roomCache = new Map();
async function fetchRoomInfo(r) { async function fetchRoomInfo(r) {
try { try {
// Make request to API to get room info let res = await r.subrequest(`/api/room/${r.variables.room_id}`, { method: 'GET' });
let res = await r.subrequest('/api/room/' + r.variables.room_id, {
method: 'GET'
});
if (res.status !== 200) { if (res.status !== 200) {
r.error(`Failed to fetch room info: ${res.status}`); r.error(`Failed to fetch room info: ${res.status}`);
@ -11,7 +10,7 @@ async function fetchRoomInfo(r) {
} }
let room = JSON.parse(res.responseText); let room = JSON.parse(res.responseText);
r.error(`Debug: Room info: ${JSON.stringify(room)}`); // Debug log r.error(`Debug: Room info fetched: ${JSON.stringify(room)}`);
return room; return room;
} catch (error) { } catch (error) {
r.error(`Error fetching room info: ${error}`); r.error(`Error fetching room info: ${error}`);
@ -19,36 +18,48 @@ async function fetchRoomInfo(r) {
} }
} }
async function routeWebSocket(r) { function checkCache(r) {
try { let room = roomCache.get(r.variables.room_id);
const roomInfo = await fetchRoomInfo(r); if (room) {
r.error(`Cache hit for room_id: ${r.variables.room_id}`);
r.return(200, JSON.stringify(room));
} else {
r.error(`Cache miss for room_id: ${r.variables.room_id}`);
r.return(404);
}
}
if (!roomInfo || !roomInfo.host) { function setCache(r) {
r.error(`Debug: Invalid room info: ${JSON.stringify(roomInfo)}`); let room = JSON.parse(r.responseBody);
roomCache.set(r.variables.room_id, room);
r.error(`Cached room info: ${JSON.stringify(room)}`);
}
async function routeWebSocket(r) {
let room = roomCache.get(r.variables.room_id);
if (!room) {
r.error(`Cache miss. Fetching room info for: ${r.variables.room_id}`);
room = await fetchRoomInfo(r);
if (!room || !room.host) {
r.error(`Invalid room info for room_id: ${r.variables.room_id}`);
r.return(404, 'Room not found or invalid'); r.return(404, 'Room not found or invalid');
return; return;
} }
// Make sure the host includes protocol if not already present roomCache.set(r.variables.room_id, room); // Cache the result
let proxyUrl = roomInfo.host; } else {
if (!proxyUrl.startsWith('http://') && !proxyUrl.startsWith('https://')) { r.error(`Cache hit for room_id: ${r.variables.room_id}`);
proxyUrl = 'http://' + proxyUrl;
}
r.error(`Debug: Original URL: ${r.uri}`);
r.error(`Debug: Setting proxy target to: ${proxyUrl}`);
r.error(`Debug: Headers: ${JSON.stringify(r.headersIn)}`);
// Set the proxy target variable
r.variables.proxy_target = proxyUrl;
// Redirect to the websocket proxy
r.internalRedirect('@websocket_proxy');
} catch (error) {
r.error(`WebSocket routing error: ${error}`);
r.return(500, 'Internal routing error');
} }
let proxyUrl = room.host.startsWith('http://') || room.host.startsWith('https://')
? room.host
: `http://${room.host}`;
r.error(`Routing WebSocket to: ${proxyUrl}`);
r.variables.proxy_target = proxyUrl;
r.internalRedirect('@websocket_proxy');
} }
export default { routeWebSocket }; export default { routeWebSocket, checkCache, setCache };

View file

@ -0,0 +1,32 @@
import { io } from "socket.io-client";
export class Student {
constructor(username, roomName) {
this.username = username;
this.roomName = roomName;
this.socket = null;
}
connectToRoom(baseUrl) {
this.socket = io(baseUrl, {
path: `/api/room/${this.roomName}/socket`,
transports: ['websocket'],autoConnect: true,
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 10000,
timeout: 20000,
});
this.socket.on('connect', () => {
this.joinRoom(this.roomName,this.username);
});
return this.socket;
}
joinRoom(roomName, username) {
if (this.socket) {
this.socket.emit('join-room', { roomName, username });
}
}
}

View file

@ -0,0 +1,35 @@
import { io } from "socket.io-client";
export class Teacher {
constructor(username, roomName) {
this.username = username;
this.roomName = roomName;
this.socket = null;
}
connectToRoom(baseUrl) {
this.socket = io(baseUrl, {
path: `/api/room/${this.roomName}/socket`,
transports: ['websocket'],autoConnect: true,
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 10000,
timeout: 20000,
});
this.socket.on('connect', () => {
this.createRoom(this.roomName);
});
this.socket.on('create-success', () => {
});
return this.socket;
}
createRoom() {
if (this.socket) {
this.socket.emit('create-room', this.roomName || undefined);
}
}
}

128
test/stressTest/main.js Normal file
View file

@ -0,0 +1,128 @@
import { attemptLoginOrRegister, createRoomContainer } from './utility/apiServices.js';
import { Student } from './class/student.js';
import { Teacher } from './class/teacher.js';
const BASE_URL = 'http://localhost';
const user = {
username: 'admin@example.com',
password: 'adminPassword',
};
const numberRooms = 30;
const studentPerRoom = 59; // Max is 60; 1 slot is reserved for the teacher
const roomAssociations = {};
const allSockets = []; // Track all active WebSocket connections
async function createRoomsAndTeachers(token) {
const roomCreationPromises = [];
const teachers = [];
for (let index = 0; index < numberRooms; index++) {
roomCreationPromises.push(
createRoomContainer(BASE_URL, token).then((room) => {
if (room?.id) {
const teacher = new Teacher(`teacher_${index}`, room.id);
teachers.push(teacher);
roomAssociations[room.id] = {
teacher,
students: [],
};
// Track teacher WebSocket for cleanup
if (teacher.socket) {
allSockets.push(teacher.socket);
}
}
})
);
}
await Promise.allSettled(roomCreationPromises);
console.log(`Created ${Object.keys(roomAssociations).length} rooms with associated teachers.`);
return teachers;
}
async function connectTeachersToRooms(teachers) {
const teacherConnectionPromises = teachers.map(async (teacher) => {
await teacher.connectToRoom(BASE_URL);
if (teacher.socket) {
allSockets.push(teacher.socket); // Track WebSocket
}
});
await Promise.allSettled(teacherConnectionPromises);
console.log('All teachers connected to their rooms.');
}
async function addAndConnectStudents() {
const studentCreationPromises = [];
Object.entries(roomAssociations).forEach(([roomId, association], roomIndex) => {
for (let i = 0; i < studentPerRoom; i++) {
const student = new Student(`student_${roomIndex}_${i}`, roomId);
association.students.push(student);
studentCreationPromises.push(
student.connectToRoom(BASE_URL).then(() => {
if (student.socket) {
allSockets.push(student.socket); // Track WebSocket
}
})
);
}
});
await Promise.allSettled(studentCreationPromises);
console.log('All students connected to their respective rooms.');
}
function closeAllSockets() {
console.log('Closing all WebSocket connections...');
allSockets.forEach((socket) => {
try {
if (socket.readyState === socket.OPEN) {
socket.close(); // Gracefully close the WebSocket
console.log('Closed WebSocket connection.');
}
} catch (error) {
console.error('Error closing WebSocket:', error.message);
}
});
console.log('All WebSocket connections closed.');
}
async function main() {
try {
const token = await attemptLoginOrRegister(BASE_URL, user.username, user.password);
if (!token) {
console.error('Failed to log in. Exiting...');
return;
}
const teachers = await createRoomsAndTeachers(token);
await connectTeachersToRooms(teachers);
await addAndConnectStudents();
console.log('All tasks completed.');
} catch (error) {
console.error('An error occurred:', error.message);
} finally {
closeAllSockets();
}
}
// Handle script termination (Ctrl+C)
process.on('SIGINT', () => {
console.log('Script interrupted (Ctrl+C).');
closeAllSockets();
process.exit(0); // Exit cleanly
});
// Handle script exit
process.on('exit', () => {
closeAllSockets();
});
main();

344
test/stressTest/package-lock.json generated Normal file
View file

@ -0,0 +1,344 @@
{
"name": "stresstest",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "stresstest",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"axios": "^1.7.7",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
},
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
},
"node_modules/@types/cors": {
"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": "*"
}
},
"node_modules/@types/node": {
"version": "22.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
"integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
"dependencies": {
"undici-types": "~6.19.8"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/cookie": {
"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/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/engine.io": {
"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.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-client": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz",
"integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/socket.io": {
"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.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
}
},
"node_modules/socket.io-client": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
}
}
}

View file

@ -0,0 +1,17 @@
{
"name": "stresstest",
"version": "1.0.0",
"description": "main.js",
"type": "module",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.7.7",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1"
}
}

View file

@ -0,0 +1,127 @@
import axios from "axios";
/**
* Logs in a user.
* @param {string} baseUrl - The base URL of the API.
* @param {string} email - The user's email.
* @param {string} password - The user's password.
* @returns {Promise<string>} - The authentication token if successful.
*/
async function login(baseUrl, email, password) {
if (!email || !password) {
throw new Error("Email and password are required.");
}
const url = `${baseUrl}/api/user/login`;
const payload = { email, password };
try {
const res = await axios.post(url, payload, {
headers: { "Content-Type": "application/json" },
});
if (res.status !== 200 || !res.data.token) {
throw new Error(`Login failed. Status: ${res.status}`);
}
console.log(`Login successful for ${email}`);
return res.data.token;
} catch (error) {
console.error(`Login error for ${email}:`, error.message);
throw error;
}
}
/**
* Registers a new user.
* @param {string} baseUrl - The base URL of the API.
* @param {string} email - The user's email.
* @param {string} password - The user's password.
* @returns {Promise<string>} - A success message if registration is successful.
*/
async function register(baseUrl, email, password) {
if (!email || !password) {
throw new Error("Email and password are required.");
}
const url = `${baseUrl}/api/user/register`;
const payload = { email, password };
try {
const res = await axios.post(url, payload, {
headers: { "Content-Type": "application/json" },
});
if (res.status !== 200) {
throw new Error(`Registration failed. Status: ${res.status}`);
}
console.log(`Registration successful for ${email}`);
return res.data.message || "Registration completed successfully.";
} catch (error) {
console.error(`Registration error for ${email}:`, error.message);
throw error;
}
}
/**
* Attempts to log in a user, or registers and logs in if the login fails.
* @param {string} baseUrl - The base URL of the API.
* @param {string} username - The user's email/username.
* @param {string} password - The user's password.
* @returns {Promise<string|null>} - The authentication token if successful, otherwise null.
*/
export async function attemptLoginOrRegister(baseUrl, username, password) {
try {
const token = await login(baseUrl, username, password);
console.log(`User successfully logged in: ${username}`);
return token;
} catch (loginError) {
console.log(`Login failed for ${username}. Attempting registration...`);
try {
const registerResponse = await register(baseUrl, username, password);
console.log(`User successfully registered: ${username}`);
const token = await login(baseUrl, username, password);
console.log(`User successfully logged in after registration: ${username}`);
return token;
} catch (registerError) {
console.error(`Registration failed for ${username}:`, registerError.message);
return null;
}
}
}
/**
* Creates a new room.
* @param {string} baseUrl - The base URL of the API.
* @param {string} token - The authorization token.
* @returns {Promise<object>} - The created room object if successful.
*/
export async function createRoomContainer(baseUrl, token) {
if (!token) {
throw new Error("Authorization token is required.");
}
const url = `${baseUrl}/api/room`;
try {
const res = await axios.post(url, {}, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
if (res.status !== 200) {
throw new Error(`Room creation failed. Status: ${res.status}`);
}
console.log("Room successfully created:", res.data);
return res.data;
} catch (error) {
console.error("Room creation error:", error.message);
throw error;
}
}