summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDave Dennis <[email protected]>2026-02-04 14:22:50 -0600
committerGitHub <[email protected]>2026-02-04 14:22:50 -0600
commit912098928a2c77537a632aee841daf95c6971e04 (patch)
treee157d88c0998efaf9b1be0df69b5458b6db2a2d4
parent222bddc41a945aec8f18536bda09c0e596748bbd (diff)
downloadopencode-912098928a2c77537a632aee841daf95c6971e04.tar.gz
opencode-912098928a2c77537a632aee841daf95c6971e04.zip
fix(app): derive terminal WebSocket URL from browser origin instead o… (#12178)
-rw-r--r--packages/app/src/components/terminal-url.test.ts36
-rw-r--r--packages/app/src/components/terminal-url.ts10
-rw-r--r--packages/app/src/components/terminal.tsx4
3 files changed, 48 insertions, 2 deletions
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 000000000..c07e3fa95
--- /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 000000000..c2c9fb53c
--- /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 4ad79ee82..563418d60 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