Commit 6488100
feat: make opencode web embeddable in iframes at a subpath
Enables a reverse-proxy / embedding host (e.g. a parent dashboard) to
serve opencode web under an arbitrary URL prefix — the built SPA, the
server's CSP, and the SDK's default-server-URL resolution all have to
cooperate for a same-origin iframe mount to actually work. Four
orthogonal changes:
1. packages/app/vite.config.ts — `base: './'`
Emits relative asset paths in the built index.html and chunk
imports (e.g. `./assets/foo.js` instead of `/assets/foo.js`), so a
document loaded under `/some/deep/iframe-prefix/` can resolve its
own asset URLs against that prefix rather than against the origin
root. No effect on direct-serve at `/`; every Vite-base subpath
story just works from one source build.
2. packages/opencode/src/server/routes/ui.ts — add `'unsafe-eval'`
to the embedded-UI CSP's script-src directive
Zod 4 (pinned via the workspace catalog at `[email protected]`) JIT-compiles
its validators at schema-definition time via `new Function(...)` —
that's where v4's speed advantage over v3 comes from. Every
`z.object({...})` in the bundle therefore needs `'unsafe-eval'`;
the existing CSP only granted `'wasm-unsafe-eval'`, which Firefox
correctly distinguishes from the broader keyword Zod actually
needs. Confirmed by tracing: the first Function-construct trap at
page load fires from `packages/app/src/context/global-sdk.tsx:12`
(`z.object({ name: z.literal('AbortError') })`) and the stack
walks through `zod/v4/core/{core,schemas,util}.js`, which is the
v4 codegen pipeline. This relaxation is therefore not optional
while we depend on zod 4 — the alternatives are switching the
`app` package to `zod/mini` (no-codegen entry, restricted API) or
downgrading to zod 3, both larger refactors.
3. packages/app/src/entry.tsx — `getCurrentUrl` honors the
localStorage defaultServerUrl override
`getCurrentUrl()` was previously hard-wired to `location.origin`
(in production) for the initial `servers[0]` entry, while
`getDefaultUrl()` would return the localStorage-set
`defaultServerUrl` when present for the `defaultServer` key. The
two disagreed: the server-context's `current` server resolves via
`allServers().find(key === state.active) ?? allServers()[0]`, so
if `state.active` pointed at the localStorage URL but that URL
wasn't in `allServers()`, the code fell back to `allServers()[0]`
— i.e. `location.origin` — and control-plane requests like
`/global/config` and `/global/event` bypassed the override
entirely. Having `getCurrentUrl` also honor the localStorage
override keeps both entries aligned and makes the override
globally effective.
4. packages/opencode/src/server/routes/ui.ts — extend `connect-src`
to allow `https://opencode.ai`
`packages/app/src/context/highlights.tsx` fetches the release
changelog from `https://opencode.ai/changelog.json` to surface
release highlights to the user. Under the previous
`connect-src 'self' data:` the fetch was blocked once the SPA
ran from a non-opencode.ai origin (i.e. exactly the embed case).
Adding `https://opencode.ai` to `connect-src` lets the changelog
feature keep working in embedded deployments and is consistent
with the SPA's existing same-domain trust (the production
fallback proxy and the `location.hostname.includes('opencode.ai')`
logic in `entry.tsx` already treat that origin as canonical).
Together these let opencode web embed inside an iframe served from a
foreign origin / subpath: assets load, the SPA bundle executes, all
SDK calls (including control-plane routes) honor the configured
server URL, and the changelog fetch isn't blocked by CSP.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>1 parent df9e1d9 commit 6488100
3 files changed
Lines changed: 14 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
101 | 109 | | |
102 | 110 | | |
103 | 111 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
6 | 10 | | |
7 | 11 | | |
8 | 12 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
0 commit comments