activity-simulator / src /cloud-server.js
abedelbahnasy55's picture
fix: timeouts, error handling, duplicate title fix
ca1b6c1
import http from 'http';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import HumanBehaviorEngine from './index.js';
import logger from './utils/logger.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PORT = parseInt(process.env.PORT) || 7860;
const publicDir = path.join(__dirname, '..', 'public');
const engine = new HumanBehaviorEngine();
let isRunning = false;
const mimeTypes = {
'.html': 'text/html',
'.js': 'application/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.svg': 'image/svg+xml',
};
function serveStatic(res, filePath) {
const ext = path.extname(filePath);
const contentType = mimeTypes[ext] || 'application/octet-stream';
fs.readFile(filePath, (err, content) => {
if (err) {
res.writeHead(404);
res.end(JSON.stringify({ error: 'not_found' }));
return;
}
res.writeHead(200, { 'Content-Type': contentType });
res.end(content);
});
}
async function startEngine() {
if (isRunning) return { status: 'already_running' };
isRunning = true;
await engine.start();
return { status: 'started' };
}
async function stopEngine() {
isRunning = false;
await engine.stop();
return { status: 'stopped' };
}
async function getStatus() {
return await engine.getStatus();
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
res.setHeader('Access-Control-Allow-Origin', '*');
if (req.method === 'GET' && url.pathname === '/health') {
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify({ status: 'ok', uptime: process.uptime() }));
return;
}
if (req.method === 'GET' && url.pathname === '/status') {
res.setHeader('Content-Type', 'application/json');
try {
const status = await getStatus();
res.writeHead(200);
res.end(JSON.stringify(status));
} catch (e) {
res.writeHead(500);
res.end(JSON.stringify({ error: e.message }));
}
return;
}
if (req.method === 'POST' && url.pathname === '/start') {
res.setHeader('Content-Type', 'application/json');
try {
const result = await startEngine();
res.writeHead(200);
res.end(JSON.stringify(result));
} catch (e) {
res.writeHead(500);
res.end(JSON.stringify({ error: e.message }));
}
return;
}
if (req.method === 'POST' && url.pathname === '/stop') {
res.setHeader('Content-Type', 'application/json');
try {
const result = await stopEngine();
res.writeHead(200);
res.end(JSON.stringify(result));
} catch (e) {
res.writeHead(500);
res.end(JSON.stringify({ error: e.message }));
}
return;
}
if (req.method === 'POST' && url.pathname === '/cycle') {
res.setHeader('Content-Type', 'application/json');
try {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Cycle timed out after 60s')), 60000)
);
await Promise.race([engine.runOnce(), timeout]);
res.writeHead(200);
res.end(JSON.stringify({ status: 'cycle_complete' }));
} catch (e) {
res.writeHead(500);
res.end(JSON.stringify({ error: e.message }));
}
return;
}
if (req.method === 'GET') {
if (url.pathname === '/' || url.pathname === '/index.html') {
serveStatic(res, path.join(publicDir, 'index.html'));
return;
}
const filePath = path.join(publicDir, url.pathname);
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
serveStatic(res, filePath);
return;
}
}
res.setHeader('Content-Type', 'application/json');
res.writeHead(404);
res.end(JSON.stringify({ error: 'not_found' }));
});
server.listen(PORT, '0.0.0.0', () => {
logger.info(`Cloud server listening on port ${PORT}`);
logger.info(`Dashboard: http://localhost:${PORT}`);
logger.info('Auto-starting simulator...');
startEngine().catch(e => logger.error(`Auto-start failed: ${e.message}`));
});
process.on('SIGTERM', async () => {
logger.info('SIGTERM received, stopping engine...');
await stopEngine();
process.exit(0);
});
process.on('SIGINT', async () => {
logger.info('SIGINT received, stopping engine...');
await stopEngine();
process.exit(0);
});