diff --git a/web/src/api.ts b/web/src/api.ts index d788261..b3857fd 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -1,11 +1,4 @@ -import type { - ApiError, - ConnectInfo, - CreateSessionRequest, - DaemonInfo, - Session, - Uuid, -} from "./types.js"; +import type { ApiError, CreateSessionRequest, DaemonInfo, Session, Uuid } from "./types.js"; async function jsonOrError(res: Response): Promise { if (res.ok) { @@ -50,14 +43,6 @@ export async function killSession(id: Uuid): Promise { ); } -export async function getConnectInfo(id: Uuid): Promise { - return jsonOrError( - await fetch(`/api/sessions/${encodeURIComponent(id)}/connect-info`, { - credentials: "same-origin", - }), - ); -} - export function eventsWsUrl(): string { const proto = location.protocol === "https:" ? "wss:" : "ws:"; return `${proto}//${location.host}/api/events`; diff --git a/web/src/attach.ts b/web/src/attach.ts index bd12b66..53fd5fe 100644 --- a/web/src/attach.ts +++ b/web/src/attach.ts @@ -2,7 +2,6 @@ import { FitAddon } from "@xterm/addon-fit"; import { Terminal } from "@xterm/xterm"; import "@xterm/xterm/css/xterm.css"; -import { getConnectInfo } from "./api.js"; import type { ClientControl, Uuid } from "./types.js"; interface AttachHandle { @@ -13,16 +12,15 @@ function statusMessage(host: HTMLElement, kind: "info" | "error", text: string): host.innerHTML = `
${text}
`; } -function streamUrl(daemonUrl: string, sessionId: Uuid, jwt: string): string { - let base = daemonUrl; - // Allow operators to give an http(s) URL; convert to ws(s). - if (base.startsWith("http://")) base = "ws://" + base.slice("http://".length); - else if (base.startsWith("https://")) base = "wss://" + base.slice("https://".length); - // Trim trailing slash. - base = base.replace(/\/+$/, ""); - const encId = encodeURIComponent(sessionId); - const encJwt = encodeURIComponent(jwt); - return `${base}/sessions/${encId}/stream?token=${encJwt}`; +/** + * Same-origin broker URL. The browser loads from `maestro-web`, which + * reverse-proxies this WebSocket to the co-located daemon's + * `/api/attach/:id` broker endpoint. The daemon short-circuits to the local + * PTY when it owns the session; cross-daemon brokering lands with NAP-247. + */ +function brokerStreamUrl(sessionId: Uuid): string { + const proto = location.protocol === "https:" ? "wss:" : "ws:"; + return `${proto}//${location.host}/api/attach/${encodeURIComponent(sessionId)}`; } export async function renderAttach( @@ -51,16 +49,7 @@ export async function renderAttach( stateEl.dataset.state = kind; }; - statusMessage(body, "info", "Fetching connect info…"); - - let connect; - try { - connect = await getConnectInfo(sessionId); - } catch (e) { - setState("error", "error"); - statusMessage(body, "error", `Failed to get connect info: ${(e as Error).message}`); - return { dispose: () => {} }; - } + statusMessage(body, "info", "Connecting…"); body.innerHTML = ""; @@ -91,8 +80,7 @@ export async function renderAttach( }; tryFit(); - const wsUrl = streamUrl(connect.daemon_url, sessionId, connect.jwt); - const ws = new WebSocket(wsUrl); + const ws = new WebSocket(brokerStreamUrl(sessionId)); ws.binaryType = "arraybuffer"; const sendControl = (msg: ClientControl) => { diff --git a/web/src/types.ts b/web/src/types.ts index ae4e124..795f3b5 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -24,11 +24,6 @@ export interface DaemonInfo { daemon_url: string; } -export interface ConnectInfo { - daemon_url: string; - jwt: string; -} - export interface WorktreeSpec { base_repo: string; branch: string;