feat: replace node-pty with E2B sandboxes

This commit is contained in:
James Murdza
2024-06-14 12:02:20 -04:00
parent b561f1e962
commit e5b320d1c5
3 changed files with 166 additions and 49 deletions

View File

@ -44,9 +44,8 @@ const io = new Server(httpServer, {
let inactivityTimeout: NodeJS.Timeout | null = null;
let isOwnerConnected = false;
const terminals: {
[id: string]: { terminal: IPty; onData: IDisposable; onExit: IDisposable };
} = {};
const containers: Record<string, Sandbox> = {};
const terminals: Record<string, Terminal> = {};
const dirName = path.join(__dirname, "..");
@ -101,6 +100,8 @@ io.use(async (socket, next) => {
next();
});
const lockManager = new LockManager();
io.on("connection", async (socket) => {
if (inactivityTimeout) clearTimeout(inactivityTimeout);
@ -119,6 +120,17 @@ io.on("connection", async (socket) => {
}
}
await lockManager.acquireLock(data.sandboxId, async () => {
try {
if (!containers[data.sandboxId]) {
containers[data.sandboxId] = await Sandbox.create();
console.log("Created container ", data.sandboxId);
}
} catch (error) {
console.error("Error creating container ", data.sandboxId, error);
}
});
const sandboxFiles = await getSandboxFiles(data.sandboxId);
sandboxFiles.fileData.forEach((file) => {
const filePath = path.join(dirName, file.id);
@ -320,41 +332,33 @@ io.on("connection", async (socket) => {
callback(newFiles.files);
});
socket.on("createTerminal", (id: string, callback) => {
socket.on("createTerminal", async (id: string, callback) => {
if (terminals[id] || Object.keys(terminals).length >= 4) {
return;
}
const pty = spawn(os.platform() === "win32" ? "cmd.exe" : "bash", [], {
name: "xterm",
cols: 100,
cwd: path.join(dirName, "projects", data.sandboxId),
await lockManager.acquireLock(data.sandboxId, async () => {
try {
terminals[id] = await containers[data.sandboxId].terminal.start({
onData: (data: string) => {
io.emit("terminalResponse", { id, data });
},
size: { cols: 80, rows: 20 },
onExit: () => console.log("Terminal exited", id),
});
await terminals[id].sendData("export PS1='user> '\rclear\r");
console.log("Created terminal", id);
} catch (error) {
console.error("Error creating terminal ", id, error);
}
});
const onData = pty.onData((data) => {
io.emit("terminalResponse", {
id,
data,
});
});
const onExit = pty.onExit((code) => console.log("exit :(", code));
pty.write("export PS1='\\u > '\r");
pty.write("clear\r");
terminals[id] = {
terminal: pty,
onData,
onExit,
};
callback();
});
socket.on("resizeTerminal", (dimensions: { cols: number; rows: number }) => {
Object.values(terminals).forEach((t) => {
t.terminal.resize(dimensions.cols, dimensions.rows);
t.resize(dimensions);
});
});
@ -364,19 +368,18 @@ io.on("connection", async (socket) => {
}
try {
terminals[id].terminal.write(data);
terminals[id].sendData(data);
} catch (e) {
console.log("Error writing to terminal", e);
}
});
socket.on("closeTerminal", (id: string, callback) => {
socket.on("closeTerminal", async (id: string, callback) => {
if (!terminals[id]) {
return;
}
terminals[id].onData.dispose();
terminals[id].onExit.dispose();
await terminals[id].kill();
delete terminals[id];
callback();
@ -430,12 +433,23 @@ io.on("connection", async (socket) => {
socket.on("disconnect", async () => {
if (data.isOwner) {
Object.entries(terminals).forEach((t) => {
const { terminal, onData, onExit } = t[1];
onData.dispose();
onExit.dispose();
const terminal = t[1];
terminal.kill();
delete terminals[t[0]];
});
await lockManager.acquireLock(data.sandboxId, async () => {
try {
if (containers[data.sandboxId]) {
await containers[data.sandboxId].close();
delete containers[data.sandboxId];
console.log("Closed container", data.sandboxId);
}
} catch (error) {
console.error("Error closing container ", data.sandboxId, error);
}
});
socket.broadcast.emit(
"disableAccess",
"The sandbox owner has disconnected."