Sendbox / simple-sandbox-manager.js
RaBU1234's picture
Update simple-sandbox-manager.js
88510eb verified
const { spawn } = require('child_process');
const fs = require('fs').promises;
const path = require('path');
class SimpleSandboxManager {
constructor() {
this.sandboxes = new Map();
this.basePath = path.join('/tmp', 'sandboxes');
this.portCounter = 9000;
this.streamClients = new Map();
this.commandData = new Map();
}
async initialize() {
await fs.mkdir(this.basePath, { recursive: true });
console.log('✅ Sandbox Manager initialized at:', this.basePath);
}
async createSandbox(sandboxId, options = {}) {
const { timeout = 600000 } = options;
const sandboxPath = path.join(this.basePath, sandboxId);
await fs.mkdir(sandboxPath, { recursive: true });
const port = this.portCounter++;
const sandbox = {
sandboxId,
path: sandboxPath,
port,
timeout,
createdAt: Date.now(),
lastActivity: Date.now(),
url: `http://localhost:${port}`,
};
this.sandboxes.set(sandboxId, sandbox);
console.log(`✅ Sandbox created: ${sandboxId} | URL: ${sandbox.url}`);
return sandbox;
}
isTimedOut(sandboxId) {
const sandbox = this.sandboxes.get(sandboxId);
if (!sandbox) return true;
return (Date.now() - sandbox.createdAt) > sandbox.timeout;
}
addStreamClient(id, cmdId, res) {
const key = `${id}:${cmdId}`;
if (!this.streamClients.has(key)) this.streamClients.set(key, []);
this.streamClients.get(key).push(res);
}
removeStreamClient(id, cmdId, res) {
const key = `${id}:${cmdId}`;
const clients = this.streamClients.get(key);
if (clients) {
const index = clients.indexOf(res);
if (index > -1) clients.splice(index, 1);
if (clients.length === 0) this.streamClients.delete(key);
}
}
sendToStreamClients(id, cmdId, type, data) {
const key = `${id}:${cmdId}`;
const clients = this.streamClients.get(key);
if (!clients || clients.length === 0) return;
const message = `data: ${JSON.stringify({ type, data, timestamp: Date.now() })}\n\n`;
clients.forEach(client => {
try {
client.write(message);
} catch (e) {
this.removeStreamClient(id, cmdId, client);
}
});
}
async executeCommand(sandboxId, command) {
const sandbox = this.sandboxes.get(sandboxId);
if (!sandbox) throw new Error('Sandbox not found');
sandbox.lastActivity = Date.now();
const proc = spawn(command, [], {
cwd: sandbox.path,
shell: true,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, PORT: sandbox.port.toString() },
});
const cmdId = `cmd-${proc.pid}`;
const dataKey = `${sandboxId}:${cmdId}`;
this.commandData.set(dataKey, { stdout: '', stderr: '', exitCode: null, startedAt: Date.now() });
console.log(`[${sandboxId}] CMD (${cmdId}): ${command}`);
proc.stdout.on('data', (data) => {
const out = data.toString();
this.commandData.get(dataKey).stdout += out;
this.sendToStreamClients(sandboxId, cmdId, 'stdout', out);
});
proc.stderr.on('data', (data) => {
const err = data.toString();
this.commandData.get(dataKey).stderr += err;
this.sendToStreamClients(sandboxId, cmdId, 'stderr', err);
});
proc.on('exit', (code) => {
console.log(`[${sandboxId}] CMD (${cmdId}) exited with code: ${code}`);
const cmdData = this.commandData.get(dataKey);
if (cmdData) cmdData.exitCode = code;
this.sendToStreamClients(sandboxId, cmdId, 'complete', { exitCode: code });
setTimeout(() => this.commandData.delete(dataKey), 10 * 60 * 1000); // 10 min cleanup
});
proc.on('error', (err) => {
console.error(`[${sandboxId}] CMD (${cmdId}) error:`, err);
this.sendToStreamClients(sandboxId, cmdId, 'error', err.message);
});
return { commandId: cmdId };
}
getCommandData(sandboxId, cmdId) {
return this.commandData.get(`${sandboxId}:${cmdId}`);
}
async writeFile(sandboxId, filePath, content) {
const sandbox = this.sandboxes.get(sandboxId);
if (!sandbox) throw new Error('Sandbox not found');
sandbox.lastActivity = Date.now();
const fullPath = path.join(sandbox.path, filePath);
await fs.mkdir(path.dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, content, 'utf-8');
console.log(`📝 File written to ${sandboxId}: ${filePath}`);
}
async getURL(sandboxId) {
const sandbox = this.sandboxes.get(sandboxId);
if (!sandbox) throw new Error('Sandbox not found');
return sandbox.url;
}
async destroySandbox(sandboxId) {
const sandbox = this.sandboxes.get(sandboxId);
if (!sandbox) return;
console.log(`🧹 Destroying sandbox: ${sandboxId}`);
this.sandboxes.delete(sandboxId);
try {
await fs.rm(sandbox.path, { recursive: true, force: true });
} catch (e) {
if (e.code !== 'ENOENT') console.error(`Cleanup error for ${sandboxId}:`, e.message);
}
}
async destroyAllSandboxes() {
await Promise.all(Array.from(this.sandboxes.keys()).map(id => this.destroySandbox(id)));
}
getSandbox(sandboxId) {
return this.sandboxes.get(sandboxId);
}
async cleanupInactive() {
const now = Date.now();
for (const [id, sandbox] of this.sandboxes.entries()) {
if ((now - sandbox.createdAt) > sandbox.timeout) {
console.log(`⏰ Cleaning up timed-out sandbox: ${id}`);
await this.destroySandbox(id);
}
}
}
}
module.exports = SimpleSandboxManager;