"use strict"; global.MY_date=require('./www/js/date.js'); // ЗАГРУЗКА const api = require('./api.js'); const game = require('./game.js'); const ws = require('./ws.js'); const fs = require('node:fs/promises'); const path = require('path'); const http = require('http'); // Запуск команд const util = require('util'); const runCommand = util.promisify(require('child_process').exec); let ws_cfg={ zlibDeflateOptions: {chunkSize: 1024, memLevel: 9,level: 7}, zlibInflateOptions: {chunkSize: 10 * 1024 }, clientNoContextTakeover: true, // Defaults to negotiated value. serverNoContextTakeover: true, // Defaults to negotiated value. serverMaxWindowBits: 12, // Defaults to negotiated value. concurrencyLimit: 16, // Limits zlib concurrency for perf. threshold: 128 // Size (in bytes) below which messages } const WebSocket = require('ws'); // Добавляем обработку ошибок для WebSocket сервера try { global.wsServer = new WebSocket.Server({ port: process.env.PORT || 9000, perMessageDeflate: ws_cfg, host: '0.0.0.0' // Слушаем на всех интерфейсах }); // Обработка ошибок сервера wsServer.on('error', (error) => { console.error('[ERROR] WebSocket сервер на порту 9000:', error); if (error.code === 'EADDRINUSE') { console.error('[ERROR] ❌ Порт 9000 уже занят! Возможные причины:'); console.error(' 1. Другой экземпляр сервера уже запущен'); console.error(' 2. Новый сервер (index_old.ts) также запущен'); console.error(' 3. Процесс не был корректно остановлен'); console.error('[РЕШЕНИЕ] 💡 Выполните команды:'); console.error(' - pkill -f "node.*index" (остановить все серверы)'); console.error(' - lsof -i :9000 (проверить кто использует порт)'); console.error(' - kill -9 (принудительно остановить процесс)'); // Не завершаем процесс сразу, даем время на чтение сообщения setTimeout(() => process.exit(1), 3000); } }); console.log('[OK] WebSocket сервер запущен на порту 9000'); } catch (error) { console.error('[FATAL] Не удалось создать WebSocket сервер:', error); process.exit(1); } // Убираем локальный сервер авторизации - используем только внешний async function main() { // Эмуляция serialcpu для Windows try { global.serialcpu = (await runCommand("cat /proc/cpuinfo | grep Serial")).stdout.split(": ")[1]; if (serialcpu.length>5) serialcpu=serialcpu.substring(0, serialcpu.length - 2); } catch (error) { // На Windows используем mock значение global.serialcpu = "mock-serial-cpu-12345"; console.log("[MOCK] Используем эмуляцию serialcpu для Windows"); } console.log("serialcpu========", global.serialcpu) // ОПРЕДЕЛЕНИЕ ORANGE PI global.isOrangePi = false; global.staticDirectory = '../dist/client'; // По умолчанию обычная сборка try { // Проверяем архитектуру процессора const cpuInfo = await runCommand("uname -m"); const isARM = cpuInfo.stdout.includes('arm'); // Проверяем модель устройства const deviceInfo = await runCommand("cat /proc/device-tree/model 2>/dev/null || echo 'unknown'"); const isOrange = deviceInfo.stdout.toLowerCase().includes('orange'); // Проверяем переменную окружения const envTarget = process.env.TARGET === 'orange-pi'; global.isOrangePi = isARM || isOrange || envTarget; if (global.isOrangePi) { global.staticDirectory = '../dist/client-orange-pi'; console.log("🍊 [ORANGE PI] Определена Orange PI плата - использую оптимизированную сборку"); console.log("📁 [ORANGE PI] Статические файлы: dist/client-orange-pi"); } else { console.log("💻 [DESKTOP] Обычное устройство - использую стандартную сборку"); console.log("📁 [DESKTOP] Статические файлы: dist/client"); } } catch (error) { console.log("⚠️ [WARNING] Не удалось определить тип устройства, использую стандартную сборку"); } // Инициализация подключения к серверу global.conn_to_server = false; // ВЕРСИЯ global.VER={ prg:0, git:null, commit:null, name:null }; // try { let dan= JSON.parse(await fs.readFile('package.json', "utf8")); VER.name=dan.name; VER.prg=dan.version; } catch (error) {} runCommand("git symbolic-ref --short HEAD").then((d)=>{ VER.git=d.stdout.slice(0, -1); runCommand("git log --oneline | wc -l").then((d)=>{VER.commit=d.stdout.slice(0, -1)}).catch(()=>{}); }).catch(()=>{}); const ws_toserver = require('./ws-toserver.js'); // Добавляем обработку ошибок для HTTP сервера const httpServer = http.createServer( (req, res)=> { let jsonString = '', origin=req.headers.origin; // Улучшенная поддержка CORS для development, production и локальной сети if (origin) { // Разрешаем доступ с любых IP в локальной сети const allowedOrigins = [ "https://code.tir.moygig.ru", "http://localhost:5173", "http://localhost:3000", "http://localhost:5000", "http://127.0.0.1:5173", "http://127.0.0.1:5000" ]; // Проверяем, что origin содержит localhost или IP адрес const isLocalNetwork = origin.includes('localhost') || origin.includes('127.0.0.1') || origin.includes('192.168.') || origin.includes('10.0.') || origin.includes('172.16.') || origin.includes('172.17.') || origin.includes('172.18.') || origin.includes('172.19.') || origin.includes('172.2') || origin.includes('172.3'); if (allowedOrigins.includes(origin) || isLocalNetwork) { res.setHeader('Access-Control-Allow-Origin', origin); res.setHeader('Access-Control-Allow-Credentials', 'true'); console.log(`[CORS] ✅ Разрешен доступ с: ${origin}`); } else { console.log(`[CORS] ⚠️ Заблокирован доступ с: ${origin}`); } } res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // Обработка OPTIONS запросов if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } req.on('data', (data) => jsonString += data ); // Пришла информация - записали. req.on('end', () => { // Обработка /api/task-file - проксирование файлов с внешнего сервера if (req.url.startsWith('/api/task-file/') && req.method === 'GET') { const fileId = req.url.substring('/api/task-file/'.length); console.log('[API] Запрос файла с ID:', fileId); // Проксируем запрос к внешнему серверу через HTTP const http = require('http'); const externalUrl = `http://ws.tirpriz.ru/api/task-file/${fileId}`; console.log('[API] Проксируем запрос к:', externalUrl); http.get(externalUrl, (externalRes) => { console.log('[API] Ответ от внешнего сервера:', externalRes.statusCode); if (externalRes.statusCode === 200) { // Передаем заголовки от внешнего сервера res.writeHead(200, { 'Content-Type': externalRes.headers['content-type'] || 'application/octet-stream', 'Content-Length': externalRes.headers['content-length'], 'Cache-Control': 'public, max-age=3600' }); // Проксируем данные externalRes.pipe(res); console.log('[API] ✅ Файл передан с внешнего сервера, ID:', fileId); } else if (externalRes.statusCode === 302 || externalRes.statusCode === 301) { // Обрабатываем редирект const redirectUrl = externalRes.headers.location; console.log('[API] Перенаправление на:', redirectUrl); // Отправляем редирект клиенту res.writeHead(302, { 'Location': redirectUrl }); res.end(); } else { // Файл не найден console.log('[API] Файл не найден, статус:', externalRes.statusCode); res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'File not found', fileId: fileId, status: externalRes.statusCode })); } }).on('error', (err) => { console.error('[API] Ошибка при запросе к внешнему серверу:', err); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'External server error', message: String(err) })); }); return; } // Обработка /api/files - эндпоинт для локальных файлов задач if (req.url.startsWith('/api/files/') && req.method === 'GET') { (async () => { try { const fileName = decodeURIComponent(req.url.substring('/api/files/'.length)); console.log('[API] Запрос файла:', fileName); // Здесь должна быть реальная логика получения файла с внешнего сервера // Пока используем заглушку или локальные файлы const filePath = path.join(__dirname, 'uploads', fileName); // Проверяем существование файла try { await fs.access(filePath); const fileContent = await fs.readFile(filePath); // Определяем MIME тип по расширению const ext = path.extname(fileName).toLowerCase(); const mimeTypes = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.pdf': 'application/pdf', '.doc': 'application/msword', '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }; const contentType = mimeTypes[ext] || 'application/octet-stream'; res.writeHead(200, { 'Content-Type': contentType, 'Content-Disposition': `inline; filename="${fileName}"` }); res.end(fileContent); console.log('[API] ✅ Файл отправлен:', fileName); } catch (fileError) { // Если файл не найден локально, пробуем запросить с внешнего сервера console.log('[API] Файл не найден локально, проксируем запрос к внешнему серверу...'); // Проксирование запроса к внешнему серверу const https = require('https'); const externalUrl = `https://ws.tirpriz.ru/api/files/${fileName}`; https.get(externalUrl, (externalRes) => { if (externalRes.statusCode === 200) { // Передаем заголовки от внешнего сервера res.writeHead(200, { 'Content-Type': externalRes.headers['content-type'] || 'application/octet-stream', 'Content-Length': externalRes.headers['content-length'], 'Cache-Control': 'public, max-age=3600' }); // Проксируем данные externalRes.pipe(res); console.log('[API] ✅ Файл получен с внешнего сервера:', fileName); } else { // Файл не найден и на внешнем сервере res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'File not found', fileName: fileName, message: 'Файл не найден ни локально, ни на внешнем сервере' })); } }).on('error', (err) => { console.error('[API] Ошибка при запросе к внешнему серверу:', err); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'External server error', message: String(err) })); }); } } catch (error) { console.error('[API] ❌ Ошибка при получении файла:', error); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Failed to load file', message: String(error) })); } })(); return; } // Обработка /api/game-info if (req.url === '/api/game-info' && req.method === 'GET') { try { const gameInfo = game.getInfo(); console.log('[API] Запрос информации об играх'); console.log('[API] Количество игр:', Object.keys(gameInfo.games || {}).length); console.log('[API] Количество групп:', Object.keys(gameInfo.groups || {}).length); if (!gameInfo.games || Object.keys(gameInfo.games).length === 0) { console.warn('[API] ⚠️ Нет доступных игр в конфигурации!'); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(gameInfo)); console.log('[API] ✅ Отправлена информация об играх'); } catch (error) { console.error('[API] ❌ Ошибка получения информации об играх:', error); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Failed to load game info', message: String(error), stack: error.stack })); } return; } // Если это API запрос или запрос к старому серверу if (req.url.startsWith('/api') || req.url.startsWith('/www')) { api.go(req, res, jsonString); } else if (req.url === '/test.html') { // Отдаем тестовую страницу serveTestPage(req, res); } else { // Для всех остальных запросов пытаемся раздать React билд serveReactApp(req, res); } }); req.on('error', (err) => console.log("ERROR HTTP ",err) ); }); // Обработка ошибок HTTP сервера httpServer.on('error', (error) => { console.error(`[ERROR] HTTP сервер на порту ${HTTP_PORT || 'неопределен'}:`, error); if (error.code === 'EADDRINUSE') { console.error('[ERROR] Порт 80 уже занят! Возможно, другой веб-сервер запущен.'); } else if (error.code === 'EACCES') { console.error('[ERROR] Нет прав для прослушивания порта 80. Запустите с sudo или используйте другой порт.'); } }); // Используем порт 80 для production, 5000 для разработки const HTTP_PORT = process.env.HTTP_PORT || (process.env.NODE_ENV === 'production' ? 80 : 5000); httpServer.listen(HTTP_PORT, '0.0.0.0', () => { console.log(`[OK] HTTP сервер запущен на порту ${HTTP_PORT}`); console.log(`[INFO] Доступ с других устройств: http://[IP-адрес-ПК]:${HTTP_PORT}`); }); const onConnect= async (wsClient)=> { let verSoft = JSON.parse( await fs.readFile(path.join(__dirname, 'data/verSoft.ini') ) ); const clientIP = wsClient._socket?.remoteAddress || 'неизвестен'; const clientId = Math.random().toString(36).substr(2, 9); wsClient._clientId = clientId; wsClient.cfg={avt:false}; console.log(`[${new Date().toLocaleTimeString()}] [WS CONNECTION] ✅ Подключен клиент:`, { clientId: clientId, clientIP: clientIP, totalClients: wsServer.clients.size, userAgent: wsClient.upgradeReq?.headers['user-agent'] || 'неизвестен' }); wsClient.send(JSON.stringify({do:"link-toserver", link:global.conn_to_server, verSof: verSoft.ver })) wsClient.on('message', function(message) { ws.go(wsClient, message); }) wsClient.on('close', function() { console.log(`[${new Date().toLocaleTimeString()}] [WS CONNECTION] ❌ Отключился клиент:`, { clientId: clientId, clientIP: clientIP, remainingClients: wsServer.clients.size, wasAuthorized: !!wsClient.cfg.avt, userId: wsClient.cfg.avt ? wsClient.cfg.avt._id : null }); }) // Добавляем обработку ошибок клиента wsClient.on('error', function(error) { console.log(`[${new Date().toLocaleTimeString()}] [WS CONNECTION] 💥 Ошибка клиента:`, { clientId: clientId, clientIP: clientIP, error: error.message }); }); } wsServer.on('connection', onConnect); console.log("pult start" ); } // Функция для раздачи тестовой страницы async function serveTestPage(req, res) { try { const testPagePath = path.join(__dirname, 'test.html'); const content = await fs.readFile(testPagePath, 'utf8'); res.setHeader('Content-Type', 'text/html'); res.writeHead(200); res.end(content); } catch (error) { console.error('Ошибка при раздаче тестовой страницы:', error); res.writeHead(500); res.end('Internal Server Error'); } } // Функция для раздачи React приложения async function serveReactApp(req, res) { try { let filePath = req.url; // Если запрос к корню, отдаем index.html if (filePath === '/' || filePath === '/index.html') { filePath = '/index.html'; } // 🍊 ИСПОЛЬЗУЕМ ДИНАМИЧЕСКИЙ ПУТЬ В ЗАВИСИМОСТИ ОТ ТИПА УСТРОЙСТВА let reactBuildPath; // Проверяем несколько возможных путей // ВАЖНО: сначала проверяем путь из global.staticDirectory const possiblePaths = [ path.join(__dirname, global.staticDirectory || '../dist/client', filePath), path.join(__dirname, '../dist/client-orange-pi', filePath), path.join(__dirname, '../dist/client', filePath) ]; // Ищем первый существующий файл for (const testPath of possiblePaths) { try { await fs.access(testPath); reactBuildPath = testPath; break; } catch { continue; } } // Если ни один путь не найден, используем fallback if (!reactBuildPath) { reactBuildPath = path.join(__dirname, '../dist/client', filePath); } // Проверяем существование файла try { await fs.access(reactBuildPath); } catch { // Если файл не найден, отдаем index.html (для SPA роутинга) const indexPath = path.join(__dirname, global.staticDirectory, '/index.html'); try { await fs.access(indexPath); const indexContent = await fs.readFile(indexPath); res.setHeader('Content-Type', 'text/html'); res.writeHead(200); res.end(indexContent); return; } catch { // Fallback: пробуем альтернативную директорию const fallbackDir = global.staticDirectory.includes('orange-pi') ? '../dist/client' : '../dist/client-orange-pi'; const fallbackIndexPath = path.join(__dirname, fallbackDir, '/index.html'); try { await fs.access(fallbackIndexPath); const fallbackContent = await fs.readFile(fallbackIndexPath); res.setHeader('Content-Type', 'text/html'); res.writeHead(200); res.end(fallbackContent); console.log(`⚠️ [FALLBACK] Использую резервную директорию: ${fallbackDir}`); return; } catch { // Fallback к старому серверу если React билд недоступен return api.go(req, res, ''); } } } // Определяем Content-Type const ext = path.extname(filePath).toLowerCase(); const contentTypes = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon' }; const contentType = contentTypes[ext] || 'application/octet-stream'; res.setHeader('Content-Type', contentType); // Читаем и отдаем файл const fileContent = await fs.readFile(reactBuildPath); res.writeHead(200); res.end(fileContent); } catch (error) { console.error('Ошибка при раздаче React файлов:', error); res.writeHead(500); res.end('Internal Server Error'); } } main(); process.on("SIGINT", () => { // прослушиваем прерывание работы программы (ctrl-c) process.exit(); });