App frames: openExternal(url) and openResource(subject) - #1744
michielbdejong wants to merge 3 commits into
Conversation
Sandboxed app frames could not open links or send the host anywhere. - `store.openExternal(url)`: http(s) only, no credentials in the URL. The host shows a bar naming the destination host in full (punycode), with the whole link under it, and opens it only on the person's click, with `noopener,noreferrer`. The sandbox keeps no `allow-popups`. A second ask answers the first as cancelled. - `store.openResource(subject)`: navigates the host to `/app/show` for an Atomic resource identifier or http(s) resource URL, after loading it with the person's store; agents, commits, blobs, nodes and non-subjects are refused before anything loads. - Both ops are in the v1 view protocol and `@tomic/plugin` types; the generated client waits on the person without its 60 s deadline (also for `proxyConnect`). Closes #1734 Closes #1735 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pre-existing on this stack (from #1700); it blocked every commit here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
openResource navigated the host page as soon as the resource loaded. It
now draws a confirm bar like openExternal: the resource's title, its
subject under it, and Open / Cancel. Only the click navigates.
- One ask at a time across both ops: a second openExternal or
openResource resolves the pending one as `cancelled`.
- Refusals (not a resource, not readable) still happen before asking.
- OpenResourceResult is now `{ status: 'opened' | 'cancelled', subject }`.
- view-client.js: openResource waits on the person, no 60 s deadline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merge-risk triage (2026-09-28). This is a read-only review of this PR's own diff. The rule it applies: nothing that works today may break for existing users or clients, and new behaviour is added alongside the old. The stack is being rebased onto Verdict: SAFE (with 1545cab) Both ops only raise a consent bar in the host; a new tab or navigation happens only in the click handler. 🤖 Generated with Claude Code |
Closes #1734
Closes #1735
Stacked on #1733 (
claude/installation-connect).Two FrameBridge ops for sandboxed app frames, so apps can offer "Open in Notion / Google Calendar / GitHub / Clockify" and "Open data table/row" without a fallback.
store.openExternal(url)→{ status: 'opened' | 'cancelled' }https://bank.example@evil.example).window.open(url, '_blank', 'noopener,noreferrer'). The sandbox staysallow-scripts allow-modals, with noallow-popups.cancelled.store.openResource(subject)→{ status: 'opened' | 'cancelled', subject }atomic:/did:ad:, not an agent, commit, blob or node) or an http(s) resource URL. Anything else is refused before anything loads.openExternal: a bar above the frame, "This app wants to open <title>, leaving the app.", with the subject under it. Open / Cancel. Only the click on Open navigates, to/app/show?subject=….openExternaloropenResourcebefore the person answers resolves the first ascancelled.?subject=, never a same-origin path, so an app can't reach a host route like/app/dev-drive.Protocol, SDK, docs
openExternalandopenResourceare inViewOperation/isViewRequest.OpenExternalResultandOpenResourceResultare exported from@tomic/plugin.view-client.js: ops that wait on the person (openExternal,openResource, and nowproxyConnecttoo) have no 60 s deadline, so a person who takes a while doesn't get a "host did not answer" error.docs/src/plugins/custom-views.md: a new "App frames" section.Tests
helpers/extensions/externalLink.test.ts(new): scheme allow-list, credentials, length cap, punycode host,noopener,noreferrer.chunks/AppPage/hostStore.test.ts:openResourceaccepts readable resources, refuses unreadable ones, and refuses agents, commits, blobs, nodes and non-subjects without loading them.browser/plugin/src/viewProtocol.test.ts: the generated client sends both ops and waits on the person without a deadline.cancelled.window.opener === null.openResourcerefuses an agent and navigates to the app's table.Checked by hand after adding the openResource confirm
A throwaway Playwright script ran against a real server built from this branch:
{status: 'cancelled', subject}and stays on the app.openExternalask followed byopenResourcecancels the link ask and shows only the resource bar.Screenshots
The confirm bar, desktop (1280×720) and mobile (390×844). Shown to the developer in chat.
🤖 Generated with Claude Code