mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Adds base for multi-room
Co-authored-by: roesnerb <roesnerb@users.noreply.github.com> Co-authored-by: MathieuSevignyLavallee <MathieuSevignyLavallee@users.noreply.github.com>
This commit is contained in:
parent
c8f9c5470c
commit
32bcb67f33
13 changed files with 5546 additions and 10 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -129,3 +129,5 @@ dist
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
db-backup/
|
db-backup/
|
||||||
|
|
||||||
|
deployments
|
||||||
|
|
@ -49,6 +49,13 @@ services:
|
||||||
- mongodb_data:/data/db
|
- mongodb_data:/data/db
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
# Ce conteneur contient la base de donnée cache nécessaire au bon fonctionnement des salles de quiz
|
||||||
|
valkey:
|
||||||
|
image: valkey/valkey:alpine
|
||||||
|
container_name: valkey
|
||||||
|
volumes:
|
||||||
|
- ./deployments/valkey.conf:/usr/local/etc/valkey/valkey.conf
|
||||||
|
|
||||||
# Ce conteneur assure que l'application est à jour en allant chercher s'il y a des mises à jours à chaque heure
|
# Ce conteneur assure que l'application est à jour en allant chercher s'il y a des mises à jours à chaque heure
|
||||||
watchtower:
|
watchtower:
|
||||||
image: containrrr/watchtower
|
image: containrrr/watchtower
|
||||||
|
|
|
||||||
18
quizRoom/Dockerfile
Normal file
18
quizRoom/Dockerfile
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Use the Node base image
|
||||||
|
FROM node:18
|
||||||
|
|
||||||
|
# Create a working directory
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy package.json and install dependencies
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy all source code to the container
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose WebSocket server port
|
||||||
|
EXPOSE 4500
|
||||||
|
|
||||||
|
# Start the WebSocket server
|
||||||
|
CMD ["node", "app.js"]
|
||||||
27
quizRoom/app.ts
Normal file
27
quizRoom/app.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import http from "http";
|
||||||
|
import { Server, ServerOptions } from "socket.io";
|
||||||
|
|
||||||
|
// Import setupWebsocket
|
||||||
|
import { setupWebsocket } from "./socket/setupWebSocket";
|
||||||
|
|
||||||
|
const port = process.env.WS_PORT ? parseInt(process.env.WS_PORT) : 4500;
|
||||||
|
|
||||||
|
// Create HTTP and WebSocket server
|
||||||
|
const server = http.createServer();
|
||||||
|
const ioOptions: Partial<ServerOptions> = {
|
||||||
|
path: "/socket.io",
|
||||||
|
cors: {
|
||||||
|
origin: "*",
|
||||||
|
methods: ["GET", "POST"],
|
||||||
|
credentials: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const io = new Server(server, ioOptions);
|
||||||
|
|
||||||
|
// Initialize WebSocket setup
|
||||||
|
setupWebsocket(io);
|
||||||
|
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(`WebSocket server is running on port ${port}`);
|
||||||
|
});
|
||||||
4784
quizRoom/package-lock.json
generated
Normal file
4784
quizRoom/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
21
quizRoom/package.json
Normal file
21
quizRoom/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "ets-pfe004-evaluetonsavoir-quizroom",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "app.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node app.js",
|
||||||
|
"test": "jest --colors"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"socket.io": "^4.7.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jest-mock": "^29.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
101
quizRoom/socket/setupWebSocket.ts
Normal file
101
quizRoom/socket/setupWebSocket.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { Server, Socket } from "socket.io";
|
||||||
|
|
||||||
|
const MAX_USERS_PER_ROOM = 60;
|
||||||
|
const MAX_TOTAL_CONNECTIONS = 2000;
|
||||||
|
|
||||||
|
export const setupWebsocket = (io: Server): void => {
|
||||||
|
let totalConnections = 0;
|
||||||
|
|
||||||
|
io.on("connection", (socket: Socket) => {
|
||||||
|
if (totalConnections >= MAX_TOTAL_CONNECTIONS) {
|
||||||
|
console.log("Connection limit reached. Disconnecting client.");
|
||||||
|
socket.emit("join-failure", "Le nombre maximum de connexions a été atteint");
|
||||||
|
socket.disconnect(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalConnections++;
|
||||||
|
console.log("A user connected:", socket.id, "| Total connections:", totalConnections);
|
||||||
|
|
||||||
|
socket.on("create-room", (sentRoomName?: string) => {
|
||||||
|
const roomName = sentRoomName ? sentRoomName.toUpperCase() : generateRoomName();
|
||||||
|
if (!io.sockets.adapter.rooms.get(roomName)) {
|
||||||
|
socket.join(roomName);
|
||||||
|
socket.emit("create-success", roomName);
|
||||||
|
} else {
|
||||||
|
socket.emit("create-failure");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("join-room", ({ enteredRoomName, username }: { enteredRoomName: string; username: string }) => {
|
||||||
|
if (io.sockets.adapter.rooms.has(enteredRoomName)) {
|
||||||
|
const clientsInRoom = io.sockets.adapter.rooms.get(enteredRoomName)?.size || 0;
|
||||||
|
|
||||||
|
if (clientsInRoom <= MAX_USERS_PER_ROOM) {
|
||||||
|
socket.join(enteredRoomName);
|
||||||
|
socket.to(enteredRoomName).emit("user-joined", { id: socket.id, name: username, answers: [] });
|
||||||
|
socket.emit("join-success");
|
||||||
|
} else {
|
||||||
|
socket.emit("join-failure", "La salle est remplie");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
socket.emit("join-failure", "Le nom de la salle n'existe pas");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("next-question", ({ roomName, question }: { roomName: string; question: string }) => {
|
||||||
|
socket.to(roomName).emit("next-question", question);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("launch-student-mode", ({ roomName, questions }: { roomName: string; questions: string[] }) => {
|
||||||
|
socket.to(roomName).emit("launch-student-mode", questions);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("end-quiz", ({ roomName }: { roomName: string }) => {
|
||||||
|
socket.to(roomName).emit("end-quiz");
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("message", (data: string) => {
|
||||||
|
console.log("Received message from", socket.id, ":", data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
totalConnections--;
|
||||||
|
console.log("A user disconnected:", socket.id, "| Total connections:", totalConnections);
|
||||||
|
|
||||||
|
for (const [room] of io.sockets.adapter.rooms) {
|
||||||
|
if (room !== socket.id) {
|
||||||
|
io.to(room).emit("user-disconnected", socket.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("submit-answer", ({
|
||||||
|
roomName,
|
||||||
|
username,
|
||||||
|
answer,
|
||||||
|
idQuestion,
|
||||||
|
}: {
|
||||||
|
roomName: string;
|
||||||
|
username: string;
|
||||||
|
answer: string;
|
||||||
|
idQuestion: string;
|
||||||
|
}) => {
|
||||||
|
socket.to(roomName).emit("submit-answer-room", {
|
||||||
|
idUser: socket.id,
|
||||||
|
username,
|
||||||
|
answer,
|
||||||
|
idQuestion,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const generateRoomName = (length = 6): string => {
|
||||||
|
const characters = "0123456789";
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
};
|
||||||
75
server/controllers/rooms.ts
Normal file
75
server/controllers/rooms.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { Redis } from 'ioredis';
|
||||||
|
import {
|
||||||
|
RoomInfo,
|
||||||
|
RoomOptions,
|
||||||
|
ProviderType,
|
||||||
|
ProviderConfig
|
||||||
|
} from '../../types/room';
|
||||||
|
import { BaseRoomProvider } from './providers/base-provider';
|
||||||
|
import { ClusterRoomProvider } from './providers/cluster-provider';
|
||||||
|
import { DockerRoomProvider } from './providers/docker-provider';
|
||||||
|
import { KubernetesRoomProvider } from './providers/kubernetes-provider';
|
||||||
|
|
||||||
|
interface RoomManagerOptions {
|
||||||
|
redisUrl?: string;
|
||||||
|
provider?: ProviderType;
|
||||||
|
providerOptions?: ProviderConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RoomManager {
|
||||||
|
private redis: Redis;
|
||||||
|
private provider: BaseRoomProvider<RoomInfo>;
|
||||||
|
|
||||||
|
constructor(options: RoomManagerOptions = {}) {
|
||||||
|
this.redis = new Redis(options.redisUrl || process.env.REDIS_URL);
|
||||||
|
this.provider = this.createProvider(
|
||||||
|
options.provider || process.env.ROOM_PROVIDER as ProviderType || 'cluster',
|
||||||
|
options.providerOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setupCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createProvider(
|
||||||
|
type: ProviderType,
|
||||||
|
options?: ProviderConfig
|
||||||
|
): BaseRoomProvider<RoomInfo> {
|
||||||
|
switch (type) {
|
||||||
|
case 'cluster':
|
||||||
|
return new ClusterRoomProvider(this.redis, options);
|
||||||
|
case 'docker':
|
||||||
|
return new DockerRoomProvider(this.redis, options);
|
||||||
|
case 'kubernetes':
|
||||||
|
return new KubernetesRoomProvider(this.redis, options);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown provider type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupCleanup(): void {
|
||||||
|
setInterval(() => {
|
||||||
|
this.provider.cleanup().catch(console.error);
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRoom(options: RoomOptions = {}): Promise<RoomInfo> {
|
||||||
|
const roomId = options.roomId || this.generateRoomId();
|
||||||
|
return await this.provider.createRoom(roomId, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteRoom(roomId: string): Promise<void> {
|
||||||
|
return await this.provider.deleteRoom(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoomStatus(roomId: string): Promise<RoomInfo | null> {
|
||||||
|
return await this.provider.getRoomStatus(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async listRooms(): Promise<RoomInfo[]> {
|
||||||
|
return await this.provider.listRooms();
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateRoomId(): string {
|
||||||
|
return `room-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
299
server/package-lock.json
generated
299
server/package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@valkey/valkey-glide": "^1.1.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.4",
|
"dotenv": "^16.4.4",
|
||||||
|
|
@ -21,6 +22,7 @@
|
||||||
"socket.io-client": "^4.7.2"
|
"socket.io-client": "^4.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.8.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-mock": "^29.7.0",
|
"jest-mock": "^29.7.0",
|
||||||
|
|
@ -1156,6 +1158,80 @@
|
||||||
"sparse-bitfield": "^3.0.3"
|
"sparse-bitfield": "^3.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@protobufjs/aspromise": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/base64": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/codegen": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/eventemitter": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/fetch": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.1",
|
||||||
|
"@protobufjs/inquire": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/float": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/inquire": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/path": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/pool": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/utf8": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@sinclair/typebox": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||||
|
|
@ -1273,11 +1349,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.8.7",
|
"version": "22.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz",
|
||||||
"integrity": "sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==",
|
"integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.25.1"
|
"undici-types": "~6.19.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/stack-utils": {
|
"node_modules/@types/stack-utils": {
|
||||||
|
|
@ -1314,6 +1391,155 @@
|
||||||
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
|
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@valkey/valkey-glide": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@valkey/valkey-glide/-/valkey-glide-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-7+NJxMiCfE/p5p7HpLnEKOVaYEcrMnTbGNT9ZBjP0QC8KO7BwSLnWlRYnnb4/6j6zEBc7ugVZ0CvJjJvTslcjw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.3"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@valkey/valkey-glide-darwin-arm64": "1.1.0",
|
||||||
|
"@valkey/valkey-glide-darwin-x64": "1.1.0",
|
||||||
|
"@valkey/valkey-glide-linux-arm64": "1.1.0",
|
||||||
|
"@valkey/valkey-glide-linux-musl-arm64": "1.1.0",
|
||||||
|
"@valkey/valkey-glide-linux-musl-x64": "1.1.0",
|
||||||
|
"@valkey/valkey-glide-linux-x64": "1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@valkey/valkey-glide-darwin-arm64": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@valkey/valkey-glide-darwin-arm64/-/valkey-glide-darwin-arm64-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-0ekMcRVcC+VhNG3ZeiLFNrIi9nxq2TM8Y0qoyGPpcb3q0+4AaSnHQR2YPnvn5Befq75ODJ+RctxQ6fbYNaArDg==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"glide-rs"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"glide-rs": "file:rust-client",
|
||||||
|
"long": "^5.2.3",
|
||||||
|
"npmignore": "^0.3.1",
|
||||||
|
"protobufjs": "^7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@valkey/valkey-glide-darwin-x64": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@valkey/valkey-glide-darwin-x64/-/valkey-glide-darwin-x64-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-fFfV5YPQ3RAnwlyjvyvfq6gxFo1FZMhChqINxxtKClwzIFg7493p3WNuOhH0M+CNyIGCle9tMx8dLPNWVdVk0Q==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"glide-rs"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"glide-rs": "file:rust-client",
|
||||||
|
"long": "^5.2.3",
|
||||||
|
"npmignore": "^0.3.1",
|
||||||
|
"protobufjs": "^7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@valkey/valkey-glide-linux-arm64": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@valkey/valkey-glide-linux-arm64/-/valkey-glide-linux-arm64-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-jY9MOeu0Ck9yLMv+LUTDCytDi8JMuWd6w27t2BKWvdnFSOLmZcKqBhkfIhQWBoXqo42uhcxzHULC5Pwtl0jLQg==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"glide-rs"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"glide-rs": "file:rust-client",
|
||||||
|
"long": "^5.2.3",
|
||||||
|
"npmignore": "^0.3.1",
|
||||||
|
"protobufjs": "^7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@valkey/valkey-glide-linux-musl-arm64": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@valkey/valkey-glide-linux-musl-arm64/-/valkey-glide-linux-musl-arm64-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-8H8BLG4ZEZuZSvuoYGKO157r/gMI3QnfjGMv3J+uzP2yKj2vZdqh9I46wiJsHl6B3gxFxtrleX7zoo5HY5NcoQ==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"glide-rs"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"glide-rs": "file:rust-client",
|
||||||
|
"long": "^5.2.3",
|
||||||
|
"npmignore": "^0.3.1",
|
||||||
|
"protobufjs": "^7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@valkey/valkey-glide-linux-musl-x64": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@valkey/valkey-glide-linux-musl-x64/-/valkey-glide-linux-musl-x64-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ljpgbfH124GeoFGIYzdoK5C8xsDCehm2U9RwYVJq2SAc5XyIyI80d7s0ZrjVAOhV77ebbVYn2i/j5bL/v/u/cw==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"glide-rs"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"glide-rs": "file:rust-client",
|
||||||
|
"long": "^5.2.3",
|
||||||
|
"npmignore": "^0.3.1",
|
||||||
|
"protobufjs": "^7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@valkey/valkey-glide-linux-x64": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@valkey/valkey-glide-linux-x64/-/valkey-glide-linux-x64-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-TIvJxMTCRa551u6HHwvLsJjJ+RSi8V4FiSTMJ9GOa5WAlT07A3bSDbMzxF7JqfvfDFFRk4vO1B/k+1JE3SlV6A==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"glide-rs"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"glide-rs": "file:rust-client",
|
||||||
|
"long": "^5.2.3",
|
||||||
|
"npmignore": "^0.3.1",
|
||||||
|
"protobufjs": "^7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/abbrev": {
|
"node_modules/abbrev": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
|
|
@ -2214,9 +2440,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||||
"integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
|
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
|
|
@ -4075,6 +4302,13 @@
|
||||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
|
|
@ -4501,6 +4735,25 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/npmignore": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/npmignore/-/npmignore-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-bBDWyDhP/p7fFlAvKrN1gl/q0nsxkouezRBJmfzvJNHnWbRlC8j2xV9zteIkS9tlFuECgaV3nlJixQpJRe5EQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.8"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"npmignore": "bin/npmignore"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/npmlog": {
|
"node_modules/npmlog": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||||
|
|
@ -4747,6 +5000,31 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/protobufjs": {
|
||||||
|
"version": "7.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
|
||||||
|
"integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.2",
|
||||||
|
"@protobufjs/base64": "^1.1.2",
|
||||||
|
"@protobufjs/codegen": "^2.0.4",
|
||||||
|
"@protobufjs/eventemitter": "^1.1.0",
|
||||||
|
"@protobufjs/fetch": "^1.1.0",
|
||||||
|
"@protobufjs/float": "^1.0.2",
|
||||||
|
"@protobufjs/inquire": "^1.1.0",
|
||||||
|
"@protobufjs/path": "^1.1.2",
|
||||||
|
"@protobufjs/pool": "^1.1.0",
|
||||||
|
"@protobufjs/utf8": "^1.1.0",
|
||||||
|
"@types/node": ">=13.7.0",
|
||||||
|
"long": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
|
|
@ -5598,9 +5876,10 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "5.25.3",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
"integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA=="
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@valkey/valkey-glide": "^1.1.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.4",
|
"dotenv": "^16.4.4",
|
||||||
|
|
@ -25,6 +26,7 @@
|
||||||
"socket.io-client": "^4.7.2"
|
"socket.io-client": "^4.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.8.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-mock": "^29.7.0",
|
"jest-mock": "^29.7.0",
|
||||||
|
|
|
||||||
34
server/roomsProviders/base-provider.ts
Normal file
34
server/roomsProviders/base-provider.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { GlideClient, GlideString } from "@valkey/valkey-glide";
|
||||||
|
import { RoomInfo, RoomOptions, BaseProviderConfig } from '../../types/room';
|
||||||
|
|
||||||
|
export abstract class BaseRoomProvider<T extends RoomInfo> {
|
||||||
|
protected valkey: GlideClient;
|
||||||
|
|
||||||
|
constructor(valkey: GlideClient, protected config: BaseProviderConfig = {}) {
|
||||||
|
this.valkey = valkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract createRoom(roomId: string, options?: RoomOptions): Promise<T>;
|
||||||
|
abstract deleteRoom(roomId: string): Promise<void>;
|
||||||
|
abstract getRoomStatus(roomId: string): Promise<T | null>;
|
||||||
|
abstract listRooms(): Promise<T[]>;
|
||||||
|
abstract cleanup(): Promise<void>;
|
||||||
|
|
||||||
|
protected async updateRoomInfo(roomId: string, info: Partial<RoomInfo>): Promise<boolean> {
|
||||||
|
let room = await this.getRoomInfo(roomId);
|
||||||
|
|
||||||
|
if(!room) return false;
|
||||||
|
|
||||||
|
for(let key in Object.keys(room)){
|
||||||
|
room[key]= info[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.valkey.set(`room:${roomId}`,room as Object as GlideString);
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async getRoomInfo(roomId: string): Promise<RoomInfo | null> {
|
||||||
|
const info = (await this.valkey.get(`room:${roomId}`)) as RoomInfo | null;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
117
server/roomsProviders/cluster-provider.ts
Normal file
117
server/roomsProviders/cluster-provider.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import * as cluster from 'node:cluster';
|
||||||
|
import { cpus } from 'node:os';
|
||||||
|
import { GlideClient } from "@valkey/valkey-glide";
|
||||||
|
import {
|
||||||
|
ClusterRoomInfo,
|
||||||
|
RoomOptions,
|
||||||
|
ClusterProviderConfig
|
||||||
|
} from '../../types/room';
|
||||||
|
import { BaseRoomProvider } from './base-provider';
|
||||||
|
|
||||||
|
export class ClusterRoomProvider extends BaseRoomProvider<ClusterRoomInfo> {
|
||||||
|
private workers: Map<number, { rooms: Set<string> }>;
|
||||||
|
|
||||||
|
constructor(valkey: GlideClient, config: ClusterProviderConfig = {}) {
|
||||||
|
super(valkey, config);
|
||||||
|
this.workers = new Map();
|
||||||
|
|
||||||
|
if (cluster.default.isPrimary) {
|
||||||
|
this.initializeCluster();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeCluster(): void {
|
||||||
|
const numCPUs = cpus().length;
|
||||||
|
|
||||||
|
for (let i = 0; i < numCPUs; i++) {
|
||||||
|
const worker = cluster.default.fork();
|
||||||
|
this.handleWorkerMessages(worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster.default.on('exit', (worker, code, signal) => {
|
||||||
|
console.log(`Worker ${worker.process.pid} died. Starting new worker...`);
|
||||||
|
const newWorker = cluster.default.fork();
|
||||||
|
this.handleWorkerMessages(newWorker);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleWorkerMessages(worker: cluster.Worker): void {
|
||||||
|
worker.on('message', async (msg: {
|
||||||
|
type: string;
|
||||||
|
roomId: string;
|
||||||
|
status: string;
|
||||||
|
}) => {
|
||||||
|
if (msg.type === 'room_status') {
|
||||||
|
await this.updateRoomInfo(msg.roomId, {
|
||||||
|
status: msg.status as any,
|
||||||
|
workerId: worker.id,
|
||||||
|
lastUpdate: Date.now()
|
||||||
|
} as Partial<ClusterRoomInfo>);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRoom(roomId: string, options: RoomOptions = {}): Promise<ClusterRoomInfo> {
|
||||||
|
const workerLoads = Array.from(this.workers.entries())
|
||||||
|
.map(([id, data]) => ({
|
||||||
|
id,
|
||||||
|
rooms: data.rooms.size
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.rooms - b.rooms);
|
||||||
|
|
||||||
|
const workerId = workerLoads[0].id;
|
||||||
|
const worker = cluster.default.workers?.[workerId];
|
||||||
|
|
||||||
|
if (!worker) {
|
||||||
|
throw new Error('No available workers');
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.send({ type: 'create_room', roomId, options });
|
||||||
|
|
||||||
|
const roomInfo: ClusterRoomInfo = {
|
||||||
|
roomId,
|
||||||
|
provider: 'cluster',
|
||||||
|
status: 'creating',
|
||||||
|
workerId,
|
||||||
|
pid: worker.process.pid!,
|
||||||
|
createdAt: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.updateRoomInfo(roomId, roomInfo);
|
||||||
|
return roomInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteRoom(roomId: string): Promise<void> {
|
||||||
|
const roomInfo = await this.getRoomInfo(roomId) as ClusterRoomInfo;
|
||||||
|
if (roomInfo?.workerId && cluster.Worker?.[roomInfo.workerId]) {
|
||||||
|
cluster.Worker[roomInfo.workerId].send({
|
||||||
|
type: 'delete_room',
|
||||||
|
roomId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await this.valkey.del(['room',roomId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoomStatus(roomId: string): Promise<ClusterRoomInfo | null> {
|
||||||
|
return await this.getRoomInfo(roomId) as ClusterRoomInfo | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async listRooms(): Promise<ClusterRoomInfo[]> {
|
||||||
|
const keys = await this.valkey.hkeys('room:*');
|
||||||
|
const rooms = await Promise.all(
|
||||||
|
keys.map(key => this.getRoomInfo(key.toString().split(':')[1]))
|
||||||
|
);
|
||||||
|
return rooms.filter((room): room is ClusterRoomInfo => room !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanup(): Promise<void> {
|
||||||
|
const rooms = await this.listRooms();
|
||||||
|
const staleTimeout = 30000; // 30 seconds
|
||||||
|
|
||||||
|
for (const room of rooms) {
|
||||||
|
if (Date.now() - (room.lastUpdate || room.createdAt) > staleTimeout) {
|
||||||
|
await this.deleteRoom(room.roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
types/room.ts
Normal file
69
types/room.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
export interface RoomInfo {
|
||||||
|
roomId: string;
|
||||||
|
status: RoomStatus;
|
||||||
|
createdAt: number;
|
||||||
|
lastUpdate?: number;
|
||||||
|
provider: ProviderType;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoomOptions {
|
||||||
|
roomId?: string;
|
||||||
|
maxUsers?: number;
|
||||||
|
timeout?: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RoomStatus = 'creating' | 'running' | 'error' | 'terminated';
|
||||||
|
export type ProviderType = 'cluster' | 'docker' | 'kubernetes';
|
||||||
|
|
||||||
|
// Provider-specific room information
|
||||||
|
export interface ClusterRoomInfo extends RoomInfo {
|
||||||
|
workerId: number;
|
||||||
|
pid: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DockerRoomInfo extends RoomInfo {
|
||||||
|
containerId: string;
|
||||||
|
containerIp: string;
|
||||||
|
containerStatus?: {
|
||||||
|
Running: boolean;
|
||||||
|
StartedAt: string;
|
||||||
|
FinishedAt: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KubernetesRoomInfo extends RoomInfo {
|
||||||
|
deploymentName: string;
|
||||||
|
namespace: string;
|
||||||
|
deploymentStatus?: {
|
||||||
|
availableReplicas: number;
|
||||||
|
readyReplicas: number;
|
||||||
|
replicas: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider configuration interfaces
|
||||||
|
export interface BaseProviderConfig {
|
||||||
|
redisUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterProviderConfig extends BaseProviderConfig {
|
||||||
|
maxWorkersPerRoom?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DockerProviderConfig extends BaseProviderConfig {
|
||||||
|
dockerConfig?: any;
|
||||||
|
networkName?: string;
|
||||||
|
containerImage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KubernetesProviderConfig extends BaseProviderConfig {
|
||||||
|
namespace?: string;
|
||||||
|
kubeConfig?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProviderConfig =
|
||||||
|
| ClusterProviderConfig
|
||||||
|
| DockerProviderConfig
|
||||||
|
| KubernetesProviderConfig;
|
||||||
Loading…
Reference in a new issue