diff --git a/src/server/api/apps.ts b/src/server/api/apps.ts index 5c44124..30059af 100644 --- a/src/server/api/apps.ts +++ b/src/server/api/apps.ts @@ -16,10 +16,8 @@ const router = Hype.router() // BackendApp -> SharedApp function convert(app: BackendApp): SharedApp { - const clone = { ...app } - delete clone.proc - delete clone.logs - return clone + const { proc, logs, ...rest } = app + return { ...rest, pid: proc?.pid } } // SSE endpoint for real-time app state updates diff --git a/src/server/index.tsx b/src/server/index.tsx index 2bfde77..37cfd88 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -22,6 +22,35 @@ app.get('/tool/:tool', c => { return c.redirect(url) }) +// Tool API proxy: /api/tools/:tool/* -> proxy to tool port +app.all('/api/tools/:tool/:path{.+}', async c => { + const toolName = c.req.param('tool') + const tool = allApps().find(a => a.tool && a.name === toolName) + if (!tool || tool.state !== 'running' || !tool.port) { + return c.json({ error: `Tool "${toolName}" not found or not running` }, 404) + } + + const subPath = '/' + c.req.param('path') + + // Build target URL + const params = new URLSearchParams(c.req.query()).toString() + const targetUrl = params + ? `http://localhost:${tool.port}${subPath}?${params}` + : `http://localhost:${tool.port}${subPath}` + + // Proxy the request + const response = await fetch(targetUrl, { + method: c.req.method, + headers: c.req.raw.headers, + body: c.req.method !== 'GET' && c.req.method !== 'HEAD' ? c.req.raw.body : undefined, + }) + + return new Response(response.body, { + status: response.status, + headers: response.headers, + }) +}) + initApps() export default { diff --git a/src/shared/types.ts b/src/shared/types.ts index 0107bc0..457b5ef 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -23,6 +23,7 @@ export type App = { state: AppState icon: string error?: string + pid?: number port?: number started?: number logs?: LogLine[]