feat: replace node-pty with E2B sandboxes
This commit is contained in:
parent
b561f1e962
commit
e5b320d1c5
131
backend/server/package-lock.json
generated
131
backend/server/package-lock.json
generated
@ -12,8 +12,8 @@
|
||||
"concurrently": "^8.2.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"e2b": "^0.16.1",
|
||||
"express": "^4.19.2",
|
||||
"node-pty": "^1.0.0",
|
||||
"rate-limiter-flexible": "^5.0.3",
|
||||
"socket.io": "^4.7.5",
|
||||
"zod": "^3.22.4"
|
||||
@ -369,6 +369,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bufferutil": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
|
||||
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@ -662,6 +675,59 @@
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/e2b": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/e2b/-/e2b-0.16.1.tgz",
|
||||
"integrity": "sha512-2L1R/REEB+EezD4Q4MmcXXNATjvCYov2lv/69+PY6V95+wl1PZblIMTYAe7USxX6P6sqANxNs+kXqZr6RvXkSw==",
|
||||
"dependencies": {
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"openapi-typescript-fetch": "^1.1.3",
|
||||
"path-browserify": "^1.0.1",
|
||||
"platform": "^1.3.6",
|
||||
"ws": "^8.15.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "^4.0.8",
|
||||
"utf-8-validate": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/e2b/node_modules/utf-8-validate": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.4.tgz",
|
||||
"integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/e2b/node_modules/ws": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
|
||||
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@ -1082,6 +1148,14 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-ws": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
||||
"integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
|
||||
"peerDependencies": {
|
||||
"ws": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
@ -1173,11 +1247,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
|
||||
"integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
@ -1186,13 +1255,15 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-pty": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz",
|
||||
"integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"nan": "^2.17.0"
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
|
||||
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
@ -1265,7 +1336,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -1297,6 +1367,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-typescript-fetch": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/openapi-typescript-fetch/-/openapi-typescript-fetch-1.1.3.tgz",
|
||||
"integrity": "sha512-smLZPck4OkKMNExcw8jMgrMOGgVGx2N/s6DbKL2ftNl77g5HfoGpZGFy79RBzU/EkaO0OZpwBnslfdBfh7ZcWg==",
|
||||
"engines": {
|
||||
"node": ">= 12.0.0",
|
||||
"npm": ">= 7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
@ -1305,6 +1384,11 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
@ -1322,6 +1406,11 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/platform": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
|
||||
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
@ -1835,6 +1924,20 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utf-8-validate": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
|
||||
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
|
@ -14,8 +14,8 @@
|
||||
"concurrently": "^8.2.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"e2b": "^0.16.1",
|
||||
"express": "^4.19.2",
|
||||
"node-pty": "^1.0.0",
|
||||
"rate-limiter-flexible": "^5.0.3",
|
||||
"socket.io": "^4.7.5",
|
||||
"zod": "^3.22.4"
|
||||
|
@ -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."
|
||||
|
Loading…
x
Reference in New Issue
Block a user