Spaces:
Sleeping
Sleeping
| const express = require('express') | |
| const http = require('http') | |
| const axios = require('axios') | |
| const WebSocket = require('ws') | |
| const app = express() | |
| const METERED_API_KEY = process.env.METERED_API_KEY | |
| const METERED_DOMAIN = process.env.METERED_DOMAIN | |
| let actualToken = [] | |
| // 🚀 GET para que Hugging Face detecte el server | |
| app.get('/', (req, res) => { | |
| console.log('GET a / recibido') | |
| res.status(200).json({ | |
| status: 'ok', | |
| message: '🚀 Project Together Server activo ✅', | |
| timestamp: new Date().toISOString(), | |
| }) | |
| }) | |
| // Endpoint de health check para Hugging Face | |
| app.get('/health', (req, res) => { | |
| console.log('Health check recibido') | |
| res.status(200).send('OK') | |
| }) | |
| // Endpoint específico para Hugging Face | |
| app.get('/healthz', (req, res) => { | |
| console.log('Healthz check recibido') | |
| res.status(200).send('OK') | |
| }) | |
| // 🔒 Bloque de CORS solo para peticiones POST | |
| app.use(function (req, res, next) { | |
| const allowedOrigins = [ | |
| 'https://unboring.net', | |
| 'https://0.0.0.0:8080', | |
| 'https://localhost:8080', | |
| 'https://192.168.0.42:8080', | |
| 'https://192.168.0.10:8080', | |
| 'https://arturitu-mixing-realities.hf.space', | |
| 'https://*.hf.space', | |
| ] | |
| const origin = req.headers.origin | |
| if ( | |
| origin && | |
| allowedOrigins.some( | |
| (o) => | |
| origin === o || (o.includes('*') && origin.endsWith(o.replace('*', ''))) | |
| ) | |
| ) { | |
| res.setHeader('Access-Control-Allow-Origin', origin) | |
| } | |
| res.header( | |
| 'Access-Control-Allow-Headers', | |
| 'Origin, X-Requested-With, Content-Type, Accept' | |
| ) | |
| next() | |
| }) | |
| // POST para devolver ICE servers | |
| app.post('/', function (req, res) { | |
| res.json(actualToken) | |
| }) | |
| // Crear servidor HTTP | |
| const server = http.createServer(app) | |
| // Crear WebSocket sobre el mismo server | |
| const wss = new WebSocket.Server({ server }) | |
| const maxRoomUsers = 2 | |
| function callback(ws, message) { | |
| ws.send(message) | |
| } | |
| function broadcastToRoommates(ws, message, room) { | |
| wss.clients.forEach((client) => { | |
| if (client.room && client.room.includes(room)) { | |
| if (ws !== client && client.readyState === WebSocket.OPEN) { | |
| client.send(message) | |
| } | |
| } | |
| }) | |
| } | |
| function broadcastToAllRoomMembers(ws, message, room) { | |
| wss.clients.forEach((client) => { | |
| if (client.room && client.room.includes(room)) { | |
| if (client.readyState === WebSocket.OPEN) { | |
| client.send(message) | |
| } | |
| } | |
| }) | |
| } | |
| function getRoomUsers(room) { | |
| let totalRoomUsers = 0 | |
| wss.clients.forEach((client) => { | |
| if (client.room && client.room.includes(room)) { | |
| totalRoomUsers += 1 | |
| } | |
| }) | |
| return totalRoomUsers | |
| } | |
| function setRole(ws, room, actualRoomUsers) { | |
| let setAsHost = true | |
| if (actualRoomUsers > 0) { | |
| setAsHost = false | |
| } | |
| wss.clients.forEach((client) => { | |
| if (client.room && client.room.includes(room) && ws !== client) { | |
| if (client.host) { | |
| setAsHost = false | |
| } else { | |
| setAsHost = true | |
| } | |
| } | |
| }) | |
| return setAsHost | |
| } | |
| function getIsValidRoom(room) { | |
| var roomNumber = parseInt(room, 10) | |
| var regex = /[0-9]/ | |
| var isValid = false | |
| if (regex.test(room) && roomNumber >= 100000000 && roomNumber <= 999999999) { | |
| isValid = true | |
| } | |
| return isValid | |
| } | |
| // Lógica signaling | |
| wss.on('connection', (ws) => { | |
| ws.room = [] | |
| ws.host = false | |
| ws.on('message', (message) => { | |
| let msgObj | |
| try { | |
| msgObj = JSON.parse(message) | |
| } catch (e) { | |
| console.error('Mensaje inválido:', e) | |
| return | |
| } | |
| if (msgObj.join) { | |
| if (!getIsValidRoom(msgObj.join)) { | |
| let fatalErrorMsg = { type: 'fatalError' } | |
| callback(ws, JSON.stringify(fatalErrorMsg)) | |
| return | |
| } | |
| let actualRoomUsers = getRoomUsers(msgObj.join) | |
| if (actualRoomUsers < maxRoomUsers) { | |
| ws.room.push(msgObj.join) | |
| ws.host = setRole(ws, msgObj.join, actualRoomUsers) | |
| let joinedMsg = { | |
| type: 'joined', | |
| host: ws.host, | |
| } | |
| callback(ws, JSON.stringify(joinedMsg)) | |
| } else { | |
| let fullMsg = { type: 'full' } | |
| callback(ws, JSON.stringify(fullMsg)) | |
| } | |
| } else if (msgObj.room) { | |
| broadcastToRoommates(ws, message, msgObj.room) | |
| } else if (msgObj.ping) { | |
| broadcastToRoommates(ws, message, msgObj.room) | |
| } else if (msgObj.start) { | |
| let startedMsg = { | |
| type: 'started', | |
| room: msgObj.start, | |
| } | |
| broadcastToAllRoomMembers(ws, JSON.stringify(startedMsg), msgObj.start) | |
| } else if (msgObj.close) { | |
| let closedMsg = { | |
| type: 'closed', | |
| room: msgObj.close, | |
| } | |
| broadcastToRoommates(ws, JSON.stringify(closedMsg), msgObj.close) | |
| } | |
| }) | |
| ws.on('close', () => { | |
| console.log('Cliente desconectado') | |
| }) | |
| }) | |
| // Obtener token ICE inicial y renovar cada hora | |
| async function renewToken() { | |
| try { | |
| if (!METERED_API_KEY || !METERED_DOMAIN) { | |
| console.warn( | |
| 'Missing METERED_API_KEY or METERED_DOMAIN environment variables' | |
| ) | |
| return | |
| } | |
| const response = await axios.get( | |
| `https://${METERED_DOMAIN}.metered.live/api/v1/turn/credentials?apiKey=${METERED_API_KEY}` | |
| ) | |
| actualToken = response.data | |
| console.log('Token ICE actualizado correctamente.') | |
| } catch (err) { | |
| console.error('Error al renovar ICE token:', err.message) | |
| // Continue with empty token if renewal fails | |
| actualToken = [] | |
| } | |
| } | |
| renewToken() | |
| setInterval(renewToken, 60 * 60 * 1000) | |
| // Iniciar server | |
| // En Hugging Face, usa puerto 7860 explícitamente | |
| const PORT = process.env.SPACE_ID ? 7860 : process.env.PORT || 3000 | |
| const HOST = '0.0.0.0' // Escuchar en todas las interfaces de red (importante para Docker) | |
| server.listen(PORT, HOST, () => { | |
| console.log(`Servidor HTTP y WebSocket escuchando en ${HOST}:${PORT}`) | |
| console.log(`Variable de entorno PORT: ${process.env.PORT || 'No definida'}`) | |
| console.log(`En entorno Hugging Face: ${process.env.SPACE_ID ? 'Sí' : 'No'}`) | |
| // Log todas las variables de entorno para diagnóstico | |
| console.log('Variables de entorno disponibles:') | |
| Object.keys(process.env).forEach((key) => { | |
| if ( | |
| !key.includes('KEY') && | |
| !key.includes('SECRET') && | |
| !key.includes('TOKEN') | |
| ) { | |
| console.log(`${key}: ${process.env[key]}`) | |
| } | |
| }) | |
| }) | |