chore: add comments

This commit is contained in:
James Murdza 2024-10-19 15:16:24 -06:00
parent 54706314ea
commit 7722c533a4
4 changed files with 92 additions and 16 deletions

View File

@ -1,15 +1,19 @@
import { SSHConfig, SSHSocketClient } from "./SSHSocketClient" import { SSHConfig, SSHSocketClient } from "./SSHSocketClient"
// Interface for the response structure from Dokku commands
export interface DokkuResponse { export interface DokkuResponse {
ok: boolean ok: boolean
output: string output: string
} }
// DokkuClient class extends SSHSocketClient to interact with Dokku via SSH
export class DokkuClient extends SSHSocketClient { export class DokkuClient extends SSHSocketClient {
constructor(config: SSHConfig) { constructor(config: SSHConfig) {
// Initialize with Dokku daemon socket path
super(config, "/var/run/dokku-daemon/dokku-daemon.sock") super(config, "/var/run/dokku-daemon/dokku-daemon.sock")
} }
// Send a command to Dokku and parse the response
async sendCommand(command: string): Promise<DokkuResponse> { async sendCommand(command: string): Promise<DokkuResponse> {
try { try {
const response = await this.sendData(command) const response = await this.sendData(command)
@ -18,15 +22,18 @@ export class DokkuClient extends SSHSocketClient {
throw new Error("Received data is not a string") throw new Error("Received data is not a string")
} }
// Parse the JSON response from Dokku
return JSON.parse(response) return JSON.parse(response)
} catch (error: any) { } catch (error: any) {
throw new Error(`Failed to send command: ${error.message}`) throw new Error(`Failed to send command: ${error.message}`)
} }
} }
// List all deployed Dokku apps
async listApps(): Promise<string[]> { async listApps(): Promise<string[]> {
const response = await this.sendCommand("apps:list") const response = await this.sendCommand("apps:list")
return response.output.split("\n").slice(1) // Split by newline and ignore the first line (header) // Split the output by newline and remove the header
return response.output.split("\n").slice(1)
} }
} }

View File

