const express = require('express'); const fs = require('fs'); const path = require('path'); const cors = require('cors'); const app = express(); const PORT = process.env.PORT || 7860; // ============================================================ // 1. Middleware // ============================================================ app.use(cors()); app.use(express.json({ limit: '10mb' })); // Force correct MIME type for MediaPipe .task files app.use((req, res, next) => { if (req.url.endsWith('.task')) { res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Disposition', 'attachment; filename="hand_landmarker.task"'); } next(); }); // ============================================================ // 2. Snapshots Directory Setup // ============================================================ const SNAPSHOT_DIR = path.join(__dirname, 'snapshots'); if (!fs.existsSync(SNAPSHOT_DIR)) { fs.mkdirSync(SNAPSHOT_DIR, { recursive: true }); console.log(`[SERVER] Created snapshots directory at ${SNAPSHOT_DIR}`); } // ============================================================ // 3. Serve Static Frontend Files // ============================================================ app.use(express.static(path.join(__dirname, 'public'))); // ============================================================ // 4. Explicit route for admin.html (case-insensitive fallback) // ============================================================ app.get('/admin.html', (req, res) => { const adminPath = path.join(__dirname, 'public', 'admin.html'); const altPath = path.join(__dirname, 'public', 'Admin.html'); if (fs.existsSync(adminPath)) { res.sendFile(adminPath); } else if (fs.existsSync(altPath)) { console.log('[SERVER] Serving Admin.html (uppercase) as admin.html'); res.sendFile(altPath); } else { res.status(404).send('admin.html not found. Please create the file in the public folder.'); } }); // ============================================================ // 5. Snapshot Binary Endpoint // ============================================================ app.get('/snapshots/:filename', (req, res) => { const filename = req.params.filename; if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { console.warn(`[SERVER] Blocked malicious filename: ${filename}`); return res.status(400).send('Invalid filename'); } const filePath = path.join(SNAPSHOT_DIR, filename); if (fs.existsSync(filePath)) { try { const data = fs.readFileSync(filePath); res.setHeader('Content-Type', 'image/png'); res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); res.send(data); console.log(`[SERVER] Served snapshot: ${filename} (${data.length} bytes)`); } catch (err) { console.error(`[SERVER] Error reading snapshot ${filename}:`, err); res.status(500).send('Error reading file'); } } else { console.log(`[SERVER] Snapshot not found: ${filename}`); res.status(404).send('Snapshot not found'); } }); // ============================================================ // 6. POST /api/log – Receive frontend logs // ============================================================ app.post('/api/log', (req, res) => { try { const { message, level = 'info', timestamp } = req.body; const logMsg = `[Frontend ${level}] ${timestamp || new Date().toISOString()} ${message || ''}`; console.log(logMsg); res.status(200).json({ success: true }); } catch (err) { console.error('[SERVER] Log error:', err); res.status(500).json({ success: false, error: err.message }); } }); // ============================================================ // 7. POST /api/snapshot – Save New Snapshot // ============================================================ app.post('/api/snapshot', (req, res) => { try { const { image } = req.body; if (!image) { return res.status(400).json({ success: false, error: 'No image data provided' }); } if (typeof image !== 'string' || image.length < 100) { return res.status(400).json({ success: false, error: 'Invalid image data format' }); } const buffer = Buffer.from(image, 'base64'); const now = new Date(); const dateStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + '_' + String(now.getHours()).padStart(2, '0') + '-' + String(now.getMinutes()).padStart(2, '0') + '-' + String(now.getSeconds()).padStart(2, '0') + '_' + String(now.getMilliseconds()).padStart(3, '0'); const filename = `snapshot_${dateStr}.png`; const filePath = path.join(SNAPSHOT_DIR, filename); fs.writeFileSync(filePath, buffer); console.log(`[SERVER] Snapshot saved: ${filename} (${buffer.length} bytes) at ${filePath}`); if (fs.existsSync(filePath)) { const stats = fs.statSync(filePath); console.log(`[SERVER] Verification: file exists, size ${stats.size} bytes`); } else { console.error(`[SERVER] Verification FAILED: file not found after write`); return res.status(500).json({ success: false, error: 'Write verification failed' }); } res.json({ success: true, path: `/snapshots/${filename}`, filename: filename }); } catch (err) { console.error('[SERVER] Save error:', err); res.status(500).json({ success: false, error: 'Internal server error: ' + err.message }); } }); // ============================================================ // 8. GET /api/snapshots/list – List All Snapshots // ============================================================ app.get('/api/snapshots/list', (req, res) => { try { const files = fs.readdirSync(SNAPSHOT_DIR) .filter(f => f.endsWith('.png')) .sort(); console.log(`[SERVER] Listed ${files.length} snapshots`); res.json({ success: true, files }); } catch (err) { console.error('[SERVER] List error:', err); res.status(500).json({ success: false, error: err.message }); } }); // ============================================================ // 9. DELETE /api/snapshots/:filename – Delete a Snapshot // ============================================================ app.delete('/api/snapshots/:filename', (req, res) => { const filename = req.params.filename; if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { return res.status(400).json({ success: false, error: 'Invalid filename' }); } const filePath = path.join(SNAPSHOT_DIR, filename); if (fs.existsSync(filePath)) { try { fs.unlinkSync(filePath); console.log(`[SERVER] Deleted snapshot: ${filename}`); res.json({ success: true, message: 'Deleted' }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } } else { res.status(404).json({ success: false, error: 'File not found' }); } }); // ============================================================ // 10. Health Check // ============================================================ app.get('/health', (req, res) => { res.status(200).send('OK'); }); // ============================================================ // 11. Start Server // ============================================================ app.listen(PORT, () => { console.log(`[SERVER] Running on port ${PORT}`); console.log(`[SERVER] Snapshots directory: ${SNAPSHOT_DIR}`); console.log(`[SERVER] Serving static files from: ${path.join(__dirname, 'public')}`); });