diff --git a/packages/app/src/components/terminal-url.test.ts b/packages/app/src/components/terminal-url.test.ts new file mode 100644 index 000000000000..c07e3fa950ec --- /dev/null +++ b/packages/app/src/components/terminal-url.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from "bun:test" +import { ptySocketUrl } from "./terminal-url" + +describe("ptySocketUrl", () => { + test("uses browser host instead of sdk host", () => { + const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { + host: "192.168.1.50:4096", + protocol: "http:", + }) + expect(url.toString()).toBe("ws://192.168.1.50:4096/pty/pty_1/connect?directory=%2Frepo") + }) + + test("uses secure websocket on https", () => { + const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { + host: "opencode.local", + protocol: "https:", + }) + expect(url.toString()).toBe("wss://opencode.local/pty/pty_1/connect?directory=%2Frepo") + }) + + test("preserves browser port", () => { + const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { + host: "opencode.local:8443", + protocol: "https:", + }) + expect(url.toString()).toBe("wss://opencode.local:8443/pty/pty_1/connect?directory=%2Frepo") + }) + + test("handles slash base url", () => { + const url = ptySocketUrl("/", "pty_1", "/repo", { + host: "192.168.1.50:4096", + protocol: "http:", + }) + expect(url.toString()).toBe("ws://192.168.1.50:4096/pty/pty_1/connect?directory=%2Frepo") + }) +}) diff --git a/packages/app/src/components/terminal-url.ts b/packages/app/src/components/terminal-url.ts new file mode 100644 index 000000000000..c2c9fb53c516 --- /dev/null +++ b/packages/app/src/components/terminal-url.ts @@ -0,0 +1,10 @@ +export function ptySocketUrl(base: string, id: string, directory: string, origin: { host: string; protocol: string }) { + const root = `${origin.protocol}//${origin.host}` + const current = new URL(root) + const prefix = /^https?:\/\//.test(base) ? base : new URL(base || "/", root).toString() + const url = new URL(prefix.replace(/\/+$/, "") + `/pty/${id}/connect?directory=${encodeURIComponent(directory)}`) + url.hostname = current.hostname + url.port = current.port + url.protocol = origin.protocol === "https:" ? "wss:" : "ws:" + return url +} diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index 4ad79ee82bd8..563418d601b2 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -8,6 +8,7 @@ import { LocalPTY } from "@/context/terminal" import { resolveThemeVariant, useTheme, withAlpha, type HexColor } from "@opencode-ai/ui/theme" import { useLanguage } from "@/context/language" import { showToast } from "@opencode-ai/ui/toast" +import { ptySocketUrl } from "./terminal-url" export interface TerminalProps extends ComponentProps<"div"> { pty: LocalPTY @@ -163,8 +164,7 @@ export const Terminal = (props: TerminalProps) => { const once = { value: false } - const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`) - url.protocol = url.protocol === "https:" ? "wss:" : "ws:" + const url = ptySocketUrl(sdk.url, local.pty.id, sdk.directory, window.location) if (window.__OPENCODE__?.serverPassword) { url.username = "opencode" url.password = window.__OPENCODE__?.serverPassword