Skip to content

feat: WebSocket transport for VPN-free remote daemon access#579

Open
hard2forgetme wants to merge 1 commit intoDimillian:mainfrom
hard2forgetme:feat/websocket-transport
Open

feat: WebSocket transport for VPN-free remote daemon access#579
hard2forgetme wants to merge 1 commit intoDimillian:mainfrom
hard2forgetme:feat/websocket-transport

Conversation

@hard2forgetme
Copy link

Summary

Adds WebSocket as an alternative transport alongside TCP for the remote daemon protocol. The daemon auto-detects WebSocket (HTTP Upgrade) vs raw TCP on the same listen port by peeking at the first bytes of each connection — no config changes, no extra ports.

This unlocks remote access through any HTTP reverse proxy (Cloudflare Tunnel, nginx, Caddy, etc.), removing the need for Tailscale/VPN for cross-network iOS connectivity.

What changed

Daemon (server-side):

  • New ws_transport.rs — handles WebSocket connections using tokio-tungstenite (already a dependency)
  • Accept loop peeks first 3 bytes: GET → WebSocket upgrade, otherwise → raw TCP
  • Same auth, same RPC dispatch, same event forwarding — just different framing

Client (app-side):

  • New ws_transport.rs implementing RemoteTransport trait
  • resolve_transport_config auto-detects ws:///wss:// URLs
  • Added WebSocket variant to RemoteBackendProvider and RemoteTransportConfig

Settings UI:

  • New "Cloudflare Tunnel" helper section alongside the existing Tailscale helper
  • Shows the cloudflared tunnel command and wss:// usage instructions

Scripts:

  • scripts/codexmonitor-tunnel.sh — manages daemon lifecycle + Cloudflare Quick Tunnel with auto-generated auth tokens

How it works

The JSON-RPC protocol is completely unchanged — each WebSocket text message = one JSON-RPC line. Auth works identically. The only difference is framing: WebSocket messages instead of newline-delimited TCP.

iPhone → wss://abc.trycloudflare.com → Cloudflare Edge → localhost:4732 → daemon (auto-detects WS)

No new dependencies

tokio-tungstenite and futures-util were already in Cargo.toml. Zero new crates.

Test plan

  • cargo check passes (verified)
  • tsc --noEmit passes (verified)
  • Daemon starts and accepts TCP connections (backward compat)
  • Daemon accepts WebSocket connections on same port
  • cloudflared tunnel --url http://127.0.0.1:4732 proxies WebSocket correctly
  • iOS app connects via wss:// URL through Cloudflare Tunnel
  • Settings UI shows Cloudflare Tunnel helper section on desktop
  • Auth token validation works over WebSocket
  • Events (app-server-event, terminal-output) stream correctly over WebSocket

Add WebSocket as an alternative transport alongside TCP for the remote
daemon protocol. The daemon auto-detects WebSocket (HTTP Upgrade) vs
raw TCP on the same port by peeking at the first bytes of each connection.

This unlocks remote access through HTTP reverse proxies like Cloudflare
Tunnel, removing the need for VPN/Tailscale for cross-network connectivity.

Changes:
- Daemon: auto-detect WebSocket vs TCP on same listen port via byte peek
- Client: new WebSocketTransport implementing RemoteTransport trait
- Client: auto-detect ws:///wss:// URLs in resolve_transport_config
- Types: add WebSocket variant to RemoteBackendProvider
- Settings UI: add Cloudflare Tunnel helper section alongside Tailscale
- Scripts: add codexmonitor-tunnel.sh for daemon + CF tunnel management

The JSON-RPC protocol is unchanged — only the framing differs (WebSocket
text messages vs newline-delimited TCP). Auth works identically.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0b502ad11

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +439 to +440
if (!remoteHostDraft.startsWith("wss://") && !remoteHostDraft.startsWith("ws://")) {
onSetRemoteHostDraft(`wss://${remoteHostDraft}`);

Choose a reason for hiding this comment

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

P1 Badge Accept WebSocket URLs before prepending wss:// here

This button turns a Cloudflare hostname into wss://…, but validateRemoteHost() in src/features/settings/hooks/useSettingsServerSection.ts:118-126 still only accepts bare host:port values. As soon as the field blurs, onCommitRemoteHost() rejects the new value, so the Settings UI cannot actually save the WebSocket endpoint that this feature needs (desktop remote mode and the mobile connect test are both blocked unless the user edits settings outside the UI).

Useful? React with 👍 / 👎.

Comment on lines +317 to +318
if [[ -n "$url" ]]; then
echo " 3. Host: ${url#https://}:443"

Choose a reason for hiding this comment

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

P1 Badge Print the tunnel host as a WebSocket URL, not raw host:443

When a tunnel is active, these instructions tell users to enter <trycloudflare-host>:443. In src-tauri/src/remote_backend/mod.rs:243-266, that value is still resolved as the TCP transport because only ws:///wss:// hosts (or the explicit provider) select WebSocket mode. Since this script created the tunnel with cloudflared tunnel --url http://127.0.0.1:4732, following the printed steps will try raw TCP against an HTTP/WebSocket endpoint and fail to connect.

Useful? React with 👍 / 👎.

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.

1 participant