Browse Source

fix(app): derive terminal WebSocket URL from browser origin instead o… (#12178)

Dave Dennis 3 weeks ago
parent
commit
912098928a

+ 36 - 0
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")
+  })
+})

+ 10 - 0
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
+}

+ 2 - 2
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 { resolveThemeVariant, useTheme, withAlpha, type HexColor } from "@opencode-ai/ui/theme"
 import { useLanguage } from "@/context/language"
 import { useLanguage } from "@/context/language"
 import { showToast } from "@opencode-ai/ui/toast"
 import { showToast } from "@opencode-ai/ui/toast"
+import { ptySocketUrl } from "./terminal-url"
 
 
 export interface TerminalProps extends ComponentProps<"div"> {
 export interface TerminalProps extends ComponentProps<"div"> {
   pty: LocalPTY
   pty: LocalPTY
@@ -163,8 +164,7 @@ export const Terminal = (props: TerminalProps) => {
 
 
       const once = { value: false }
       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) {
       if (window.__OPENCODE__?.serverPassword) {
         url.username = "opencode"
         url.username = "opencode"
         url.password = window.__OPENCODE__?.serverPassword
         url.password = window.__OPENCODE__?.serverPassword