Skip to content

feat: support portable connections for external viewers - #150

Merged
antfu merged 1 commit into
devframes:mainfrom
webfansplz:feat/devframe-connection-setup
Jul 31, 2026
Merged

feat: support portable connections for external viewers#150
antfu merged 1 commit into
devframes:mainfrom
webfansplz:feat/devframe-connection-setup

Conversation

@webfansplz

Copy link
Copy Markdown
Collaborator

Background

I am currently refactoring the Vite DevTools Web Extension so that it can reuse the same DevTools UI and Devframe integrations as the embedded and standalone clients.

The extension panel runs under a chrome-extension:// origin, while the inspected application, Vite development server, and individual DevTools iframe applications run under the development server origin.

This exposed a limitation in the existing connection model: it works well when the client can access a same-origin parent window, but an external viewer cannot reliably carry the complete connection into its own context or into cross-origin child frames.

Summary

This PR adds a portable, serializable Devframe connection model that can be passed between JavaScript realms and external viewers.

It introduces:

  • setupDevframeConnection() to prepare connection metadata independently from RPC client creation
  • getDevframeConnection() to retrieve a previously prepared connection
  • A connection option for connectDevframe() / getDevframeRpcClient()
  • rpc.connection, exposing the complete active connection
  • DEVFRAME_CONNECTION_KEY as the shared cross-realm key
  • WebSocket URL resolution based on the metadata source URL
  • Synchronization of newly issued auth tokens back into rpc.connection

Motivation

The Web Extension should behave as another Devframe viewer, rather than implementing a separate connection and authentication system.

However, the previous connection setup implicitly depended on the current page:

  • Relative WebSocket paths were resolved using the viewer’s location.
  • Connection metadata and authentication state were primarily discovered through same-origin globals or parent windows.
  • An external viewer had no first-class value containing the metadata, its source URL, and the current auth token.
  • Cross-origin child frames could not inherit the extension panel’s connection.
  • After interactive authorization, consumers could not reliably obtain the newly issued token from the existing RPC client.

In practice, this caused Web Extension code to reconstruct connection details, interact with internal globals, and resolve WebSocket addresses itself. It also meant each child DevTools iframe could become unauthorized even when the extension host was already trusted.

Approach

A DevframeConnection now represents everything required to establish a client connection:

interface DevframeConnection {
  connectionMeta: ConnectionMeta
  metaBaseUrl: string
  authToken?: string
}

metaBaseUrl records the absolute URL from which __connection.json was loaded. Relative WebSocket paths and sidecar ports can therefore be resolved against the Devframe server instead of the external viewer’s origin.

A host can prepare the connection once:

const connection = await setupDevframeConnection({
  baseURL: '/__devframe/',
})

It can then pass that serializable value to another viewer:

const rpc = await connectDevframe({
  connection,
})

The resulting RPC client also exposes the active connection:

rpc.connection

When authorization returns a new token, rpc.connection is updated so the host can pass the trusted connection to child viewers without requiring another authorization step.

For Vite DevTools, this allows the extension panel to obtain the inspected application’s connection, connect as a standalone viewer, and pass that connection to DevTools iframe applications through the existing remote connection mechanism.

Compatibility

This is additive and does not change the existing connection flow.

Existing calls continue to work:

const rpc = await connectDevframe({
  baseURL: '/__devframe/',
})

The existing metadata and authentication globals remain synchronized for compatibility with current integrations. Explicit connectionMeta, baseURL, and authToken options are still supported.

Security

The connection may contain an authentication token, so it is intentionally represented as an explicit value. The consuming host decides which viewer receives it.

This PR does not automatically expose the connection to arbitrary cross-origin frames. In the Web Extension integration, Vite DevTools only forwards it to child applications served by the same Devframe server origin.

Copilot AI review requested due to automatic review settings July 31, 2026 04:26
@netlify

netlify Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit 526e06a
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a6c394d8178b700082c588f
😎 Deploy Preview https://deploy-preview-150--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

Adds a portable, serializable DevframeConnection model to decouple connection preparation from RPC client creation, enabling external viewers (e.g. browser extensions / cross-realm contexts) to carry full connection metadata (including meta source URL and auth token) across origins and frames.

