Skip to content

feat: add external viewer connection utilities - #160

Open
webfansplz wants to merge 2 commits into
devframes:mainfrom
webfansplz:feat/external-viewer-portability
Open

feat: add external viewer connection utilities#160
webfansplz wants to merge 2 commits into
devframes:mainfrom
webfansplz:feat/external-viewer-portability

Conversation

@webfansplz

Copy link
Copy Markdown
Collaborator

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:

  • runs under its own browser-extension origin
  • connects to the Devframe server belonging to the inspected application
  • must resolve WebSocket endpoints, dock URLs, and dock icons relative to that server rather than the extension page
  • needs to reuse an existing trusted connection without duplicating Devframe's connection protocol downstream

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/hub as reusable APIs.

What changed

External viewer origin registration

This PR adds a token-protected WebSocket origin registry:

  • createWsOriginRegistry() creates a live origin allowlist
  • the registration token is published through connection metadata
  • registerDevframeViewerOrigin() registers an external viewer origin through the connection metadata endpoint
  • allowedOrigins now accepts a registry in addition to the existing array and false options
  • hosts can provide additional origin validation, such as allowing only browser-extension schemes

The 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/client now provides:

  • buildRemoteDevframeUrl() to attach an existing trusted connection to an external viewer URL
  • stripRemoteConnectionFromUrl() to remove the connection descriptor before displaying or copying the URL

The 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 from devframe/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:

  • origin registration is part of establishing a portable Devframe transport
  • remote connection descriptors are a Hub-level external viewer concern
  • dock resource resolution is required whenever a Hub renders docks outside the host application's origin

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.

  • Existing allowedOrigins arrays continue to work.
  • Setting allowedOrigins: false retains its existing behavior.
  • Existing remote dock URL formats and the fragment | query transport option are preserved.
  • Existing server-side remote docks now delegate to the shared implementation without changing their public API.

Security considerations

  • Viewer origins must present a server-generated registration token.
  • Registered origins are normalized and matched exactly.
  • Hosts can apply additional validation before accepting an origin.
  • WebSocket origin registration remains separate from RPC authentication.
  • Remote connection descriptors use URL fragments by default so authentication tokens are not included in HTTP requests.

The registration token is a bearer credential and should be treated accordingly. The security guide documents the origin registration flow and its intended usage.

Copilot AI lite review requested due to automatic review settings August 4, 2026 04:38
@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit a5d803d
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a716eb3df02270008f93eda
😎 Deploy Preview https://deploy-preview-160--devfra.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/hub/src/remote-url.ts
Comment thread packages/devframe/src/client/connection.ts
Comment thread docs/guide/security.md Outdated
Copilot AI review requested due to automatic review settings August 4, 2026 04:46
@webfansplz
webfansplz requested a review from antfu August 4, 2026 04:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 case rawHash has 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)}`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants