Compare commits
1 Commits
fix/migrat
...
run-button
Author | SHA1 | Date | |
---|---|---|---|
af45b6196c |
106
backend/server/package-lock.json
generated
106
backend/server/package-lock.json
generated
@ -12,7 +12,7 @@
|
|||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"e2b": "^0.16.2-beta.47",
|
"e2b": "^0.16.1",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"rate-limiter-flexible": "^5.0.3",
|
"rate-limiter-flexible": "^5.0.3",
|
||||||
"simple-git": "^3.25.0",
|
"simple-git": "^3.25.0",
|
||||||
@ -41,28 +41,6 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@bufbuild/protobuf": {
|
|
||||||
"version": "1.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
|
|
||||||
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag=="
|
|
||||||
},
|
|
||||||
"node_modules/@connectrpc/connect": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-vZeOkKaAjyV4+RH3+rJZIfDFJAfr+7fyYr6sLDKbYX3uuTVszhFe9/YKf5DNqrDb5cKdKVlYkGn6DTDqMitAnA==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@bufbuild/protobuf": "^1.4.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@connectrpc/connect-web": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-13aO4psFbbm7rdOFGV0De2Za64DY/acMspgloDlcOKzLPPs0yZkhp1OOzAQeiAIr7BM/VOHIA3p8mF0inxCYTA==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@bufbuild/protobuf": "^1.4.2",
|
|
||||||
"@connectrpc/connect": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
@ -465,7 +443,6 @@
|
|||||||
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
|
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-gyp-build": "^4.3.0"
|
"node-gyp-build": "^4.3.0"
|
||||||
},
|
},
|
||||||
@ -587,11 +564,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
},
|
},
|
||||||
"node_modules/compare-versions": {
|
|
||||||
"version": "6.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz",
|
|
||||||
"integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="
|
|
||||||
},
|
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@ -765,19 +737,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/e2b": {
|
"node_modules/e2b": {
|
||||||
"version": "0.16.2-beta.47",
|
"version": "0.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/e2b/-/e2b-0.16.2-beta.47.tgz",
|
"resolved": "https://registry.npmjs.org/e2b/-/e2b-0.16.2.tgz",
|
||||||
"integrity": "sha512-tMPDYLMD+8+JyLPrsWft3NHBhK5YKOFOXzKMwpOKR5KvXOkd1silkArDwplmBUzN/eG/uRzWdtHZs9mHUQ5b9g==",
|
"integrity": "sha512-xKmVK4ipgVQPJ/uyyrfH9LnaawERRWt8U2UZhdhGfzdL/QU/OpBjuhoIbFCv1Uy6qXV4nIiJ6Nw4MBC4HmXf1g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bufbuild/protobuf": "^1.10.0",
|
"isomorphic-ws": "^5.0.0",
|
||||||
"@connectrpc/connect": "^1.4.0",
|
"normalize-path": "^3.0.0",
|
||||||
"@connectrpc/connect-web": "^1.4.0",
|
"openapi-typescript-fetch": "^1.1.3",
|
||||||
"compare-versions": "^6.1.0",
|
"path-browserify": "^1.0.1",
|
||||||
"openapi-fetch": "^0.9.7",
|
"platform": "^1.3.6",
|
||||||
"platform": "^1.3.6"
|
"ws": "^8.15.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"bufferutil": "^4.0.8",
|
||||||
|
"utf-8-validate": "^6.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
@ -1219,6 +1195,14 @@
|
|||||||
"node": ">=0.12.0"
|
"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": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
@ -1317,7 +1301,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
|
||||||
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
|
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"node-gyp-build": "bin.js",
|
"node-gyp-build": "bin.js",
|
||||||
"node-gyp-build-optional": "optional.js",
|
"node-gyp-build-optional": "optional.js",
|
||||||
@ -1400,7 +1383,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -1435,19 +1417,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/openapi-fetch": {
|
"node_modules/openapi-typescript-fetch": {
|
||||||
"version": "0.9.8",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.8.tgz",
|
"resolved": "https://registry.npmjs.org/openapi-typescript-fetch/-/openapi-typescript-fetch-1.1.3.tgz",
|
||||||
"integrity": "sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg==",
|
"integrity": "sha512-smLZPck4OkKMNExcw8jMgrMOGgVGx2N/s6DbKL2ftNl77g5HfoGpZGFy79RBzU/EkaO0OZpwBnslfdBfh7ZcWg==",
|
||||||
"dependencies": {
|
"engines": {
|
||||||
"openapi-typescript-helpers": "^0.0.8"
|
"node": ">= 12.0.0",
|
||||||
|
"npm": ">= 7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/openapi-typescript-helpers": {
|
|
||||||
"version": "0.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz",
|
|
||||||
"integrity": "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g=="
|
|
||||||
},
|
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@ -1456,6 +1434,11 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
@ -2070,7 +2053,6 @@
|
|||||||
"integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==",
|
"integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-gyp-build": "^4.3.0"
|
"node-gyp-build": "^4.3.0"
|
||||||
},
|
},
|
||||||
@ -2116,6 +2098,26 @@
|
|||||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||||
|
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||||
|
"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/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"e2b": "^0.16.2-beta.47",
|
"e2b": "^0.16.1",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"rate-limiter-flexible": "^5.0.3",
|
"rate-limiter-flexible": "^5.0.3",
|
||||||
"simple-git": "^3.25.0",
|
"simple-git": "^3.25.0",
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
import { Sandbox, ProcessHandle } from "e2b";
|
|
||||||
|
|
||||||
// Terminal class to manage a pseudo-terminal (PTY) in a sandbox environment
|
|
||||||
export class Terminal {
|
|
||||||
private pty: ProcessHandle | undefined; // Holds the PTY process handle
|
|
||||||
private sandbox: Sandbox; // Reference to the sandbox environment
|
|
||||||
|
|
||||||
// Constructor initializes the Terminal with a sandbox
|
|
||||||
constructor(sandbox: Sandbox) {
|
|
||||||
this.sandbox = sandbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the terminal with specified rows, columns, and data handler
|
|
||||||
async init({
|
|
||||||
rows = 20,
|
|
||||||
cols = 80,
|
|
||||||
onData,
|
|
||||||
}: {
|
|
||||||
rows?: number;
|
|
||||||
cols?: number;
|
|
||||||
onData: (responseData: string) => void;
|
|
||||||
}): Promise<void> {
|
|
||||||
// Create a new PTY process
|
|
||||||
this.pty = await this.sandbox.pty.create({
|
|
||||||
rows,
|
|
||||||
cols,
|
|
||||||
timeout: 0,
|
|
||||||
onData: (data: Uint8Array) => {
|
|
||||||
onData(new TextDecoder().decode(data)); // Convert received data to string and pass to handler
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send data to the terminal
|
|
||||||
async sendData(data: string) {
|
|
||||||
if (this.pty) {
|
|
||||||
await this.sandbox.pty.sendInput(this.pty.pid, new TextEncoder().encode(data));
|
|
||||||
await this.pty.wait();
|
|
||||||
} else {
|
|
||||||
console.log("Cannot send data because pty is not initialized.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resize the terminal
|
|
||||||
async resize(size: { cols: number; rows: number }): Promise<void> {
|
|
||||||
if (this.pty) {
|
|
||||||
await this.sandbox.pty.resize(this.pty.pid, size);
|
|
||||||
} else {
|
|
||||||
console.log("Cannot resize terminal because pty is not initialized.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the terminal, killing the PTY process and stopping the input stream
|
|
||||||
async close(): Promise<void> {
|
|
||||||
if (this.pty) {
|
|
||||||
await this.pty.kill();
|
|
||||||
} else {
|
|
||||||
console.log("Cannot kill pty because it is not initialized.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage example:
|
|
||||||
// const terminal = new Terminal(sandbox);
|
|
||||||
// await terminal.init();
|
|
||||||
// terminal.sendData('ls -la');
|
|
||||||
// await terminal.resize({ cols: 100, rows: 30 });
|
|
||||||
// await terminal.close();
|
|
@ -20,11 +20,7 @@ import {
|
|||||||
saveFile,
|
saveFile,
|
||||||
} from "./fileoperations";
|
} from "./fileoperations";
|
||||||
import { LockManager } from "./utils";
|
import { LockManager } from "./utils";
|
||||||
|
import { Sandbox, Terminal, FilesystemManager } from "e2b";
|
||||||
import { Sandbox, Filesystem } from "e2b";
|
|
||||||
|
|
||||||
import { Terminal } from "./Terminal"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MAX_BODY_SIZE,
|
MAX_BODY_SIZE,
|
||||||
createFileRL,
|
createFileRL,
|
||||||
@ -56,12 +52,12 @@ const terminals: Record<string, Terminal> = {};
|
|||||||
const dirName = "/home/user";
|
const dirName = "/home/user";
|
||||||
|
|
||||||
const moveFile = async (
|
const moveFile = async (
|
||||||
filesystem: Filesystem,
|
filesystem: FilesystemManager,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
newFilePath: string
|
newFilePath: string
|
||||||
) => {
|
) => {
|
||||||
const fileContents = await filesystem.read(filePath);
|
const fileContents = await filesystem.readBytes(filePath);
|
||||||
await filesystem.write(newFilePath, fileContents);
|
await filesystem.writeBytes(newFilePath, fileContents);
|
||||||
await filesystem.remove(filePath);
|
await filesystem.remove(filePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -160,7 +156,7 @@ io.on("connection", async (socket) => {
|
|||||||
await lockManager.acquireLock(data.sandboxId, async () => {
|
await lockManager.acquireLock(data.sandboxId, async () => {
|
||||||
try {
|
try {
|
||||||
if (!containers[data.sandboxId]) {
|
if (!containers[data.sandboxId]) {
|
||||||
containers[data.sandboxId] = await Sandbox.create({ timeoutMs: 1200000 });
|
containers[data.sandboxId] = await Sandbox.create();
|
||||||
console.log("Created container ", data.sandboxId);
|
console.log("Created container ", data.sandboxId);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -171,7 +167,7 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
// Change the owner of the project directory to user
|
// Change the owner of the project directory to user
|
||||||
const fixPermissions = async () => {
|
const fixPermissions = async () => {
|
||||||
await containers[data.sandboxId].commands.run(
|
await containers[data.sandboxId].process.startAndWait(
|
||||||
`sudo chown -R user "${path.join(dirName, "projects", data.sandboxId)}"`
|
`sudo chown -R user "${path.join(dirName, "projects", data.sandboxId)}"`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -179,14 +175,10 @@ io.on("connection", async (socket) => {
|
|||||||
const sandboxFiles = await getSandboxFiles(data.sandboxId);
|
const sandboxFiles = await getSandboxFiles(data.sandboxId);
|
||||||
sandboxFiles.fileData.forEach(async (file) => {
|
sandboxFiles.fileData.forEach(async (file) => {
|
||||||
const filePath = path.join(dirName, file.id);
|
const filePath = path.join(dirName, file.id);
|
||||||
try {
|
await containers[data.sandboxId].filesystem.makeDir(
|
||||||
await containers[data.sandboxId].files.makeDir(
|
path.dirname(filePath)
|
||||||
path.dirname(filePath)
|
);
|
||||||
);
|
await containers[data.sandboxId].filesystem.write(filePath, file.data);
|
||||||
} catch (e: any) {
|
|
||||||
console.log("Failed to create directory: " + e);
|
|
||||||
}
|
|
||||||
await containers[data.sandboxId].files.write(filePath, file.data);
|
|
||||||
});
|
});
|
||||||
fixPermissions();
|
fixPermissions();
|
||||||
|
|
||||||
@ -239,7 +231,7 @@ io.on("connection", async (socket) => {
|
|||||||
if (!file) return;
|
if (!file) return;
|
||||||
file.data = body;
|
file.data = body;
|
||||||
|
|
||||||
await containers[data.sandboxId].files.write(
|
await containers[data.sandboxId].filesystem.write(
|
||||||
path.join(dirName, file.id),
|
path.join(dirName, file.id),
|
||||||
body
|
body
|
||||||
);
|
);
|
||||||
@ -261,7 +253,7 @@ io.on("connection", async (socket) => {
|
|||||||
const newFileId = folderId + "/" + parts.pop();
|
const newFileId = folderId + "/" + parts.pop();
|
||||||
|
|
||||||
await moveFile(
|
await moveFile(
|
||||||
containers[data.sandboxId].files,
|
containers[data.sandboxId].filesystem,
|
||||||
path.join(dirName, fileId),
|
path.join(dirName, fileId),
|
||||||
path.join(dirName, newFileId)
|
path.join(dirName, newFileId)
|
||||||
);
|
);
|
||||||
@ -354,7 +346,7 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
const id = `projects/${data.sandboxId}/${name}`;
|
const id = `projects/${data.sandboxId}/${name}`;
|
||||||
|
|
||||||
await containers[data.sandboxId].files.write(
|
await containers[data.sandboxId].filesystem.write(
|
||||||
path.join(dirName, id),
|
path.join(dirName, id),
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
@ -391,7 +383,7 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
const id = `projects/${data.sandboxId}/${name}`;
|
const id = `projects/${data.sandboxId}/${name}`;
|
||||||
|
|
||||||
await containers[data.sandboxId].files.makeDir(
|
await containers[data.sandboxId].filesystem.makeDir(
|
||||||
path.join(dirName, id)
|
path.join(dirName, id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -420,7 +412,7 @@ io.on("connection", async (socket) => {
|
|||||||
parts.slice(0, parts.length - 1).join("/") + "/" + newName;
|
parts.slice(0, parts.length - 1).join("/") + "/" + newName;
|
||||||
|
|
||||||
await moveFile(
|
await moveFile(
|
||||||
containers[data.sandboxId].files,
|
containers[data.sandboxId].filesystem,
|
||||||
path.join(dirName, fileId),
|
path.join(dirName, fileId),
|
||||||
path.join(dirName, newFileId)
|
path.join(dirName, newFileId)
|
||||||
);
|
);
|
||||||
@ -443,7 +435,7 @@ io.on("connection", async (socket) => {
|
|||||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
await containers[data.sandboxId].files.remove(
|
await containers[data.sandboxId].filesystem.remove(
|
||||||
path.join(dirName, fileId)
|
path.join(dirName, fileId)
|
||||||
);
|
);
|
||||||
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
||||||
@ -470,7 +462,7 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
await containers[data.sandboxId].files.remove(
|
await containers[data.sandboxId].filesystem.remove(
|
||||||
path.join(dirName, file)
|
path.join(dirName, file)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -499,36 +491,35 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
await lockManager.acquireLock(data.sandboxId, async () => {
|
await lockManager.acquireLock(data.sandboxId, async () => {
|
||||||
try {
|
try {
|
||||||
terminals[id] = new Terminal(containers[data.sandboxId])
|
terminals[id] = await containers[data.sandboxId].terminal.start({
|
||||||
await terminals[id].init({
|
onData: (responseData: string) => {
|
||||||
onData: (responseString: string) => {
|
io.emit("terminalResponse", { id, data: responseData });
|
||||||
io.emit("terminalResponse", { id, data: responseString });
|
|
||||||
|
|
||||||
function extractPortNumber(inputString: string) {
|
function extractPortNumber(inputString: string) {
|
||||||
// Remove ANSI escape codes
|
// Remove ANSI escape codes
|
||||||
const cleanedString = inputString.replace(/\x1B\[[0-9;]*m/g, '');
|
const cleanedString = inputString.replace(/\x1B\[[0-9;]*m/g, '');
|
||||||
|
|
||||||
// Regular expression to match port number
|
// Regular expression to match port number
|
||||||
const regex = /http:\/\/localhost:(\d+)/;
|
const regex = /http:\/\/localhost:(\d+)/;
|
||||||
// If a match is found, return the port number
|
// If a match is found, return the port number
|
||||||
const match = cleanedString.match(regex);
|
const match = cleanedString.match(regex);
|
||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
}
|
}
|
||||||
const port = parseInt(extractPortNumber(responseString) ?? "");
|
const port = parseInt(extractPortNumber(responseData) ?? "");
|
||||||
if (port) {
|
if (port) {
|
||||||
io.emit(
|
io.emit(
|
||||||
"previewURL",
|
"previewURL",
|
||||||
"https://" + containers[data.sandboxId].getHost(port)
|
"https://" + containers[data.sandboxId].getHostname(port)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
cols: 80,
|
size: { cols: 80, rows: 20 },
|
||||||
rows: 20,
|
onExit: () => console.log("Terminal exited", id),
|
||||||
//onExit: () => console.log("Terminal exited", id),
|
|
||||||
});
|
});
|
||||||
await terminals[id].sendData(
|
await terminals[id].sendData(
|
||||||
`cd "${path.join(dirName, "projects", data.sandboxId)}"\rexport PS1='user> '\rclear\r`
|
`cd "${path.join(dirName, "projects", data.sandboxId)}"\r`
|
||||||
);
|
);
|
||||||
|
await terminals[id].sendData("export PS1='user> '\rclear\r");
|
||||||
console.log("Created terminal", id);
|
console.log("Created terminal", id);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(`Error creating terminal ${id}:`, e);
|
console.error(`Error creating terminal ${id}:`, e);
|
||||||
@ -557,7 +548,7 @@ io.on("connection", async (socket) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
socket.on("terminalData", async (id: string, data: string) => {
|
socket.on("terminalData", (id: string, data: string) => {
|
||||||
try {
|
try {
|
||||||
if (!terminals[id]) {
|
if (!terminals[id]) {
|
||||||
return;
|
return;
|
||||||
@ -576,7 +567,7 @@ io.on("connection", async (socket) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await terminals[id].close();
|
await terminals[id].kill();
|
||||||
delete terminals[id];
|
delete terminals[id];
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
@ -645,7 +636,7 @@ io.on("connection", async (socket) => {
|
|||||||
if (data.isOwner && connections[data.sandboxId] <= 0) {
|
if (data.isOwner && connections[data.sandboxId] <= 0) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(terminals).map(async ([key, terminal]) => {
|
Object.entries(terminals).map(async ([key, terminal]) => {
|
||||||
await terminal.close();
|
await terminal.kill();
|
||||||
delete terminals[key];
|
delete terminals[key];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -653,7 +644,7 @@ io.on("connection", async (socket) => {
|
|||||||
await lockManager.acquireLock(data.sandboxId, async () => {
|
await lockManager.acquireLock(data.sandboxId, async () => {
|
||||||
try {
|
try {
|
||||||
if (containers[data.sandboxId]) {
|
if (containers[data.sandboxId]) {
|
||||||
await containers[data.sandboxId].kill();
|
await containers[data.sandboxId].close();
|
||||||
delete containers[data.sandboxId];
|
delete containers[data.sandboxId];
|
||||||
console.log("Closed container", data.sandboxId);
|
console.log("Closed container", data.sandboxId);
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,7 @@ import { ClerkProvider } from "@clerk/nextjs"
|
|||||||
import { Toaster } from "@/components/ui/sonner"
|
import { Toaster } from "@/components/ui/sonner"
|
||||||
import { Analytics } from "@vercel/analytics/react"
|
import { Analytics } from "@vercel/analytics/react"
|
||||||
import { TerminalProvider } from '@/context/TerminalContext';
|
import { TerminalProvider } from '@/context/TerminalContext';
|
||||||
import { PreviewProvider } from "@/context/PreviewContext";
|
import { PreviewProvider } from "@/context/PreviewContext"
|
||||||
import { SocketProvider } from '@/context/SocketContext'
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Sandbox",
|
title: "Sandbox",
|
||||||
@ -16,7 +15,7 @@ export const metadata: Metadata = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}>) {
|
}>) {
|
||||||
@ -30,13 +29,11 @@ export default function RootLayout({
|
|||||||
forcedTheme="dark"
|
forcedTheme="dark"
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<SocketProvider>
|
|
||||||
<PreviewProvider>
|
<PreviewProvider>
|
||||||
<TerminalProvider>
|
<TerminalProvider>
|
||||||
{children}
|
{children}
|
||||||
</TerminalProvider>
|
</TerminalProvider>
|
||||||
</PreviewProvider>
|
</PreviewProvider>
|
||||||
</SocketProvider>
|
|
||||||
<Analytics />
|
<Analytics />
|
||||||
<Toaster position="bottom-left" richColors />
|
<Toaster position="bottom-left" richColors />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
@ -49,8 +49,10 @@ export default function Dashboard({
|
|||||||
const q = searchParams.get("q")
|
const q = searchParams.get("q")
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
useEffect(() => { // update the dashboard to show a new project
|
useEffect(() => {
|
||||||
|
if (!sandboxes) {
|
||||||
router.refresh()
|
router.refresh()
|
||||||
|
}
|
||||||
}, [sandboxes])
|
}, [sandboxes])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import { SetStateAction, useCallback, useEffect, useRef, useState } from "react"
|
import { SetStateAction, useCallback, useEffect, useRef, useState } from "react"
|
||||||
import monaco from "monaco-editor"
|
import monaco from "monaco-editor"
|
||||||
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
|
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
|
||||||
|
import { Socket, io } from "socket.io-client"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { useClerk } from "@clerk/nextjs"
|
import { useClerk } from "@clerk/nextjs"
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ import PreviewWindow from "./preview"
|
|||||||
import Terminals from "./terminals"
|
import Terminals from "./terminals"
|
||||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||||
import { PreviewProvider, usePreview } from '@/context/PreviewContext';
|
import { PreviewProvider, usePreview } from '@/context/PreviewContext';
|
||||||
import { useSocket } from "@/context/SocketContext"
|
import { useTerminal } from '@/context/TerminalContext';
|
||||||
|
|
||||||
export default function CodeEditor({
|
export default function CodeEditor({
|
||||||
userData,
|
userData,
|
||||||
@ -40,20 +41,24 @@ export default function CodeEditor({
|
|||||||
userData: User
|
userData: User
|
||||||
sandboxData: Sandbox
|
sandboxData: Sandbox
|
||||||
}) {
|
}) {
|
||||||
|
const socketRef = useRef<Socket | null>(null);
|
||||||
|
|
||||||
//SocketContext functions and effects
|
// Initialize socket connection if it doesn't exist
|
||||||
const { socket, setUserAndSandboxId } = useSocket();
|
if (!socketRef.current) {
|
||||||
|
socketRef.current = io(
|
||||||
|
`${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userData.id}&sandboxId=${sandboxData.id}`,
|
||||||
|
{
|
||||||
|
timeout: 2000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Terminalcontext functionsand effects
|
||||||
|
const { setUserAndSandboxId } = useTerminal();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Ensure userData.id and sandboxData.id are available before attempting to connect
|
setUserAndSandboxId(userData.id, sandboxData.id);
|
||||||
if (userData.id && sandboxData.id) {
|
}, [userData.id, sandboxData.id, setUserAndSandboxId]);
|
||||||
// Check if the socket is not initialized or not connected
|
|
||||||
if (!socket || (socket && !socket.connected)) {
|
|
||||||
// Initialize socket connection
|
|
||||||
setUserAndSandboxId(userData.id, sandboxData.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [socket, userData.id, sandboxData.id, setUserAndSandboxId]);
|
|
||||||
|
|
||||||
//Preview Button state
|
//Preview Button state
|
||||||
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true)
|
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true)
|
||||||
@ -328,9 +333,9 @@ export default function CodeEditor({
|
|||||||
);
|
);
|
||||||
console.log(`Saving file...${activeFileId}`);
|
console.log(`Saving file...${activeFileId}`);
|
||||||
console.log(`Saving file...${value}`);
|
console.log(`Saving file...${value}`);
|
||||||
socket?.emit("saveFile", activeFileId, value);
|
socketRef.current?.emit("saveFile", activeFileId, value);
|
||||||
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000),
|
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000),
|
||||||
[socket]
|
[socketRef]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -427,11 +432,11 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
// Connection/disconnection effect
|
// Connection/disconnection effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket?.connect()
|
socketRef.current?.connect()
|
||||||
return () => {
|
return () => {
|
||||||
socket?.disconnect()
|
socketRef.current?.disconnect()
|
||||||
}
|
}
|
||||||
}, [socket])
|
}, [])
|
||||||
|
|
||||||
// Socket event listener effect
|
// Socket event listener effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -464,24 +469,25 @@ export default function CodeEditor({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
socket?.on("connect", onConnect)
|
socketRef.current?.on("connect", onConnect)
|
||||||
socket?.on("disconnect", onDisconnect)
|
socketRef.current?.on("disconnect", onDisconnect)
|
||||||
socket?.on("loaded", onLoadedEvent)
|
socketRef.current?.on("loaded", onLoadedEvent)
|
||||||
socket?.on("error", onError)
|
socketRef.current?.on("error", onError)
|
||||||
socket?.on("terminalResponse", onTerminalResponse)
|
socketRef.current?.on("terminalResponse", onTerminalResponse)
|
||||||
socket?.on("disableAccess", onDisableAccess)
|
socketRef.current?.on("disableAccess", onDisableAccess)
|
||||||
socket?.on("previewURL", loadPreviewURL)
|
socketRef.current?.on("previewURL", loadPreviewURL)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket?.off("connect", onConnect)
|
socketRef.current?.off("connect", onConnect)
|
||||||
socket?.off("disconnect", onDisconnect)
|
socketRef.current?.off("disconnect", onDisconnect)
|
||||||
socket?.off("loaded", onLoadedEvent)
|
socketRef.current?.off("loaded", onLoadedEvent)
|
||||||
socket?.off("error", onError)
|
socketRef.current?.off("error", onError)
|
||||||
socket?.off("terminalResponse", onTerminalResponse)
|
socketRef.current?.off("terminalResponse", onTerminalResponse)
|
||||||
socket?.off("disableAccess", onDisableAccess)
|
socketRef.current?.off("disableAccess", onDisableAccess)
|
||||||
socket?.off("previewURL", loadPreviewURL)
|
socketRef.current?.off("previewURL", loadPreviewURL)
|
||||||
}
|
}
|
||||||
}, [socket, terminals, setTerminals, setFiles, toast, setDisableAccess, isOwner, loadPreviewURL]);
|
// }, []);
|
||||||
|
}, [terminals])
|
||||||
|
|
||||||
// Helper functions for tabs:
|
// Helper functions for tabs:
|
||||||
|
|
||||||
@ -491,13 +497,14 @@ export default function CodeEditor({
|
|||||||
const fileCache = useRef(new Map());
|
const fileCache = useRef(new Map());
|
||||||
|
|
||||||
// Debounced function to get file content
|
// Debounced function to get file content
|
||||||
const debouncedGetFile =
|
const debouncedGetFile = useCallback(
|
||||||
(tabId: any, callback: any) => {
|
debounce((tabId, callback) => {
|
||||||
socket?.emit('getFile', tabId, callback);
|
socketRef.current?.emit('getFile', tabId, callback);
|
||||||
} // 300ms debounce delay, adjust as needed
|
}, 300), // 300ms debounce delay, adjust as needed
|
||||||
|
[]
|
||||||
const selectFile = (tab: TTab) => {
|
);
|
||||||
|
|
||||||
|
const selectFile = useCallback((tab: TTab) => {
|
||||||
if (tab.id === activeFileId) return;
|
if (tab.id === activeFileId) return;
|
||||||
|
|
||||||
setGenerate((prev) => ({ ...prev, show: false }));
|
setGenerate((prev) => ({ ...prev, show: false }));
|
||||||
@ -522,7 +529,7 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
setEditorLanguage(processFileType(tab.name));
|
setEditorLanguage(processFileType(tab.name));
|
||||||
setActiveFileId(tab.id);
|
setActiveFileId(tab.id);
|
||||||
};
|
}, [activeFileId, tabs, debouncedGetFile]);
|
||||||
|
|
||||||
// Close tab and remove from tabs
|
// Close tab and remove from tabs
|
||||||
const closeTab = (id: string) => {
|
const closeTab = (id: string) => {
|
||||||
@ -596,7 +603,7 @@ export default function CodeEditor({
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
socket?.emit("renameFile", id, newName)
|
socketRef.current?.emit("renameFile", id, newName)
|
||||||
setTabs((prev) =>
|
setTabs((prev) =>
|
||||||
prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab))
|
prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab))
|
||||||
)
|
)
|
||||||
@ -605,7 +612,7 @@ export default function CodeEditor({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteFile = (file: TFile) => {
|
const handleDeleteFile = (file: TFile) => {
|
||||||
socket?.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => {
|
socketRef.current?.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => {
|
||||||
setFiles(response)
|
setFiles(response)
|
||||||
})
|
})
|
||||||
closeTab(file.id)
|
closeTab(file.id)
|
||||||
@ -615,11 +622,11 @@ export default function CodeEditor({
|
|||||||
setDeletingFolderId(folder.id)
|
setDeletingFolderId(folder.id)
|
||||||
console.log("deleting folder", folder.id)
|
console.log("deleting folder", folder.id)
|
||||||
|
|
||||||
socket?.emit("getFolder", folder.id, (response: string[]) =>
|
socketRef.current?.emit("getFolder", folder.id, (response: string[]) =>
|
||||||
closeTabs(response)
|
closeTabs(response)
|
||||||
)
|
)
|
||||||
|
|
||||||
socket?.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => {
|
socketRef.current?.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => {
|
||||||
setFiles(response)
|
setFiles(response)
|
||||||
setDeletingFolderId("")
|
setDeletingFolderId("")
|
||||||
})
|
})
|
||||||
@ -647,7 +654,7 @@ export default function CodeEditor({
|
|||||||
{generate.show && ai ? (
|
{generate.show && ai ? (
|
||||||
<GenerateInput
|
<GenerateInput
|
||||||
user={userData}
|
user={userData}
|
||||||
socket={socket!}
|
socket={socketRef.current}
|
||||||
width={generate.width - 90}
|
width={generate.width - 90}
|
||||||
data={{
|
data={{
|
||||||
fileName: tabs.find((t) => t.id === activeFileId)?.name ?? "",
|
fileName: tabs.find((t) => t.id === activeFileId)?.name ?? "",
|
||||||
@ -707,7 +714,7 @@ export default function CodeEditor({
|
|||||||
handleRename={handleRename}
|
handleRename={handleRename}
|
||||||
handleDeleteFile={handleDeleteFile}
|
handleDeleteFile={handleDeleteFile}
|
||||||
handleDeleteFolder={handleDeleteFolder}
|
handleDeleteFolder={handleDeleteFolder}
|
||||||
socket={socket!}
|
socket={socketRef.current}
|
||||||
setFiles={setFiles}
|
setFiles={setFiles}
|
||||||
addNew={(name, type) => addNew(name, type, setFiles, sandboxData)}
|
addNew={(name, type) => addNew(name, type, setFiles, sandboxData)}
|
||||||
deletingFolderId={deletingFolderId}
|
deletingFolderId={deletingFolderId}
|
||||||
@ -825,7 +832,7 @@ export default function CodeEditor({
|
|||||||
open={() => {
|
open={() => {
|
||||||
usePreview().previewPanelRef.current?.expand()
|
usePreview().previewPanelRef.current?.expand()
|
||||||
setIsPreviewCollapsed(false)
|
setIsPreviewCollapsed(false)
|
||||||
}} collapsed={isPreviewCollapsed} src={previewURL} ref={previewWindowRef} />
|
} } collapsed={isPreviewCollapsed} src={previewURL} ref={previewWindowRef} />
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle />
|
<ResizableHandle />
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
|
@ -8,15 +8,12 @@ import { toast } from "sonner";
|
|||||||
import EditorTerminal from "./terminal";
|
import EditorTerminal from "./terminal";
|
||||||
import { useTerminal } from "@/context/TerminalContext";
|
import { useTerminal } from "@/context/TerminalContext";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useSocket } from "@/context/SocketContext"
|
|
||||||
|
|
||||||
export default function Terminals() {
|
export default function Terminals() {
|
||||||
|
|
||||||
const { socket } = useSocket();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
terminals,
|
terminals,
|
||||||
setTerminals,
|
setTerminals,
|
||||||
|
socket,
|
||||||
createNewTerminal,
|
createNewTerminal,
|
||||||
closeTerminal,
|
closeTerminal,
|
||||||
activeTerminalId,
|
activeTerminalId,
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
||||||
import { io, Socket } from 'socket.io-client';
|
|
||||||
|
|
||||||
interface SocketContextType {
|
|
||||||
socket: Socket | null;
|
|
||||||
setUserAndSandboxId: (userId: string, sandboxId: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SocketContext = createContext<SocketContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
||||||
const [socket, setSocket] = useState<Socket | null>(null);
|
|
||||||
const [userId, setUserId] = useState<string | null>(null);
|
|
||||||
const [sandboxId, setSandboxId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (userId && sandboxId) {
|
|
||||||
console.log("Initializing socket connection...");
|
|
||||||
const newSocket = io(`${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userId}&sandboxId=${sandboxId}`);
|
|
||||||
console.log("Socket instance:", newSocket);
|
|
||||||
setSocket(newSocket);
|
|
||||||
|
|
||||||
newSocket.on('connect', () => {
|
|
||||||
console.log("Socket connected:", newSocket.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
newSocket.on('disconnect', () => {
|
|
||||||
console.log("Socket disconnected");
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
console.log("Disconnecting socket...");
|
|
||||||
newSocket.disconnect();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [userId, sandboxId]);
|
|
||||||
|
|
||||||
const setUserAndSandboxId = (newUserId: string, newSandboxId: string) => {
|
|
||||||
setUserId(newUserId);
|
|
||||||
setSandboxId(newSandboxId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const value = {
|
|
||||||
socket,
|
|
||||||
setUserAndSandboxId,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SocketContext.Provider value={ value }>
|
|
||||||
{children}
|
|
||||||
</SocketContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSocket = (): SocketContextType => {
|
|
||||||
const context = useContext(SocketContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('useSocket must be used within a SocketProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
@ -1,11 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { createContext, useContext, useState } from 'react';
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||||
|
import { io, Socket } from 'socket.io-client';
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import { createTerminal as createTerminalHelper, closeTerminal as closeTerminalHelper } from '@/lib/terminal';
|
import { createTerminal as createTerminalHelper, closeTerminal as closeTerminalHelper } from '@/lib/terminal';
|
||||||
import { useSocket } from '@/context/SocketContext';
|
|
||||||
|
|
||||||
interface TerminalContextType {
|
interface TerminalContextType {
|
||||||
|
socket: Socket | null;
|
||||||
terminals: { id: string; terminal: Terminal | null }[];
|
terminals: { id: string; terminal: Terminal | null }[];
|
||||||
setTerminals: React.Dispatch<React.SetStateAction<{ id: string; terminal: Terminal | null }[]>>;
|
setTerminals: React.Dispatch<React.SetStateAction<{ id: string; terminal: Terminal | null }[]>>;
|
||||||
activeTerminalId: string;
|
activeTerminalId: string;
|
||||||
@ -14,16 +15,41 @@ interface TerminalContextType {
|
|||||||
setCreatingTerminal: React.Dispatch<React.SetStateAction<boolean>>;
|
setCreatingTerminal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
createNewTerminal: (command?: string) => Promise<void>;
|
createNewTerminal: (command?: string) => Promise<void>;
|
||||||
closeTerminal: (id: string) => void;
|
closeTerminal: (id: string) => void;
|
||||||
|
setUserAndSandboxId: (userId: string, sandboxId: string) => void;
|
||||||
deploy: (callback: () => void) => void;
|
deploy: (callback: () => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TerminalContext = createContext<TerminalContextType | undefined>(undefined);
|
const TerminalContext = createContext<TerminalContextType | undefined>(undefined);
|
||||||
|
|
||||||
export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const { socket } = useSocket();
|
const [socket, setSocket] = useState<Socket | null>(null);
|
||||||
const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]);
|
const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]);
|
||||||
const [activeTerminalId, setActiveTerminalId] = useState<string>('');
|
const [activeTerminalId, setActiveTerminalId] = useState<string>('');
|
||||||
const [creatingTerminal, setCreatingTerminal] = useState<boolean>(false);
|
const [creatingTerminal, setCreatingTerminal] = useState<boolean>(false);
|
||||||
|
const [userId, setUserId] = useState<string | null>(null);
|
||||||
|
const [sandboxId, setSandboxId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userId && sandboxId) {
|
||||||
|
console.log("Initializing socket connection...");
|
||||||
|
const newSocket = io(`${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userId}&sandboxId=${sandboxId}`);
|
||||||
|
console.log("Socket instance:", newSocket);
|
||||||
|
setSocket(newSocket);
|
||||||
|
|
||||||
|
newSocket.on('connect', () => {
|
||||||
|
console.log("Socket connected:", newSocket.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on('disconnect', () => {
|
||||||
|
console.log("Socket disconnected");
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
console.log("Disconnecting socket...");
|
||||||
|
newSocket.disconnect();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [userId, sandboxId]);
|
||||||
|
|
||||||
const createNewTerminal = async (command?: string): Promise<void> => {
|
const createNewTerminal = async (command?: string): Promise<void> => {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
@ -59,6 +85,11 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setUserAndSandboxId = (newUserId: string, newSandboxId: string) => {
|
||||||
|
setUserId(newUserId);
|
||||||
|
setSandboxId(newSandboxId);
|
||||||
|
};
|
||||||
|
|
||||||
const deploy = (callback: () => void) => {
|
const deploy = (callback: () => void) => {
|
||||||
if (!socket) console.error("Couldn't deploy: No socket");
|
if (!socket) console.error("Couldn't deploy: No socket");
|
||||||
console.log("Deploying...")
|
console.log("Deploying...")
|
||||||
@ -68,6 +99,7 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||||||
}
|
}
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
|
socket,
|
||||||
terminals,
|
terminals,
|
||||||
setTerminals,
|
setTerminals,
|
||||||
activeTerminalId,
|
activeTerminalId,
|
||||||
@ -76,6 +108,7 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||||||
setCreatingTerminal,
|
setCreatingTerminal,
|
||||||
createNewTerminal,
|
createNewTerminal,
|
||||||
closeTerminal,
|
closeTerminal,
|
||||||
|
setUserAndSandboxId,
|
||||||
deploy
|
deploy
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user