feat: add external viewer connection utilities - #160
Conversation
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Promotes external-viewer connection responsibilities (origin registration, remote URL construction, dock resource resolution, and shared WebSocket URL resolution) into devframe and @devframes/hub so browser-extension and cross-origin viewers can reuse the same connection metadata/protocol logic instead of reimplementing it per host.
Changes:
- Added a token-protected WebSocket origin registry (
createWsOriginRegistry) and a client-side bootstrap helper (registerDevframeViewerOrigin) wired through connection metadata. - Added reusable Hub client utilities for external viewers: building/stripping remote connection URLs and resolving dock iframe URLs + icons against the Devframe server that supplied the connection.
- Updated tests, API snapshots, and docs to cover the new external viewer flows.
Reviewed changes
Copilot reviewed 22 out of 27 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/snapshots/tsnapi/devframe/rpc/transports/ws-server.snapshot.js | Snapshot updated for new ws-server export. |
| tests/snapshots/tsnapi/devframe/rpc/transports/ws-server.snapshot.d.ts | Snapshot updated for new registry-related types/exports. |
| tests/snapshots/tsnapi/devframe/index.snapshot.d.ts | Snapshot updated for viewerOriginToken in connection metadata. |
| tests/snapshots/tsnapi/devframe/constants.snapshot.js | Snapshot updated for new query param constants. |
| tests/snapshots/tsnapi/devframe/constants.snapshot.d.ts | Snapshot updated for new query param constants. |
| tests/snapshots/tsnapi/devframe/client.snapshot.js | Snapshot updated for new client exports. |
| tests/snapshots/tsnapi/devframe/client.snapshot.d.ts | Snapshot updated for resolveWsUrl and related types. |
| tests/snapshots/tsnapi/@devframes/hub/client.snapshot.js | Snapshot updated for new Hub client exports/utilities. |
| tests/snapshots/tsnapi/@devframes/hub/client.snapshot.d.ts | Snapshot updated for new Hub client types/exports. |
| packages/hub/src/remote-url.ts | New shared implementation for encoding/stripping remote connection descriptors. |
| packages/hub/src/node/host-docks.ts | Server-side remote dock URL building delegates to shared remote URL encoder. |
| packages/hub/src/client/remote.ts | Adds buildRemoteDevframeUrl, re-exports strip helper, and continues parsing remote descriptors. |
| packages/hub/src/client/remote.test.ts | Adds tests for building/parsing/stripping remote connection URLs. |
| packages/hub/src/client/index.ts | Re-exports dock resource resolution helpers. |
| packages/hub/src/client/dock-resources.ts | Adds resolveDockUrl/resolveDockIcon for external viewer dock resource resolution. |
| packages/hub/src/client/dock-resources.test.ts | Adds unit tests for dock URL/icon resolution behavior. |
| packages/devframe/src/types/context.ts | Documents and adds viewerOriginToken to ConnectionMeta. |
| packages/devframe/src/rpc/transports/ws.test.ts | Adds coverage for viewer origin registry + client registration flow. |
| packages/devframe/src/rpc/transports/ws-server.ts | Implements createWsOriginRegistry and allows allowedOrigins to accept a registry. |
| packages/devframe/src/node/server.ts | Extends allowedOrigins type to accept the new registry. |
| packages/devframe/src/constants.ts | Adds bootstrap query param constants for viewer origin registration. |
| packages/devframe/src/client/index.ts | Exports resolveWsUrl and its location type. |
| packages/devframe/src/client/connection.ts | Adds registerDevframeViewerOrigin helper for external viewers. |
| packages/devframe/src/client/connection.test.ts | Adds tests for external viewer origin registration helper. |
| docs/guide/security.md | Documents external viewer origin registration flow. |
| docs/guide/hub.md | Documents dock URL/icon resolution helpers for external viewers. |
| docs/guide/client.md | Documents external viewer origin registration + external viewer remote URL helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 27 changed files in this pull request and generated no new comments.
Suppressed comments (1)
packages/hub/src/remote-url.ts:48
buildRemoteConnectionUrl()breaks hash-router URLs that have a route but no query string (e.g.https://viewer/#/inspect). In that caserawHashhas no?, so the code falls back to treating the fragment as an&-joined param list and produces#/inspect&devframe-remote-connection=..., which changes the route path and can prevent the viewer router from matching.
Handle #/route (no query) by appending ?${REMOTE_CONNECTION_KEY}=... instead of &... when the fragment looks like a hash-router route.
const query = setRemoteConnectionParam(rawHash.slice(routeQueryIdx + 1), param)
return `${beforeHash}#${route}${query}`
}
return `${beforeHash}#${setRemoteConnectionParam(rawHash, param)}`
Context
I am working on the Vite DevTools Web Extension, which renders Vite DevTools inside a browser extension panel.
Unlike an in-page or same-origin iframe viewer, the extension panel:
The initial implementation in Vite DevTools had to manually parse connection metadata, construct remote viewer URLs, maintain a mutable WebSocket origin allowlist, and resolve dock resources itself. Those responsibilities are not Vite-specific and would otherwise need to be reimplemented by every external viewer.
This PR promotes those capabilities into Devframe and
@devframes/hubas reusable APIs.What changed
External viewer origin registration
This PR adds a token-protected WebSocket origin registry:
createWsOriginRegistry()creates a live origin allowlistregisterDevframeViewerOrigin()registers an external viewer origin through the connection metadata endpointallowedOriginsnow accepts a registry in addition to the existing array andfalseoptionsThe registration token only authorizes an origin for the WebSocket handshake. It does not replace the existing RPC authentication token.
This lets an external viewer connect without requiring consumers to mutate a shared allowlist or disable WebSocket origin protection.
Portable remote connection URLs
@devframes/hub/clientnow provides:buildRemoteDevframeUrl()to attach an existing trusted connection to an external viewer URLstripRemoteConnectionFromUrl()to remove the connection descriptor before displaying or copying the URLThe connection descriptor continues to default to the URL fragment, keeping the authentication token out of HTTP requests and referrer headers.
The existing server-side remote dock implementation now shares the same internal URL encoding logic, so client- and server-generated descriptors follow one format.
Dock resource resolution
The Hub client now exposes:
resolveDockUrl()resolveDockIcon()These helpers resolve relative iframe URLs and URL-backed icons against the Devframe server that supplied the connection metadata. Symbolic icon names, absolute URLs, protocol-relative URLs, and data URLs remain unchanged.
This is necessary when the Hub UI is hosted by an external viewer whose own origin is unrelated to the server hosting the dock resources.
Shared WebSocket resolution
resolveWsUrl()is now exported fromdevframe/client, allowing Hub and other external viewers to use the same WebSocket endpoint resolution rules as the built-in Devframe client.Why this belongs upstream
These capabilities are not specific to Vite or browser extensions:
Keeping these APIs upstream avoids protocol duplication and prevents consumers from depending on Devframe globals or internal connection metadata details.
Compatibility
This PR is additive and does not introduce breaking changes.
allowedOriginsarrays continue to work.allowedOrigins: falseretains its existing behavior.fragment | querytransport option are preserved.Security considerations
The registration token is a bearer credential and should be treated accordingly. The security guide documents the origin registration flow and its intended usage.