// ===== Configuration ===== var config = { port: 8080, }; // // ========================= let rootDir = __dirname; if (!rootDir.endsWith('/')) rootDir += '/'; const binDir = rootDir + `build`; const watchPaths = [rootDir, binDir]; const http = require('http'); const fs = require('fs'); const path = require('path'); const { exec } = require('child_process'); const WebSocket = require('ws'); const mimeTypes = { '.html': 'text/html', '.js': 'text/javascript', '.wasm': 'application/wasm', '.css': 'text/css', '.json': 'application/json' }; const server = http.createServer((req, res) => { let norm = path.normalize(req.url); console.log(`${req.method} ${norm}`); // Serve config endpoint if (norm === '/config') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(config)); return; } // Serve files let filePath; if (norm.startsWith('/bin/')) { filePath = path.join(binDir, norm.slice(5)); } else if (norm == '/') { filePath = rootDir + 'index.html'; } else { filePath = rootDir + norm; } const extname = String(path.extname(filePath)).toLowerCase(); const contentType = mimeTypes[extname] || 'application/octet-stream'; fs.readFile(filePath, (error, content) => { if (error) { if (error.code === 'ENOENT') { res.writeHead(404, { 'Content-Type': 'text/html' }); res.end('

404 - File Not Found

', 'utf-8'); } else { res.writeHead(500); res.end(`Server Error: ${error.code}`, 'utf-8'); } } else { res.writeHead(200, { 'Content-Type': contentType, 'Content-Length': content.byteLength, 'Access-Control-Expose-Headers': 'Content-Length', 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp', }); res.end(content, 'utf-8'); } }); }); const wss = new WebSocket.Server({ noServer: true }); wss.on('connection', () => { }); server.on('upgrade', (request, socket, head) => { if (request.url === '/control') { wss.handleUpgrade(request, socket, head, (ws) => { wss.emit('connection', ws, request); }); } else { socket.destroy(); } }); function broadcastRefresh() { const message = JSON.stringify({ type: 'refresh' }); let clientCount = 0; wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(message); clientCount++; } }); if (clientCount > 0) { console.log(`Sent refresh to ${clientCount} client(s)`); } } // Watch files for changes const watchers = []; watchPaths.forEach((watchPath) => { try { const watcher = fs.watch(watchPath, { recursive: true }, (eventType, filename) => { console.log(`File changed: ${filename} (${eventType})`); if (filename) { console.log(`File changed: ${filename} (${eventType})`); broadcastRefresh(); } }); watchers.push(watcher); console.log(`Watching: ${path.resolve(watchPath)}`); } catch (error) { console.error(`Failed to watch ${watchPath}:`, error.message); } }); server.listen(config.port, () => { console.log(`Server running at http://localhost:${config.port}/`); console.log(`WebSocket listening on ws://localhost:${config.port}/control`); console.log(`WASM binary path configured as: ${binDir}`); const shouldLaunchBrowser = process.argv.includes('--launch'); if (shouldLaunchBrowser) { exec(`librewolf --new-window http://localhost:${config.port}/`, (error) => { if (error) { console.error(`Failed to launch LibreWolf: ${error.message}`); } else { console.log('LibreWolf launched'); } }); } });