Files
stream.graff.tech-new/server/src/index.ts
T

348 lines
11 KiB
TypeScript

import { Elysia } from "elysia";
import cors from "@elysiajs/cors";
import { authController } from "./controllers/auth";
import { sessionController } from "./controllers/session";
import { companyController } from "./controllers/company";
import { branchController } from "./controllers/branch";
import { serverController } from "./controllers/server";
import { chatController } from "./controllers/chat";
import { serverSessionService } from "./services/serverSession";
import { saveChatMessage } from "./services/chat";
import { Server } from "socket.io";
import { createServer } from "http";
import { AddressInfo } from "net";
const app = new Elysia();
app.use(
cors({
origin: true,
// credentials: true,
})
);
app.use(authController);
app.use(sessionController);
app.use(companyController);
app.use(branchController);
app.use(serverController);
app.use(chatController);
app.listen(process.env.PORT || 3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
// Setup Socket.IO для WebRTC на отдельном порту
const httpServer = createServer();
const io = new Server(httpServer, {
cors: {
origin: "*",
},
});
httpServer.listen(process.env.SOCKET_PORT || 3001, () => {
console.log(
`🎥 WebRTC Socket.IO server running on port ${
(httpServer.address() as AddressInfo).port
}`
);
});
interface Room {
id: string;
participants: Set<string>;
}
interface User {
id: string;
roomId?: string;
socketId: string;
}
const rooms = new Map<string, Room>();
const users = new Map<string, User>();
// Вспомогательные функции
function findUserBySocketId(socketId: string): User | undefined {
for (const [userId, user] of users.entries()) {
if (user.socketId === socketId) {
return user;
}
}
return undefined;
}
function findSocketIdByUserId(userId: string): string | undefined {
const user = users.get(userId);
return user?.socketId;
}
io.on("connection", (socket) => {
console.log(`[WebRTC] User connected: ${socket.id}`);
// Присоединение к комнате
socket.on("join-room", async ({ roomId, userId, isAudioEnabled, isVideoEnabled }) => {
console.log(
`[WebRTC] User ${userId} (socket: ${socket.id}) joining room ${roomId}, audio: ${isAudioEnabled}, video: ${isVideoEnabled}`
);
// Покинуть предыдущую комнату если была
const existingUser = users.get(userId);
if (existingUser?.roomId) {
console.log(
`[WebRTC] User ${userId} leaving previous room ${existingUser.roomId}`
);
socket.leave(existingUser.roomId);
const prevRoom = rooms.get(existingUser.roomId);
if (prevRoom) {
prevRoom.participants.delete(userId);
socket.to(existingUser.roomId).emit("user-left", userId);
}
}
// Присоединиться к новой комнате
socket.join(roomId);
// Создать комнату если не существует
if (!rooms.has(roomId)) {
console.log(`[WebRTC] Creating new room: ${roomId}`);
rooms.set(roomId, {
id: roomId,
participants: new Set(),
});
}
const room = rooms.get(roomId)!;
room.participants.add(userId);
// Сохранить пользователя
users.set(userId, {
id: userId,
roomId,
socketId: socket.id,
});
console.log(
`[WebRTC] Room ${roomId} now has participants:`,
Array.from(room.participants)
);
// Уведомить других участников
socket.to(roomId).emit("user-joined", userId);
console.log(
`[WebRTC] Notified room ${roomId} about user ${userId} joining`
);
// Отправить состояние аудио/видео нового пользователя всем в комнате
socket.to(roomId).emit("audio-toggle", { userId, isEnabled: isAudioEnabled !== false });
socket.to(roomId).emit("video-toggle", { userId, isEnabled: isVideoEnabled !== false });
// Отправить список участников новому пользователю
const participants = Array.from(room.participants).filter(
(id) => id !== userId
);
console.log(
`[WebRTC] Sending participant list to ${userId}:`,
participants
);
socket.emit("room-participants", participants);
});
// Покидание комнаты
socket.on("leave-room", ({ roomId, userId }) => {
console.log(`[WebRTC] User ${userId} leaving room ${roomId}`);
socket.leave(roomId);
const room = rooms.get(roomId);
if (room) {
room.participants.delete(userId);
socket.to(roomId).emit("user-left", userId);
// Удалить пустую комнату
if (room.participants.size === 0) {
rooms.delete(roomId);
console.log(`[WebRTC] Deleted empty room ${roomId}`);
}
}
const user = users.get(userId);
if (user) {
user.roomId = undefined;
}
});
// WebRTC сигнализация
socket.on("offer", ({ target, offer }) => {
console.log(`[WebRTC] Offer from ${socket.id} to ${target}`);
const targetSocketId = findSocketIdByUserId(target);
const senderUser = findUserBySocketId(socket.id);
if (targetSocketId && senderUser) {
socket.to(targetSocketId).emit("offer", {
offer,
sender: senderUser.id,
});
}
});
socket.on("answer", ({ target, answer }) => {
console.log(`[WebRTC] Answer from ${socket.id} to ${target}`);
const targetSocketId = findSocketIdByUserId(target);
const senderUser = findUserBySocketId(socket.id);
if (targetSocketId && senderUser) {
socket.to(targetSocketId).emit("answer", {
answer,
sender: senderUser.id,
});
}
});
socket.on("ice-candidate", ({ target, candidate }) => {
console.log(`[WebRTC] ICE candidate from ${socket.id} to ${target}`);
const targetSocketId = findSocketIdByUserId(target);
const senderUser = findUserBySocketId(socket.id);
if (targetSocketId && senderUser) {
socket.to(targetSocketId).emit("ice-candidate", {
candidate,
sender: senderUser.id,
});
}
});
// Обработка audio/video toggle
socket.on("audio-toggle", ({ roomId, userId, isEnabled }) => {
console.log(`[WebRTC] Audio toggle from ${userId} in room ${roomId}: ${isEnabled}`);
// Отправляем всем в комнате (кроме отправителя)
socket.to(roomId).emit("audio-toggle", { userId, isEnabled });
});
socket.on("video-toggle", ({ roomId, userId, isEnabled }) => {
console.log(`[WebRTC] Video toggle from ${userId} in room ${roomId}: ${isEnabled}`);
// Отправляем всем в комнате (кроме отправителя)
socket.to(roomId).emit("video-toggle", { userId, isEnabled });
});
// Обработка speaking state
socket.on("speaking-state", ({ roomId, userId, isSpeaking }) => {
// Отправляем всем в комнате (кроме отправителя)
socket.to(roomId).emit("speaking-state", { userId, isSpeaking });
});
// Обработка сообщений чата
socket.on("chat-message", async ({ roomId, userId, content, userName }) => {
console.log(`[Chat] Message from ${userId} in room ${roomId}: ${content}`);
const user = findUserBySocketId(socket.id);
if (!user || user.roomId !== roomId) {
console.warn(`[Chat] User ${socket.id} is not in room ${roomId}`);
return;
}
try {
// Сохраняем сообщение в БД
const savedMessage = await saveChatMessage({
sessionId: roomId,
userId: userId || null, // null для анонимных пользователей
content,
type: "text",
});
// Формируем сообщение для отправки клиентам
const messageToSend = {
id: savedMessage.id,
senderId: userId,
senderName: userName,
content: savedMessage.content,
timestamp: savedMessage.createdAt,
type: savedMessage.type,
};
// Отправляем всем в комнате (включая отправителя)
io.to(roomId).emit("chat-message", messageToSend);
console.log(`[Chat] Message broadcasted to room ${roomId}`);
} catch (error) {
console.error(`[Chat] Error saving message:`, error);
socket.emit("chat-error", {
message: "Failed to save message",
});
}
});
// Отключение
socket.on("disconnect", () => {
console.log(`[WebRTC] User disconnected: ${socket.id}`);
// Найти пользователя по socket ID
const disconnectedUser = findUserBySocketId(socket.id);
if (disconnectedUser) {
users.delete(disconnectedUser.id);
if (disconnectedUser.roomId) {
const room = rooms.get(disconnectedUser.roomId);
if (room) {
room.participants.delete(disconnectedUser.id);
socket
.to(disconnectedUser.roomId)
.emit("user-left", disconnectedUser.id);
// Удалить пустую комнату
if (room.participants.size === 0) {
rooms.delete(disconnectedUser.roomId);
console.log(
`[WebRTC] Deleted empty room ${disconnectedUser.roomId}`
);
}
}
}
}
});
});
// Запуск фоновой задачи для автоматического назначения серверов
const AUTO_ASSIGN_INTERVAL_MS = parseInt(
process.env.AUTO_ASSIGN_INTERVAL_MS || "1000",
10
); // 1 секунда по умолчанию
async function autoAssignServersTask() {
try {
const results = await serverSessionService.autoAssignServers();
if (results.total > 0) {
console.log(
`[${new Date().toISOString()}] 🎯 Auto-assign: ${
results.assigned
} assigned, ${results.failed} failed из ${results.total}`
);
if (results.errors.length > 0) {
console.error(
`[${new Date().toISOString()}] ❌ Ошибки назначения:`,
results.errors
);
}
}
} catch (error) {
console.error(
`[${new Date().toISOString()}] ❌ Ошибка auto-assign:`,
error instanceof Error ? error.message : error
);
}
// Планируем следующий запуск
setTimeout(autoAssignServersTask, AUTO_ASSIGN_INTERVAL_MS);
}
// Запускаем через 1 секунду после старта сервера
setTimeout(() => {
console.log(
`[${new Date().toISOString()}] 🤖 Запуск фоновой задачи auto-assign (интервал: ${AUTO_ASSIGN_INTERVAL_MS}ms)`
);
autoAssignServersTask();
}, 1000);