summaryrefslogtreecommitdiffhomepage
path: root/packages/app
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-05-03 14:23:29 -0400
committerGitHub <[email protected]>2026-05-03 18:23:29 +0000
commit387220f368ca3a31d94b4be3937d9d825ebd888c (patch)
treeb9e1c49c993618c5137bb1016124f1019cb78e2f /packages/app
parentadb7cb1037d24aa18021133b5993fa81869d8ba0 (diff)
downloadopencode-387220f368ca3a31d94b4be3937d9d825ebd888c.tar.gz
opencode-387220f368ca3a31d94b4be3937d9d825ebd888c.zip
fix(server): support desktop PTY websockets with HttpApi (#25598)
Diffstat (limited to 'packages/app')
-rw-r--r--packages/app/src/components/terminal.tsx28
-rw-r--r--packages/app/src/utils/terminal-websocket-url.test.ts36
-rw-r--r--packages/app/src/utils/terminal-websocket-url.ts16
3 files changed, 58 insertions, 22 deletions
diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx
index ff5ff9dad..998936bc6 100644
--- a/packages/app/src/components/terminal.tsx
+++ b/packages/app/src/components/terminal.tsx
@@ -15,6 +15,7 @@ import { terminalFontFamily, useSettings } from "@/context/settings"
import type { LocalPTY } from "@/context/terminal"
import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters"
import { terminalWriter } from "@/utils/terminal-writer"
+import { terminalWebSocketURL } from "@/utils/terminal-websocket-url"
const TOGGLE_TERMINAL_ID = "terminal.toggle"
const DEFAULT_TOGGLE_TERMINAL_KEYBIND = "ctrl+`"
@@ -67,13 +68,6 @@ const debugTerminal = (...values: unknown[]) => {
console.debug("[terminal]", ...values)
}
-const errorName = (err: unknown) => {
- if (!err || typeof err !== "object") return
- if (!("name" in err)) return
- const errorName = err.name
- return typeof errorName === "string" ? errorName : undefined
-}
-
const useTerminalUiBindings = (input: {
container: HTMLDivElement
term: Term
@@ -478,10 +472,9 @@ export const Terminal = (props: TerminalProps) => {
const gone = () =>
client.pty
- .get({ ptyID: id })
- .then(() => false)
+ .get({ ptyID: id }, { throwOnError: false })
+ .then((result) => result.response.status === 404)
.catch((err) => {
- if (errorName(err) === "NotFoundError") return true
debugTerminal("failed to inspect terminal session", err)
return false
})
@@ -509,18 +502,9 @@ export const Terminal = (props: TerminalProps) => {
if (disposed) return
drop?.()
- const next = new URL(url + `/pty/${id}/connect`)
- next.searchParams.set("directory", directory)
- next.searchParams.set("cursor", String(seek))
- next.protocol = next.protocol === "https:" ? "wss:" : "ws:"
- if (!sameOrigin && password) {
- next.searchParams.set("auth_token", btoa(`${username}:${password}`))
- // For same-origin requests, let the browser reuse the page's existing auth.
- next.username = username
- next.password = password
- }
-
- const socket = new WebSocket(next)
+ const socket = new WebSocket(
+ terminalWebSocketURL({ url, id, directory, cursor: seek, sameOrigin, username, password }),
+ )
socket.binaryType = "arraybuffer"
ws = socket
diff --git a/packages/app/src/utils/terminal-websocket-url.test.ts b/packages/app/src/utils/terminal-websocket-url.test.ts
new file mode 100644
index 000000000..c85863abd
--- /dev/null
+++ b/packages/app/src/utils/terminal-websocket-url.test.ts
@@ -0,0 +1,36 @@
+import { describe, expect, test } from "bun:test"
+import { terminalWebSocketURL } from "./terminal-websocket-url"
+
+describe("terminalWebSocketURL", () => {
+ test("uses query auth without embedding credentials in websocket URL", () => {
+ const url = terminalWebSocketURL({
+ url: "http://127.0.0.1:49365",
+ id: "pty_test",
+ directory: "/tmp/project",
+ cursor: 0,
+ sameOrigin: false,
+ username: "opencode",
+ password: "secret",
+ })
+
+ expect(url.protocol).toBe("ws:")
+ expect(url.username).toBe("")
+ expect(url.password).toBe("")
+ expect(url.searchParams.get("auth_token")).toBe(btoa("opencode:secret"))
+ })
+
+ test("omits query auth for same-origin websocket URL", () => {
+ const url = terminalWebSocketURL({
+ url: "https://app.example.test",
+ id: "pty_test",
+ directory: "/tmp/project",
+ cursor: 10,
+ sameOrigin: true,
+ username: "opencode",
+ password: "secret",
+ })
+
+ expect(url.protocol).toBe("wss:")
+ expect(url.searchParams.has("auth_token")).toBe(false)
+ })
+})
diff --git a/packages/app/src/utils/terminal-websocket-url.ts b/packages/app/src/utils/terminal-websocket-url.ts
new file mode 100644
index 000000000..146df16b7
--- /dev/null
+++ b/packages/app/src/utils/terminal-websocket-url.ts
@@ -0,0 +1,16 @@
+export function terminalWebSocketURL(input: {
+ url: string
+ id: string
+ directory: string
+ cursor: number
+ sameOrigin: boolean
+ username: string
+ password?: string
+}) {
+ const next = new URL(`${input.url}/pty/${input.id}/connect`)
+ next.searchParams.set("directory", input.directory)
+ next.searchParams.set("cursor", String(input.cursor))
+ next.protocol = next.protocol === "https:" ? "wss:" : "ws:"
+ if (!input.sameOrigin && input.password) next.searchParams.set("auth_token", btoa(`${input.username}:${input.password}`))
+ return next
+}