@ -12,11 +12,13 @@ import {
import { MAX_BODY_SIZE } from "./ratelimit" import { MAX_BODY_SIZE } from "./ratelimit"
import { TFile, TFileData, TFolder } from "./types" import { TFile, TFileData, TFolder } from "./types"
// Define the structure for sandbox files
export type SandboxFiles = { export type SandboxFiles = {
files: (TFolder | TFile)[] files: (TFolder | TFile)[]
fileData: TFileData[] fileData: TFileData[]
} }
// FileManager class to handle file operations in a sandbox
export class FileManager { export class FileManager {
private sandboxId: string private sandboxId: string
private sandbox: Sandbox private sandbox: Sandbox
@ -25,6 +27,7 @@ export class FileManager {
private dirName = "/home/user" private dirName = "/home/user"
private refreshFileList: (files: SandboxFiles) => void private refreshFileList: (files: SandboxFiles) => void
// Constructor to initialize the FileManager
constructor( constructor(
sandboxId: string, sandboxId: string,
sandbox: Sandbox, sandbox: Sandbox,
@ -36,6 +39,7 @@ export class FileManager {
this.refreshFileList = refreshFileList this.refreshFileList = refreshFileList
} }
// Initialize the FileManager
async initialize() { async initialize() {
this.sandboxFiles = await getSandboxFiles(this.sandboxId) this.sandboxFiles = await getSandboxFiles(this.sandboxId)
const projectDirectory = path.posix.join( const projectDirectory = path.posix.join(
@ -94,6 +98,7 @@ export class FileManager {
} }
} }
// Watch a directory for changes
async watchDirectory(directory: string): Promise<WatchHandle | undefined> { async watchDirectory(directory: string): Promise<WatchHandle | undefined> {
try { try {
const handle = await this.sandbox.files.watch( const handle = await this.sandbox.files.watch(
@ -130,7 +135,7 @@ export class FileManager {
) )
} }
// A new file or directory was created. // Handle file/directory creation event
if (event.type === "create") { if (event.type === "create") {
const folder = findFolderById( const folder = findFolderById(
this.sandboxFiles.files, this.sandboxFiles.files,
@ -140,16 +145,16 @@ export class FileManager {
const newItem = isDir const newItem = isDir
? ({ ? ({
id: sandboxFilePath, id: sandboxFilePath,
name: event.name, name: event.name,
type: "folder", type: "folder",
children: [], children: [],
} as TFolder) } as TFolder)
: ({ : ({
id: sandboxFilePath, id: sandboxFilePath,
name: event.name, name: event.name,
type: "file", type: "file",
} as TFile) } as TFile)
if (folder) { if (folder) {
// If the folder exists, add the new item (file/folder) as a child // If the folder exists, add the new item (file/folder) as a child
@ -174,7 +179,7 @@ export class FileManager {
console.log(`Create ${sandboxFilePath}`) console.log(`Create ${sandboxFilePath}`)
} }
// A file or directory was removed or renamed. // Handle file/directory removal or rename event
else if (event.type === "remove" || event.type == "rename") { else if (event.type === "remove" || event.type == "rename") {
const folder = findFolderById( const folder = findFolderById(
this.sandboxFiles.files, this.sandboxFiles.files,
@ -206,7 +211,7 @@ export class FileManager {
console.log(`Removed: ${sandboxFilePath}`) console.log(`Removed: ${sandboxFilePath}`)
} }
// The contents of a file were changed. // Handle file write event
else if (event.type === "write") { else if (event.type === "write") {
const folder = findFolderById( const folder = findFolderById(
this.sandboxFiles.files, this.sandboxFiles.files,
@ -259,6 +264,7 @@ export class FileManager {
} }
} }
// Watch subdirectories recursively
async watchSubdirectories(directory: string) { async watchSubdirectories(directory: string) {
const dirContent = await this.sandbox.files.list(directory) const dirContent = await this.sandbox.files.list(directory)
await Promise.all( await Promise.all(
@ -271,15 +277,18 @@ export class FileManager {
) )
} }
// Get file content
async getFile(fileId: string): Promise<string | undefined> { async getFile(fileId: string): Promise<string | undefined> {
const file = this.sandboxFiles.fileData.find((f) => f.id === fileId) const file = this.sandboxFiles.fileData.find((f) => f.id === fileId)
return file?.data return file?.data
} }
// Get folder content
async getFolder(folderId: string): Promise<string[]> { async getFolder(folderId: string): Promise<string[]> {
return getFolder(folderId) return getFolder(folderId)
} }
// Save file content
async saveFile(fileId: string, body: string): Promise<void> { async saveFile(fileId: string, body: string): Promise<void> {
if (!fileId) return // handles saving when no file is open if (!fileId) return // handles saving when no file is open
@ -295,6 +304,7 @@ export class FileManager {
this.fixPermissions() this.fixPermissions()
} }
// Move a file to a different folder
async moveFile( async moveFile(
fileId: string, fileId: string,
folderId: string folderId: string
@ -318,6 +328,7 @@ export class FileManager {
return newFiles.files return newFiles.files
} }
// Move a file within the container
private async moveFileInContainer(oldPath: string, newPath: string) { private async moveFileInContainer(oldPath: string, newPath: string) {
try { try {
const fileContents = await this.sandbox.files.read( const fileContents = await this.sandbox.files.read(
@ -333,6 +344,7 @@ export class FileManager {
} }
} }
// Create a new file
async createFile(name: string): Promise<boolean> { async createFile(name: string): Promise<boolean> {
const size: number = await getProjectSize(this.sandboxId) const size: number = await getProjectSize(this.sandboxId)
if (size > 200 * 1024 * 1024) { if (size > 200 * 1024 * 1024) {
@ -360,11 +372,13 @@ export class FileManager {
return true return true
} }
// Create a new folder
async createFolder(name: string): Promise<void> { async createFolder(name: string): Promise<void> {
const id = `projects/${this.sandboxId}/${name}` const id = `projects/${this.sandboxId}/${name}`
await this.sandbox.files.makeDir(path.posix.join(this.dirName, id)) await this.sandbox.files.makeDir(path.posix.join(this.dirName, id))
} }
// Rename a file
async renameFile(fileId: string, newName: string): Promise<void> { async renameFile(fileId: string, newName: string): Promise<void> {
const fileData = this.sandboxFiles.fileData.find((f) => f.id === fileId) const fileData = this.sandboxFiles.fileData.find((f) => f.id === fileId)
const file = this.sandboxFiles.files.find((f) => f.id === fileId) const file = this.sandboxFiles.files.find((f) => f.id === fileId)
@ -381,6 +395,7 @@ export class FileManager {
file.id = newFileId file.id = newFileId
} }
// Delete a file
async deleteFile(fileId: string): Promise<(TFolder | TFile)[]> { async deleteFile(fileId: string): Promise<(TFolder | TFile)[]> {
const file = this.sandboxFiles.fileData.find((f) => f.id === fileId) const file = this.sandboxFiles.fileData.find((f) => f.id === fileId)
if (!file) return this.sandboxFiles.files if (!file) return this.sandboxFiles.files
@ -396,6 +411,7 @@ export class FileManager {
return newFiles.files return newFiles.files
} }
// Delete a folder
async deleteFolder(folderId: string): Promise<(TFolder | TFile)[]> { async deleteFolder(folderId: string): Promise<(TFolder | TFile)[]> {
const files = await getFolder(folderId) const files = await getFolder(folderId)
@ -413,6 +429,7 @@ export class FileManager {
return newFiles.files return newFiles.files
} }
// Close all file watchers
async closeWatchers() { async closeWatchers() {
await Promise.all( await Promise.all(
this.fileWatchers.map(async (handle: WatchHandle) => { this.fileWatchers.map(async (handle: WatchHandle) => {

View File

@ -1,5 +1,6 @@
import { Client } from "ssh2" import { Client } from "ssh2"
// Interface defining the configuration for SSH connection
export interface SSHConfig { export interface SSHConfig {
host: string host: string
port?: number port?: number
@ -7,25 +8,29 @@ export interface SSHConfig {
privateKey: Buffer privateKey: Buffer
} }
// Class to handle SSH connections and communicate with a Unix socket
export class SSHSocketClient { export class SSHSocketClient {
private conn: Client private conn: Client
private config: SSHConfig private config: SSHConfig
private socketPath: string private socketPath: string
private isConnected: boolean = false private isConnected: boolean = false
// Constructor initializes the SSH client and sets up configuration
constructor(config: SSHConfig, socketPath: string) { constructor(config: SSHConfig, socketPath: string) {
this.conn = new Client() this.conn = new Client()
this.config = { ...config, port: 22 } this.config = { ...config, port: 22 } // Default port to 22 if not provided
this.socketPath = socketPath this.socketPath = socketPath
this.setupTerminationHandlers() this.setupTerminationHandlers()
} }
// Set up handlers for graceful termination
private setupTerminationHandlers() { private setupTerminationHandlers() {
process.on("SIGINT", this.closeConnection.bind(this)) process.on("SIGINT", this.closeConnection.bind(this))
process.on("SIGTERM", this.closeConnection.bind(this)) process.on("SIGTERM", this.closeConnection.bind(this))
} }
// Method to close the SSH connection
private closeConnection() { private closeConnection() {
console.log("Closing SSH connection...") console.log("Closing SSH connection...")
this.conn.end() this.conn.end()
@ -33,6 +38,7 @@ export class SSHSocketClient {
process.exit(0) process.exit(0)
} }
// Method to establish the SSH connection
connect(): Promise<void> { connect(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.conn this.conn
@ -54,6 +60,7 @@ export class SSHSocketClient {
}) })
} }
// Method to send data through the SSH connection to the Unix socket
sendData(data: string): Promise<string> { sendData(data: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.isConnected) { if (!this.isConnected) {
@ -61,6 +68,7 @@ export class SSHSocketClient {
return return
} }
// Use netcat to send data to the Unix socket
this.conn.exec( this.conn.exec(
`echo "${data}" | nc -U ${this.socketPath}`, `echo "${data}" | nc -U ${this.socketPath}`,
(err, stream) => { (err, stream) => {

View File

@ -20,12 +20,14 @@ import { TerminalManager } from "./TerminalManager"
import { User } from "./types" import { User } from "./types"
import { LockManager } from "./utils" import { LockManager } from "./utils"
// Handle uncaught exceptions
process.on("uncaughtException", (error) => { process.on("uncaughtException", (error) => {
console.error("Uncaught Exception:", error) console.error("Uncaught Exception:", error)
// Do not exit the process // Do not exit the process
// You can add additional logging or recovery logic here // You can add additional logging or recovery logic here
}) })
// Handle unhandled promise rejections
process.on("unhandledRejection", (reason, promise) => { process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled Rejection at:", promise, "reason:", reason) console.error("Unhandled Rejection at:", promise, "reason:", reason)
// Do not exit the process // Do not exit the process
@ -35,8 +37,10 @@ process.on("unhandledRejection", (reason, promise) => {
// The amount of time in ms that a container will stay alive without a hearbeat. // The amount of time in ms that a container will stay alive without a hearbeat.
const CONTAINER_TIMEOUT = 120_000 const CONTAINER_TIMEOUT = 120_000
// Load environment variables
dotenv.config() dotenv.config()
// Initialize Express app and create HTTP server
const app: Express = express() const app: Express = express()
const port = process.env.PORT || 4000 const port = process.env.PORT || 4000
app.use(cors()) app.use(cors())
@ -47,10 +51,12 @@ const io = new Server(httpServer, {
}, },
}) })
// Check if the sandbox owner is connected
function isOwnerConnected(sandboxId: string): boolean { function isOwnerConnected(sandboxId: string): boolean {
return (connections[sandboxId] ?? 0) > 0 return (connections[sandboxId] ?? 0) > 0
} }
// Extract port number from a string
function extractPortNumber(inputString: string): number | null { function extractPortNumber(inputString: string): number | null {
const cleanedString = inputString.replace(/\x1B\[[0-9;]*m/g, "") const cleanedString = inputString.replace(/\x1B\[[0-9;]*m/g, "")
const regex = /http:\/\/localhost:(\d+)/ const regex = /http:\/\/localhost:(\d+)/
@ -58,12 +64,15 @@ function extractPortNumber(inputString: string): number | null {
return match ? parseInt(match[1]) : null return match ? parseInt(match[1]) : null
} }
// Initialize containers and managers
const containers: Record<string, Sandbox> = {} const containers: Record<string, Sandbox> = {}
const connections: Record<string, number> = {} const connections: Record<string, number> = {}
const fileManagers: Record<string, FileManager> = {} const fileManagers: Record<string, FileManager> = {}
const terminalManagers: Record<string, TerminalManager> = {} const terminalManagers: Record<string, TerminalManager> = {}
// Middleware for socket authentication
io.use(async (socket, next) => { io.use(async (socket, next) => {
// Define the schema for handshake query validation
const handshakeSchema = z.object({ const handshakeSchema = z.object({
userId: z.string(), userId: z.string(),
sandboxId: z.string(), sandboxId: z.string(),
@ -74,12 +83,14 @@ io.use(async (socket, next) => {
const q = socket.handshake.query const q = socket.handshake.query
const parseQuery = handshakeSchema.safeParse(q) const parseQuery = handshakeSchema.safeParse(q)
// Check if the query is valid according to the schema
if (!parseQuery.success) { if (!parseQuery.success) {
next(new Error("Invalid request.")) next(new Error("Invalid request."))
return return
} }
const { sandboxId, userId } = parseQuery.data const { sandboxId, userId } = parseQuery.data
// Fetch user data from the database
const dbUser = await fetch( const dbUser = await fetch(
`${process.env.DATABASE_WORKER_URL}/api/user?id=${userId}`, `${process.env.DATABASE_WORKER_URL}/api/user?id=${userId}`,
{ {
@ -90,32 +101,39 @@ io.use(async (socket, next) => {
) )
const dbUserJSON = (await dbUser.json()) as User const dbUserJSON = (await dbUser.json()) as User
// Check if user data was retrieved successfully
if (!dbUserJSON) { if (!dbUserJSON) {
next(new Error("DB error.")) next(new Error("DB error."))
return return
} }
// Check if the user owns the sandbox or has shared access
const sandbox = dbUserJSON.sandbox.find((s) => s.id === sandboxId) const sandbox = dbUserJSON.sandbox.find((s) => s.id === sandboxId)
const sharedSandboxes = dbUserJSON.usersToSandboxes.find( const sharedSandboxes = dbUserJSON.usersToSandboxes.find(
(uts) => uts.sandboxId === sandboxId (uts) => uts.sandboxId === sandboxId
) )
// If user doesn't own or have shared access to the sandbox, deny access
if (!sandbox && !sharedSandboxes) { if (!sandbox && !sharedSandboxes) {
next(new Error("Invalid credentials.")) next(new Error("Invalid credentials."))
return return
} }
// Set socket data with user information
socket.data = { socket.data = {
userId, userId,
sandboxId: sandboxId, sandboxId: sandboxId,
isOwner: sandbox !== undefined, isOwner: sandbox !== undefined,
} }
// Allow the connection
next() next()
}) })
// Initialize lock manager
const lockManager = new LockManager() const lockManager = new LockManager()
// Check for required environment variables
if (!process.env.DOKKU_HOST) if (!process.env.DOKKU_HOST)
console.error("Environment variable DOKKU_HOST is not defined") console.error("Environment variable DOKKU_HOST is not defined")
if (!process.env.DOKKU_USERNAME) if (!process.env.DOKKU_USERNAME)
@ -123,6 +141,7 @@ if (!process.env.DOKKU_USERNAME)
if (!process.env.DOKKU_KEY) if (!process.env.DOKKU_KEY)
console.error("Environment variable DOKKU_KEY is not defined") console.error("Environment variable DOKKU_KEY is not defined")
// Initialize Dokku client
const client = const client =
process.env.DOKKU_HOST && process.env.DOKKU_KEY && process.env.DOKKU_USERNAME process.env.DOKKU_HOST && process.env.DOKKU_KEY && process.env.DOKKU_USERNAME
? new DokkuClient({ ? new DokkuClient({
@ -133,6 +152,7 @@ const client =
: null : null
client?.connect() client?.connect()
// Initialize Git client used to deploy Dokku apps
const git = const git =
process.env.DOKKU_HOST && process.env.DOKKU_KEY process.env.DOKKU_HOST && process.env.DOKKU_KEY
? new SecureGitClient( ? new SecureGitClient(
@ -141,6 +161,7 @@ const git =
) )
: null : null
// Handle socket connections
io.on("connection", async (socket) => { io.on("connection", async (socket) => {
try { try {
const data = socket.data as { const data = socket.data as {
@ -149,6 +170,7 @@ io.on("connection", async (socket) => {
isOwner: boolean isOwner: boolean
} }
// Handle connection based on user type (owner or not)
if (data.isOwner) { if (data.isOwner) {
connections[data.sandboxId] = (connections[data.sandboxId] ?? 0) + 1 connections[data.sandboxId] = (connections[data.sandboxId] ?? 0) + 1
} else { } else {
@ -158,6 +180,7 @@ io.on("connection", async (socket) => {
} }
} }
// Create or retrieve container
const createdContainer = await lockManager.acquireLock( const createdContainer = await lockManager.acquireLock(
data.sandboxId, data.sandboxId,
async () => { async () => {
@ -180,10 +203,12 @@ io.on("connection", async (socket) => {
} }
) )
// Function to send loaded event
const sendLoadedEvent = (files: SandboxFiles) => { const sendLoadedEvent = (files: SandboxFiles) => {
socket.emit("loaded", files.files) socket.emit("loaded", files.files)
} }
// Initialize file and terminal managers if container was created
if (createdContainer) { if (createdContainer) {
fileManagers[data.sandboxId] = new FileManager( fileManagers[data.sandboxId] = new FileManager(
data.sandboxId, data.sandboxId,
@ -203,6 +228,7 @@ io.on("connection", async (socket) => {
// Load file list from the file manager into the editor // Load file list from the file manager into the editor
sendLoadedEvent(fileManager.sandboxFiles) sendLoadedEvent(fileManager.sandboxFiles)
// Handle various socket events (heartbeat, file operations, terminal operations, etc.)
socket.on("heartbeat", async () => { socket.on("heartbeat", async () => {
try { try {
// This keeps the container alive for another CONTAINER_TIMEOUT seconds. // This keeps the container alive for another CONTAINER_TIMEOUT seconds.
@ -214,6 +240,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to get file content
socket.on("getFile", async (fileId: string, callback) => { socket.on("getFile", async (fileId: string, callback) => {
try { try {
const fileContent = await fileManager.getFile(fileId) const fileContent = await fileManager.getFile(fileId)
@ -224,6 +251,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to get folder contents
socket.on("getFolder", async (folderId: string, callback) => { socket.on("getFolder", async (folderId: string, callback) => {
try { try {
const files = await fileManager.getFolder(folderId) const files = await fileManager.getFolder(folderId)
@ -234,6 +262,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to save file
socket.on("saveFile", async (fileId: string, body: string) => { socket.on("saveFile", async (fileId: string, body: string) => {
try { try {
await saveFileRL.consume(data.userId, 1) await saveFileRL.consume(data.userId, 1)
@ -244,6 +273,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to move file
socket.on( socket.on(
"moveFile", "moveFile",
async (fileId: string, folderId: string, callback) => { async (fileId: string, folderId: string, callback) => {
@ -263,6 +293,7 @@ io.on("connection", async (socket) => {
message?: string message?: string
} }
// Handle request to list apps
socket.on( socket.on(
"list", "list",
async (callback: (response: CallbackResponse) => void) => { async (callback: (response: CallbackResponse) => void) => {
@ -283,6 +314,7 @@ io.on("connection", async (socket) => {
} }
) )
// Handle request to deploy project
socket.on( socket.on(
"deploy", "deploy",
async (callback: (response: CallbackResponse) => void) => { async (callback: (response: CallbackResponse) => void) => {
@ -313,6 +345,7 @@ io.on("connection", async (socket) => {
} }
) )
// Handle request to create a new file
socket.on("createFile", async (name: string, callback) => { socket.on("createFile", async (name: string, callback) => {
try { try {
await createFileRL.consume(data.userId, 1) await createFileRL.consume(data.userId, 1)
@ -324,6 +357,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to create a new folder
socket.on("createFolder", async (name: string, callback) => { socket.on("createFolder", async (name: string, callback) => {
try { try {
await createFolderRL.consume(data.userId, 1) await createFolderRL.consume(data.userId, 1)
@ -335,6 +369,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to rename a file
socket.on("renameFile", async (fileId: string, newName: string) => { socket.on("renameFile", async (fileId: string, newName: string) => {
try { try {
await renameFileRL.consume(data.userId, 1) await renameFileRL.consume(data.userId, 1)
@ -345,6 +380,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to delete a file
socket.on("deleteFile", async (fileId: string, callback) => { socket.on("deleteFile", async (fileId: string, callback) => {
try { try {
await deleteFileRL.consume(data.userId, 1) await deleteFileRL.consume(data.userId, 1)
@ -356,6 +392,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to delete a folder
socket.on("deleteFolder", async (folderId: string, callback) => { socket.on("deleteFolder", async (folderId: string, callback) => {
try { try {
const newFiles = await fileManager.deleteFolder(folderId) const newFiles = await fileManager.deleteFolder(folderId)
@ -366,6 +403,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to create a new terminal
socket.on("createTerminal", async (id: string, callback) => { socket.on("createTerminal", async (id: string, callback) => {
try { try {
await lockManager.acquireLock(data.sandboxId, async () => { await lockManager.acquireLock(data.sandboxId, async () => {
@ -387,6 +425,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to resize terminal
socket.on( socket.on(
"resizeTerminal", "resizeTerminal",
(dimensions: { cols: number; rows: number }) => { (dimensions: { cols: number; rows: number }) => {
@ -399,6 +438,7 @@ io.on("connection", async (socket) => {
} }
) )
// Handle terminal input data
socket.on("terminalData", async (id: string, data: string) => { socket.on("terminalData", async (id: string, data: string) => {
try { try {
await terminalManager.sendTerminalData(id, data) await terminalManager.sendTerminalData(id, data)
@ -408,6 +448,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to close terminal
socket.on("closeTerminal", async (id: string, callback) => { socket.on("closeTerminal", async (id: string, callback) => {
try { try {
await terminalManager.closeTerminal(id) await terminalManager.closeTerminal(id)
@ -418,6 +459,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Handle request to generate code
socket.on( socket.on(
"generateCode", "generateCode",
async ( async (
@ -442,7 +484,7 @@ io.on("connection", async (socket) => {
} }
) )
// Generate code from cloudflare workers AI // Generate code from Cloudflare Workers AI
const generateCodePromise = fetch( const generateCodePromise = fetch(
`${process.env.AI_WORKER_URL}/api?fileName=${encodeURIComponent( `${process.env.AI_WORKER_URL}/api?fileName=${encodeURIComponent(
fileName fileName
@ -472,6 +514,7 @@ io.on("connection", async (socket) => {
} }
) )
// Handle socket disconnection
socket.on("disconnect", async () => { socket.on("disconnect", async () => {
try { try {
if (data.isOwner) { if (data.isOwner) {
@ -498,6 +541,7 @@ io.on("connection", async (socket) => {
} }
}) })
// Start the server
httpServer.listen(port, () => { httpServer.listen(port, () => {
console.log(`Server running on port ${port}`) console.log(`Server running on port ${port}`)
}) })