Files
SuperVPN/.history/desktop_global/signaling-server/server_20251009094659.js
2025-10-09 09:57:24 +09:00

250 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const net = require('net');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
const MEDIA_PORT = process.env.MEDIA_PORT || 5000;
// Логирование
const log = (message) => {
console.log(`[${new Date().toISOString()}] ${message}`);
};
// Хранилище активных каналов
const activeChannels = new Map();
// Middleware
app.use(express.json());
app.use((req, res, next) => {
log(`${req.method} ${req.url} from ${req.ip}`);
next();
});
// CORS для всех доменов (в продакшене настроить конкретные домены)
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
// Маршрут для получения IP медиа-сервера (совместимость с оригинальным API)
app.get('/get-ip-kr.php', (req, res) => {
const port = req.query.port;
log(`IP request for channel: ${port}`);
// В простой реализации возвращаем IP этого же сервера
// В продакшене здесь может быть логика балансировки
const mediaServerIP = process.env.MEDIA_SERVER_IP || '127.0.0.1';
// Возвращаем IP в бинарном формате (4 байта) как в оригинале
const ipParts = mediaServerIP.split('.').map(n => parseInt(n));
const buffer = Buffer.from(ipParts);
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Length': buffer.length
});
res.end(buffer);
log(`Returned IP: ${mediaServerIP} for channel ${port}`);
});
// REST API для управления
app.get('/api/status', (req, res) => {
res.json({
status: 'running',
activeChannels: activeChannels.size,
uptime: process.uptime(),
channels: Array.from(activeChannels.keys())
});
});
app.get('/api/channels', (req, res) => {
const channelList = Array.from(activeChannels.entries()).map(([channel, data]) => ({
channel,
connections: data.connections,
created: data.created
}));
res.json(channelList);
});
// Веб-интерфейс
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>VideoReader Global Server</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.status { background: #f0f0f0; padding: 20px; border-radius: 5px; }
.channel { margin: 10px 0; padding: 10px; background: #e8f4fd; border-radius: 3px; }
</style>
</head>
<body>
<h1>🌐 VideoReader Global Server</h1>
<div class="status">
<h2>Server Status</h2>
<p><strong>Status:</strong> Running</p>
<p><strong>Signaling Port:</strong> ${PORT}</p>
<p><strong>Media Port:</strong> ${MEDIA_PORT}</p>
<p><strong>Active Channels:</strong> <span id="channelCount">Loading...</span></p>
</div>
<h2>📡 Configuration</h2>
<pre>
{
"SignalingServer": "${req.get('host')}",
"DataPort": ${MEDIA_PORT},
"DefaultChannel": 10,
"FallbackIP": "127.0.0.1",
"UseSSL": false,
"ProfileName": "local"
}
</pre>
<h2>📊 Active Channels</h2>
<div id="channels">Loading...</div>
<script>
async function updateStatus() {
try {
const response = await fetch('/api/channels');
const channels = await response.json();
document.getElementById('channelCount').textContent = channels.length;
const channelsDiv = document.getElementById('channels');
channelsDiv.innerHTML = channels.length === 0
? '<p>No active channels</p>'
: channels.map(ch =>
\`<div class="channel">
<strong>Channel \${ch.channel}:</strong>
\${ch.connections} connections
<small>(created: \${new Date(ch.created).toLocaleString()})</small>
</div>\`
).join('');
} catch (e) {
console.error('Failed to update status:', e);
}
}
updateStatus();
setInterval(updateStatus, 5000);
</script>
</body>
</html>
`);
});
// TCP медиа-сервер
const mediaServer = net.createServer((socket) => {
let channel = null;
let deviceType = null;
log(`New connection from ${socket.remoteAddress}:${socket.remotePort}`);
socket.on('data', (data) => {
if (channel === null && data.length >= 2) {
// Первые 2 байта: тип устройства (0=receiver, 1=sender) и канал
deviceType = data[0];
channel = data[1];
log(`Device connected - Type: ${deviceType === 0 ? 'receiver' : 'sender'}, Channel: ${channel}`);
// Регистрируем канал
if (!activeChannels.has(channel)) {
activeChannels.set(channel, {
connections: 0,
created: new Date().toISOString(),
receivers: [],
senders: []
});
}
const channelData = activeChannels.get(channel);
channelData.connections++;
if (deviceType === 0) {
channelData.receivers.push(socket);
} else {
channelData.senders.push(socket);
}
// Обработка данных после установки канала
if (data.length > 2) {
handleMediaData(socket, data.slice(2), channel, deviceType);
}
} else if (channel !== null) {
// Передача медиа-данных
handleMediaData(socket, data, channel, deviceType);
}
});
socket.on('close', () => {
if (channel !== null) {
const channelData = activeChannels.get(channel);
if (channelData) {
channelData.connections--;
// Удаляем сокет из соответствующего массива
if (deviceType === 0) {
channelData.receivers = channelData.receivers.filter(s => s !== socket);
} else {
channelData.senders = channelData.senders.filter(s => s !== socket);
}
// Удаляем канал если нет подключений
if (channelData.connections <= 0) {
activeChannels.delete(channel);
log(`Channel ${channel} removed - no active connections`);
}
}
}
log(`Connection closed from ${socket.remoteAddress}:${socket.remotePort}`);
});
socket.on('error', (err) => {
log(`Socket error: ${err.message}`);
});
});
function handleMediaData(fromSocket, data, channel, fromType) {
const channelData = activeChannels.get(channel);
if (!channelData) return;
// Пересылаем данные от отправителей к получателям и наоборот
const targetSockets = fromType === 0 ? channelData.senders : channelData.receivers;
targetSockets.forEach(socket => {
if (socket !== fromSocket && socket.writable) {
try {
socket.write(data);
} catch (err) {
log(`Error forwarding data: ${err.message}`);
}
}
});
}
// Запуск серверов
app.listen(PORT, () => {
log(`🌐 Signaling server running on port ${PORT}`);
log(`📡 Web interface: http://localhost:${PORT}`);
});
mediaServer.listen(MEDIA_PORT, () => {
log(`📺 Media server running on port ${MEDIA_PORT}`);
});
// Graceful shutdown
process.on('SIGINT', () => {
log('Shutting down servers...');
mediaServer.close();
process.exit(0);
});
// Экспорт для возможности тестирования
module.exports = { app, mediaServer };