mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-06 08:21:50 +00:00
fix(server): serve embedded UI from bunfs (#25632)
This commit is contained in:
parent
a9dc0fae3d
commit
6312c55d55
2 changed files with 60 additions and 14 deletions
|
|
@ -45,6 +45,31 @@ export function embeddedUI() {
|
|||
return embeddedUIPromise
|
||||
}
|
||||
|
||||
function notFound() {
|
||||
return HttpServerResponse.jsonUnsafe({ error: "Not Found" }, { status: 404 })
|
||||
}
|
||||
|
||||
function embeddedUIResponse(file: string, body: Uint8Array) {
|
||||
const mime = AppFileSystem.mimeType(file)
|
||||
const headers = new Headers({ "content-type": mime })
|
||||
if (mime.startsWith("text/html")) headers.set("content-security-policy", DEFAULT_CSP)
|
||||
return HttpServerResponse.raw(body, { headers })
|
||||
}
|
||||
|
||||
export function serveEmbeddedUIEffect(
|
||||
requestPath: string,
|
||||
fs: AppFileSystem.Interface,
|
||||
embeddedWebUI: Record<string, string>,
|
||||
) {
|
||||
const file = embeddedWebUI[requestPath.replace(/^\//, "")] ?? embeddedWebUI["index.html"] ?? null
|
||||
if (!file) return Effect.succeed(notFound())
|
||||
|
||||
return fs.readFile(file).pipe(
|
||||
Effect.map((body) => embeddedUIResponse(file, body)),
|
||||
Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(notFound())),
|
||||
)
|
||||
}
|
||||
|
||||
export function serveUIEffect(
|
||||
request: HttpServerRequest.HttpServerRequest,
|
||||
services: { fs: AppFileSystem.Interface; client: HttpClient.HttpClient },
|
||||
|
|
@ -53,19 +78,7 @@ export function serveUIEffect(
|
|||
const embeddedWebUI = yield* Effect.promise(() => embeddedUI())
|
||||
const path = new URL(request.url, "http://localhost").pathname
|
||||
|
||||
if (embeddedWebUI) {
|
||||
const match = embeddedWebUI[path.replace(/^\//, "")] ?? embeddedWebUI["index.html"] ?? null
|
||||
if (!match) return HttpServerResponse.jsonUnsafe({ error: "Not Found" }, { status: 404 })
|
||||
|
||||
if (yield* services.fs.existsSafe(match)) {
|
||||
const mime = AppFileSystem.mimeType(match)
|
||||
const headers = new Headers({ "content-type": mime })
|
||||
if (mime.startsWith("text/html")) headers.set("content-security-policy", DEFAULT_CSP)
|
||||
return HttpServerResponse.raw(yield* services.fs.readFile(match), { headers })
|
||||
}
|
||||
|
||||
return HttpServerResponse.jsonUnsafe({ error: "Not Found" }, { status: 404 })
|
||||
}
|
||||
if (embeddedWebUI) return yield* serveEmbeddedUIEffect(path, services.fs, embeddedWebUI)
|
||||
|
||||
const response = yield* services.client.execute(
|
||||
HttpClientRequest.make(request.method)(upstreamURL(path), {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
|||
import { ServerAuth } from "../../src/server/auth"
|
||||
import { authorizationRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/authorization"
|
||||
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
|
||||
import { serveUIEffect } from "../../src/server/shared/ui"
|
||||
import { serveEmbeddedUIEffect, serveUIEffect } from "../../src/server/shared/ui"
|
||||
import { Server } from "../../src/server/server"
|
||||
|
||||
void Log.init({ print: false })
|
||||
|
|
@ -184,6 +184,39 @@ describe("HttpApi UI fallback", () => {
|
|||
expect(await response.text()).toBe("console.log('ok')")
|
||||
})
|
||||
|
||||
test("serves embedded UI assets when Bun can read them but access reports missing", async () => {
|
||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||
let readPath: string | undefined
|
||||
|
||||
const response = await Effect.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* AppFileSystem.Service
|
||||
return yield* serveEmbeddedUIEffect(
|
||||
"/assets/app.js",
|
||||
{
|
||||
...fs,
|
||||
existsSafe: () => Effect.die("embedded UI should not rely on filesystem access checks"),
|
||||
readFile: (path) => {
|
||||
readPath = path
|
||||
return path === "/$bunfs/root/assets/app.js"
|
||||
? Effect.succeed(new TextEncoder().encode("console.log('embedded')"))
|
||||
: Effect.die(`unexpected embedded UI path: ${path}`)
|
||||
},
|
||||
},
|
||||
{ "assets/app.js": "/$bunfs/root/assets/app.js" },
|
||||
)
|
||||
}).pipe(
|
||||
Effect.provide(AppFileSystem.defaultLayer),
|
||||
Effect.map(HttpServerResponse.toWeb),
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(readPath).toBe("/$bunfs/root/assets/app.js")
|
||||
expect(response.headers.get("content-type")).toContain("text/javascript")
|
||||
expect(await response.text()).toBe("console.log('embedded')")
|
||||
})
|
||||
|
||||
test("keeps matched API routes ahead of the UI fallback", async () => {
|
||||
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue