|
|
const express = require('express'); |
|
|
const cors = require('cors'); |
|
|
const SimpleSandboxManager = require('./simple-sandbox-manager'); |
|
|
|
|
|
const app = express(); |
|
|
|
|
|
app.use(cors({ origin: '*' })); |
|
|
app.use(express.json({ limit: '50mb' })); |
|
|
|
|
|
const sandboxManager = new SimpleSandboxManager(); |
|
|
|
|
|
(async () => { |
|
|
await sandboxManager.initialize(); |
|
|
console.log('π Custom Sandbox Manager is ready.'); |
|
|
})(); |
|
|
|
|
|
|
|
|
app.get('/health', (req, res) => { |
|
|
res.status(200).json({ |
|
|
status: 'healthy', |
|
|
sandboxes: sandboxManager.sandboxes.size |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.post('/api/sandboxes', async (req, res) => { |
|
|
try { |
|
|
const { timeout = 600000, template = 'next-js' } = req.body; |
|
|
const sandboxId = `sbx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; |
|
|
|
|
|
const sandbox = await sandboxManager.createSandbox(sandboxId, { timeout, template }); |
|
|
|
|
|
|
|
|
res.json({ id: sandboxId, sandboxId: sandboxId }); |
|
|
} catch (error) { |
|
|
console.error('β Create error:', error); |
|
|
res.status(500).json({ error: error.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
app.get('/api/sandboxes/:id', async (req, res) => { |
|
|
try { |
|
|
const { id } = req.params; |
|
|
const sandbox = sandboxManager.getSandbox(id); |
|
|
|
|
|
if (!sandbox || sandboxManager.isTimedOut(id)) { |
|
|
if (sandbox) await sandboxManager.destroySandbox(id); |
|
|
return res.status(404).json({ error: 'Sandbox not found or has timed out' }); |
|
|
} |
|
|
|
|
|
|
|
|
res.json({ status: 'running' }); |
|
|
} catch (error) { |
|
|
console.error('β Status error:', error); |
|
|
res.status(500).json({ error: error.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
app.post('/api/sandboxes/:id/cmd', async (req, res) => { |
|
|
try { |
|
|
const { id } = req.params; |
|
|
const { command } = req.body; |
|
|
|
|
|
const result = await sandboxManager.executeCommand(id, command); |
|
|
res.json({ commandId: result.commandId, id: result.commandId }); |
|
|
} catch (error) { |
|
|
console.error('β Command error:', error); |
|
|
res.status(500).json({ error: error.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
app.get('/api/sandboxes/:id/cmds/:cmdId', async (req, res) => { |
|
|
try { |
|
|
const { id, cmdId } = req.params; |
|
|
const cmdData = sandboxManager.getCommandData(id, cmdId); |
|
|
|
|
|
if (!cmdData || cmdData.exitCode === null) { |
|
|
|
|
|
return res.json({ exitCode: null, stdout: cmdData?.stdout || '', stderr: cmdData?.stderr || '' }); |
|
|
} |
|
|
|
|
|
res.json({ |
|
|
exitCode: cmdData.exitCode, |
|
|
stdout: cmdData.stdout || '', |
|
|
stderr: cmdData.stderr || '' |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('β Get command error:', error); |
|
|
res.status(404).json({ error: 'Command not found' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
app.get('/api/sandboxes/:id/cmds/:cmdId/logs', (req, res) => { |
|
|
const { id, cmdId } = req.params; |
|
|
console.log(`π‘ SSE stream requested for ${id}/${cmdId}`); |
|
|
|
|
|
res.setHeader('Content-Type', 'text/event-stream'); |
|
|
res.setHeader('Cache-Control', 'no-cache'); |
|
|
res.setHeader('Connection', 'keep-alive'); |
|
|
res.setHeader('X-Accel-Buffering', 'no'); |
|
|
res.setHeader('Access-Control-Allow-Origin', '*'); |
|
|
res.flushHeaders(); |
|
|
|
|
|
sandboxManager.addStreamClient(id, cmdId, res); |
|
|
|
|
|
req.on('close', () => { |
|
|
sandboxManager.removeStreamClient(id, cmdId, res); |
|
|
console.log(`π‘ SSE stream closed for ${id}/${cmdId}`); |
|
|
res.end(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
app.post('/api/sandboxes/:id/fs/write', async (req, res) => { |
|
|
try { |
|
|
const { id } = req.params; |
|
|
const { path: filePath, content } = req.body; |
|
|
await sandboxManager.writeFile(id, filePath, content); |
|
|
res.status(204).send(); |
|
|
} catch (error) { |
|
|
console.error('β File write error:', error); |
|
|
res.status(500).json({ error: error.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
app.get('/api/sandboxes/:id/url', async (req, res) => { |
|
|
try { |
|
|
const { id } = req.params; |
|
|
const url = await sandboxManager.getURL(id); |
|
|
res.json({ url }); |
|
|
} catch (error) { |
|
|
console.error('β Get URL error:', error); |
|
|
res.status(404).json({ error: 'Sandbox or URL not found' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
setInterval(() => { |
|
|
sandboxManager.cleanupInactive(); |
|
|
}, 5 * 60 * 1000); |
|
|
|
|
|
const PORT = 3001; |
|
|
app.listen(PORT, '0.0.0.0', () => { |
|
|
console.log(`π Custom Sandbox API running on http://localhost:${PORT}`); |
|
|
}); |
|
|
|
|
|
process.on('SIGTERM', async () => { |
|
|
console.log('β οΈ Shutting down... cleaning up all sandboxes.'); |
|
|
await sandboxManager.destroyAllSandboxes(); |
|
|
process.exit(0); |
|
|
}); |
|
|
|