import { Observable } from "@noya-app/observable"; import { RPCManager } from "./rpcManager"; export type SandboxPhase = | "idle" | "preparing" | "uploading" | "installing" | "starting" | "running" | "error"; export type SandboxState = { status: SandboxPhase; sandboxId?: string; previewUrl?: string; devProcessId?: string; port?: number; installLogs: string; devLogs: string; lastUpdated: string; error?: string; }; const nowIso = () => new Date().toISOString(); export const createInitialSandboxState = (): SandboxState => ({ status: "idle", installLogs: "", devLogs: "", lastUpdated: nowIso(), }); export type ExecResult = { success: boolean; stdout: string; stderr: string; exitCode: number; }; export class SandboxManager { state$ = new Observable(createInitialSandboxState()); constructor(private rpcManager: RPCManager) {} private setState(next: SandboxState) { this.state$.set(next); } setFromServer = (next: SandboxState) => { this.setState(next); }; async exec(command: string, cwd?: string): Promise { const response = await this.rpcManager.requestRoute( "POST /api/sandbox/exec", { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ command, cwd }), } ); return this.rpcManager.getResponseBody(response) as ExecResult; } async listFiles(): Promise<{ files: Array<{ name: string; path: string; type: "file" | "directory" }>; }> { const response = await this.rpcManager.requestRoute( "GET /api/sandbox/files" ); return this.rpcManager.getResponseBody(response) as { files: Array<{ name: string; path: string; type: "file" | "directory" }>; }; } async exposePreview(): Promise<{ previewUrl?: string; error?: string }> { const response = await this.rpcManager.requestRoute( "POST /api/sandbox/preview", { headers: { "Content-Type": "application/json" } } ); return this.rpcManager.getResponseBody(response) as { previewUrl?: string; error?: string; }; } async getBuildFilesZip(): Promise<{ success: boolean; zip?: string; error?: string; }> { const response = await this.rpcManager.requestRoute( "GET /api/sandbox/build-files" ); return this.rpcManager.getResponseBody(response) as { success: boolean; zip?: string; error?: string; }; } }