const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const compression = require('compression'); const path = require('path'); const app = express(); const PORT = process.env.PORT || 7860; // Detect Hugging Face environment and configure accordingly const isHuggingFace = process.env.SPACE_ID || process.env.SPACE_AUTHOR_NAME || process.env.HF_SPACE || process.env.GRADIO_SERVER_NAME || (process.env.HOST && process.env.HOST.includes('hf.space')); console.log(`🌍 Environment: ${isHuggingFace ? 'Hugging Face' : 'Local/Other'}`); // Secure CSP configuration const cspDirectives = { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"], scriptSrc: ["'self'", "'unsafe-inline'"], // unsafe-inline needed for inline event handlers imgSrc: ["'self'", "https:", "data:", "blob:"], frameSrc: ["'self'", "https://www.youtube-nocookie.com", "https://www.youtube.com"], connectSrc: ["'self'"], mediaSrc: ["'self'", "https:", "data:"], objectSrc: ["'none'"], childSrc: ["'self'", "https://www.youtube-nocookie.com"], workerSrc: ["'self'", "blob:"], manifestSrc: ["'self'"], baseUri: ["'self'"], formAction: ["'self'"], upgradeInsecureRequests: isHuggingFace ? [] : null, }; if (isHuggingFace) { console.log('🔒 Hugging Face detected - Using balanced CSP for compatibility and security'); // Add Hugging Face specific domains cspDirectives.frameSrc.push("https://*.hf.space", "https://*.huggingface.co"); cspDirectives.connectSrc.push("https://*.hf.space", "https://*.huggingface.co"); } else { console.log('🔒 Local environment - Using strict CSP'); } app.use(helmet({ contentSecurityPolicy: { directives: cspDirectives, reportOnly: false // Set to true for testing, false for enforcement }, crossOriginEmbedderPolicy: false, // Disabled for YouTube embeds crossOriginResourcePolicy: { policy: "cross-origin" }, referrerPolicy: { policy: "strict-origin-when-cross-origin" }, strictTransportSecurity: { maxAge: 31536000, includeSubDomains: true, preload: true }, xContentTypeOptions: true, xDnsPrefetchControl: { allow: false }, xDownloadOptions: true, xFrameOptions: { action: isHuggingFace ? 'sameorigin' : 'deny' }, xPoweredBy: false, xXssProtection: true })); // Configure CORS based on environment // Whitelist of allowed origins const allowedOrigins = [ 'http://localhost:7860', 'http://localhost:3000', 'https://*.hf.space', 'https://*.huggingface.co' ]; const corsOptions = { origin: function (origin, callback) { // Allow requests with no origin (mobile apps, curl, etc.) if (!origin) return callback(null, true); if (isHuggingFace) { // For Hugging Face, allow all hf.space and huggingface.co origins if (origin.includes('hf.space') || origin.includes('huggingface.co')) { return callback(null, true); } } // Allow local network IPs (192.168.x.x, 10.x.x.x, etc.) if (origin.match(/^http:\/\/(localhost|127\.0\.0\.1|192\.168\.\d+\.\d+|10\.\d+\.\d+\.\d+)(:\d+)?$/)) { return callback(null, true); } // Check if origin is in whitelist const isAllowed = allowedOrigins.some(allowed => { if (allowed.includes('*')) { const pattern = allowed.replace(/\*/g, '.*'); return new RegExp(pattern).test(origin); } return allowed === origin; }); if (isAllowed) { callback(null, true); } else { console.log(`Blocked origin: ${origin}`); callback(new Error('Not allowed by CORS')); } }, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], exposedHeaders: ['Content-Range', 'X-Content-Range'], maxAge: 600 // Cache preflight requests for 10 minutes }; app.use(cors(corsOptions)); // Additional security headers app.use((req, res, next) => { res.header('X-Content-Type-Options', 'nosniff'); res.header('X-Frame-Options', isHuggingFace ? 'SAMEORIGIN' : 'DENY'); res.header('Referrer-Policy', 'strict-origin-when-cross-origin'); next(); }); // ⚡ Compressão otimizada com Gzip/Brotli app.use(compression({ // Comprimir todos os responses acima de 1KB threshold: 1024, // Nível de compressão (0-9, 6 é o padrão balanceado) level: 6, // Filtrar tipos de conteúdo que devem ser comprimidos filter: (req, res) => { if (req.headers['x-no-compression']) { return false; } return compression.filter(req, res); } })); app.use(express.json()); // 📦 Servir arquivos estáticos com cache agressivo app.use(express.static(path.join(__dirname, 'public'), { // Cache por 1 ano para assets com versão maxAge: '1y', // Habilitar ETags etag: true, // Habilitar Last-Modified lastModified: true, // Configurar cache específico por tipo de arquivo setHeaders: (res, path, stat) => { // Cache agressivo para assets versionados (JS, CSS com hash) if (path.match(/\.(js|css)$/)) { res.set('Cache-Control', 'public, max-age=31536000, immutable'); } // Cache moderado para HTML (1 hora) else if (path.endsWith('.html')) { res.set('Cache-Control', 'public, max-age=3600, must-revalidate'); } // Cache longo para imagens else if (path.match(/\.(jpg|jpeg|png|gif|svg|webp|ico)$/)) { res.set('Cache-Control', 'public, max-age=604800'); // 1 semana } // Cache para fontes else if (path.match(/\.(woff|woff2|ttf|eot)$/)) { res.set('Cache-Control', 'public, max-age=31536000'); // 1 ano } // Cache para manifest e service worker (1 dia) else if (path.match(/\.(json|webmanifest)$/) || path.endsWith('sw.js')) { res.set('Cache-Control', 'public, max-age=86400'); // 1 dia } } })); // Routes app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); app.get('/health', (req, res) => { res.json({ status: 'OK', timestamp: new Date().toISOString() }); }); // API endpoints for progress tracking app.post('/api/progress', (req, res) => { // In a real app, this would save to a database res.json({ success: true, message: 'Progress saved' }); }); app.get('/api/progress/:day', (req, res) => { // In a real app, this would fetch from a database res.json({ day: req.params.day, completed: [] }); }); const server = app.listen(PORT, '0.0.0.0', () => { console.log(`🚀 30-Day Keto Planner running on port ${PORT}`); console.log(`📱 Access at: http://localhost:${PORT}`); console.log(`✅ Server status: RUNNING`); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('📴 SIGTERM received, shutting down gracefully'); server.close(() => { console.log('💤 Process terminated'); process.exit(0); }); }); process.on('SIGINT', () => { console.log('📴 SIGINT received, shutting down gracefully'); server.close(() => { console.log('💤 Process terminated'); process.exit(0); }); }); // Handle server errors server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.error(`❌ Port ${PORT} is already in use`); console.log('💡 For local development, try a different port or stop other services'); console.log('🚀 For Hugging Face deployment, port 7860 will work correctly'); } else { console.error('❌ Server error:', err); } process.exit(1); });