Changes:

  • Introduces DevframeConnection, setupDevframeConnection(), and getDevframeConnection() and wires them into getDevframeRpcClient() / connectDevframe().
  • Exposes the active connection via rpc.connection and synchronizes newly issued auth tokens back into that connection.
  • Updates WebSocket URL resolution to anchor legacy numeric-port/host inference to the metadata origin (not the viewer origin), and documents the new flow.

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/snapshots/tsnapi/devframe/constants.snapshot.js Updates public API snapshot for newly exported constant.
tests/snapshots/tsnapi/devframe/constants.snapshot.d.ts Updates public API types snapshot for newly exported constant.
tests/snapshots/tsnapi/devframe/client.snapshot.js Updates public API snapshot for newly exported connection helpers.
tests/snapshots/tsnapi/devframe/client.snapshot.d.ts Updates public API types snapshot to include DevframeConnection, new options shape, and rpc.connection.
packages/devframe/src/constants.ts Adds DEVFRAME_CONNECTION_KEY constant for cross-realm connection sharing.
packages/devframe/src/client/rpc.ts Refactors client creation to use setupDevframeConnection() and exposes rpc.connection with token synchronization.
packages/devframe/src/client/rpc-ws.ts Anchors WS URL resolution (protocol/hostname) to the connection metadata base URL for external viewers.
packages/devframe/src/client/rpc-ws.test.ts Adds coverage for extension-style locations and metadata-anchored numeric port handling.
packages/devframe/src/client/connection.ts Implements DevframeConnection, getDevframeConnection(), and setupDevframeConnection() including meta-base URL tracking.
packages/devframe/src/client/connection.test.ts Adds tests for connection preparation, RPC client exposure, token precedence, and fallback base behavior.
packages/devframe/src/client/connection-storage.ts Centralizes cross-realm/global/localStorage storage and synchronization for connection + token.
docs/guide/client.md Documents external viewer connection sharing, rpc.connection, and the new connection option.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/devframe/src/client/connection.ts
Comment thread packages/devframe/src/client/connection.ts
@webfansplz
webfansplz marked this pull request as draft July 31, 2026 04:31
@webfansplz
webfansplz force-pushed the feat/devframe-connection-setup branch from 813bb48 to 1df20d0 Compare July 31, 2026 04:45
@webfansplz
webfansplz force-pushed the feat/devframe-connection-setup branch from 1df20d0 to 526e06a Compare July 31, 2026 04:51
@webfansplz
webfansplz marked this pull request as ready for review July 31, 2026 04:57
Copilot AI review requested due to automatic review settings July 31, 2026 04:57
@webfansplz
webfansplz requested a review from antfu July 31, 2026 04:59

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 11 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (2)

docs/guide/client.md:64

  • The text implies DEVFRAME_CONNECTION_KEY is directly readable, but it’s just the string key. To make cross-realm usage unambiguous, document that the prepared connection is stored on globalThis[DEVFRAME_CONNECTION_KEY] (and that this is what external viewers should read).
`getDevframeConnection()` returns the prepared connection in the current
window or an accessible parent window. Cross-realm viewers can read the
serializable value through `DEVFRAME_CONNECTION_KEY` from
`devframe/constants`.

docs/guide/client.md:83

  • The authToken option description is inaccurate: the code defaults to the locally persisted token (and may also use a token embedded in connection metadata), not a “human-readable id”.
| `authToken` | Override the auth token. Defaults to a locally-persisted human-readable id. |

Copilot AI review requested due to automatic review settings July 31, 2026 05:55

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.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

Copilot AI review requested due to automatic review settings July 31, 2026 05:57
@antfubot
antfubot force-pushed the feat/devframe-connection-setup branch from 8b4f51f to 526e06a Compare July 31, 2026 05:57

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.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@antfu
antfu merged commit e696af6 into devframes:main Jul 31, 2026
18 of 20 checks passed
@webfansplz
webfansplz deleted the feat/devframe-connection-setup branch July 31, 2026 07:05
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.

3 participants