Spaces:
Sleeping
Sleeping
| 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')}`); | |
| }); |