348 lines
11 KiB
TypeScript
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);
|