Repository navigation
feat(webui): serve a Mini App page through its own loopback proxy - #264
Closed
Fectivnfy112357 wants to merge 3 commits into
Closed
Fectivnfy112357 wants to merge 3 commits into
Fectivnfy112357 wants to merge 3 commits into
Conversation
First slice of #255. A Mini App page refers to its own origin by absolute path — the scaffold serves `/dashboard`, and a real page also fetches `/api/...` and its assets — so the proxy forwards paths unchanged rather than hiding them behind a prefix that would have to be patched back by intercepting every fetch and absolute URL in the document. `startMiniAppProxy` puts one listener on loopback per running Mini App, on a port derived per plugin. That stability is required, not cosmetic: the page's `localStorage` and `IndexedDB` are keyed by its origin, so a port that moved between runs would silently discard the page's own state. Requests are forwarded with the Mini App's credential injected, and the credential never appears in a response. Upgrades are forwarded under the same rule, and a closed peer tears down its pair — a half-closed pipe would hold the upstream server open forever, which is how this showed up in the test teardown. Server-only by placement: the proxy takes the upstream origin and the credential as input and returns `viewUrl`, which is what a client frames. The client never assembles a Mini App origin. It lives in `packages/webui/src/server/` because ADR 0020 puts the proxy in the WebUI service and keeps everything that touches a real origin on the server side of the facade; the server layer may import `shared` and `runtime-port` and nothing else. Proven against a real upstream server: the path is forwarded unchanged, the response body reaches the caller, the credential is injected outbound and absent from the response, an upgrade is forwarded with the credential, and the derived port is stable per plugin and inside the loopback range. Still to do for #255: the document rewrite that injects the page bridge (the bridge itself is #260), the per-session and per-presentation view URL, and wiring the proxy into the open operation so it starts and stops with the Mini App. Verified: pnpm typecheck:webui (server, client and test); the focused suite (4 cases); pnpm check:source after the inventory write. pnpm check:webui-dependency still fails on a pre-existing violation in WorkspacePanels.tsx — `document.fonts.ready` in a bindings file, present at the base commit 9713c4c and untouched by this branch; the new module adds none.
…e bridge Second slice of #255. The page is framed by this client on a different origin, so a page must not be able to refuse being framed. The proxy drops `X-Frame-Options` from every response it forwards and removes the `frame-ancestors` directive from a page's own CSP, leaving its other directives alone — the Mini App's origin is separate from the client's, so the rest of its policy governs a document that cannot reach the client anyway. Nothing is added: no header the page did not ask for, no CSP of the proxy's own. The document is the one response the proxy rewrites. The page bridge has to be in the document before the page's own scripts run, so the proxy injects a script tag for it before `</head>`, after the opening `<body>` tag when a page has no head, or ahead of the document when it has neither; the body it sends is not the body it received, so it restates `Content-Length` and drops the upstream's `Transfer-Encoding` with it. The bridge script is served by the proxy itself at its own path and never forwarded upstream, which keeps the seam in one place: this change owns which document, where the tag goes and what headers survive, while the bridge body arrives with #260. ADR 0020's open question about framing is settled by these rules and removed from the open list; the bridge's method-level error mapping is the only one left. Proven: the bridge is injected before `</head>`, before `<body>` on a headless document, `X-Frame-Options` is gone, `frame-ancestors` is gone while `default-src` and `script-src` survive, the bridge path is served locally without reaching the upstream, and paths and bodies of ordinary responses are untouched. Verified: pnpm typecheck:webui (server, client and test); the focused suite (8 cases); pnpm check:source after the inventory write. pnpm check:webui-dependency still fails on the pre-existing WorkspacePanels.tsx violation described in the previous commit.
Closes #255's stability criterion with evidence. The page's `localStorage` and `IndexedDB` are keyed by its origin, so a port that moved between runs would silently discard the page's own state. The derived port is a pure function of the plugin id, and this asserts the property end to end: start on the derived port, close, start again from a fresh derivation, and the view URL is identical. Verified: pnpm typecheck:webui; the focused suite (9 cases).
Fectivnfy112357
force-pushed
the
feat/miniapp-ownership-probe
branch
from
October 26, 2026 08:33
e3cc29e to
fb56967
Compare
Fectivnfy112357
force-pushed
the
feat/miniapp-proxy
branch
from
October 26, 2026 08:33
1f00969 to
44af7f5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #255. Stacked on #263 → #262 → #261; merge top-down and GitHub retargets each PR as its base
merges.
Change
A Mini App page is served through one loopback listener per Mini App, with the credential and the page
bridge injected, and without enabling the capability for this client's runtime owner.
path, so paths are forwarded unchanged rather than hidden behind a prefix that would have to be patched
back by intercepting every
fetch,XMLHttpRequestand absolute URL. The derived port is deliberate:the page's
localStorageandIndexedDBare keyed by its origin, so a port that moved between runswould silently discard the page's own state.
viewUrl, and from everything the client sees.pipe holds the upstream server open forever.
X-Frame-Optionsis dropped,frame-ancestorsis removed from the page's own CSP with its other directives preserved, and a scripttag for the bridge goes before
</head>(or after<body>, or ahead of the document). The bridgescript is served by the proxy at its own path and never forwarded upstream.
Content-Lengthisrestated and the upstream's
Transfer-Encodingdropped with it.Server-only by placement: the proxy takes the upstream origin and the credential as input and returns
viewUrl. It lives inpackages/webui/src/server/, whose allowed imports aresharedandruntime-port, so everything touching a real origin stays on the server side of the facade.Validation
pnpm typecheck:webui— server, client and test projects, passedpnpm check:sourceafternode scripts/source-inventory.mjs --write— 5459 files, passedpackages/webui/test/unit/miniapp-proxy.test.ts— 9 cases against a real upstreamhttp.Server:unchanged path, response body, credential injected outbound and absent from the response, upgrade
forwarded with the credential, bridge injected before
</head>/ after<body>/ ahead of thedocument,
X-Frame-Optionsandframe-ancestorsgone with other CSP directives kept, bridge servedlocally, view URL identical across a restart
pnpm check:webui-dependencyfails, and not on this branch:document.fonts.readyused fromclient/features/workspace/ui/WorkspacePanels.tsx:489,534, already failing at the base commit9713c4cf; the new module contributes zero violations. Flagged rather than fixed because it is abindings-layer decision in another file and outside this ticket.
pnpm verifyin full; browser, package and release steps are left to CINot in this PR
Starting a Mini App with explicit options and producing its view URL — moved to #256 with your
agreement. The only legal route to a Mini App's origin and credential is a
runtime-portcapabilitymember, which
packages/webui/AGENTS.mdsays failspnpm typecheck:webuiwithout a binding; its consumeris #256's open operation.
Publication and contribution checks
release/public-source.json; new tests are declared intest/vitest-suites.jsonwhere applicable.Maintainer handoff
Publication scope or license changes: none.
Shared-source port: not needed — the proxy is written against the shipped behaviour described in ADR
0020, not copied from internal code.