Skip to content

feat: smart LAN/tunnel URL switching for remote connections#179

Merged
PureWeen merged 3 commits intomainfrom
feat/smart-network-url-switching
Feb 27, 2026
Merged

feat: smart LAN/tunnel URL switching for remote connections#179
PureWeen merged 3 commits intomainfrom
feat/smart-network-url-switching

Conversation

@jfversluis
Copy link
Collaborator

Problem

When connecting a mobile device to a desktop PolyPilot server, users must choose between a LAN address (fast, only works on same network) or a DevTunnel URL (works everywhere, slower). If the user scans a LAN QR code and then leaves the house, the connection breaks with no fallback.

Solution

The app now stores both URLs and automatically picks the best one:

How it works

  1. QR codes encode both URLs when both sharing modes are active (DevTunnel + Direct Connection)
  2. At connect time, the app probes the LAN address first (2-second HTTP timeout):
    • ✅ LAN reachable → use fast local connection
    • ❌ LAN unreachable → fall back to tunnel
    • 📱 On cellular (no WiFi) → skip LAN probe entirely, use tunnel
  3. On reconnect, re-resolves which URL to use — network may have changed
  4. Connectivity monitoring (iOS/Android) — debounced MAUI Connectivity.ConnectivityChanged events detect WiFi↔cellular transitions

New fields

Field Purpose
ConnectionSettings.LanUrl LAN server address (e.g., http://192.168.1.5:4322)
ConnectionSettings.LanToken Server password for LAN auth

QR code format (backward compatible)

{
  "url": "https://xxx.devtunnels.ms",
  "token": "tunnel-jwt",
  "lanUrl": "http://192.168.1.5:4322",
  "lanToken": "server-password"
}

Old QR codes without lanUrl continue to work — single-URL mode, no probing overhead.

Edge cases handled

Scenario Behavior
Only LAN, no tunnel Uses LAN directly, no probe
Only tunnel, no LAN Uses tunnel directly, no probe
LAN IP changed 2s probe fails → tunnel fallback
WiFi → cellular Reconnect re-resolves to tunnel
Cellular → WiFi Reconnect probes LAN first

Files changed (9 files)

  • ConnectionSettings.cs — LanUrl/LanToken properties
  • WsBridgeClient.cs — ConnectSmartAsync, ResolveUrlAsync, ProbeLanAsync, IsCellularOnly
  • IWsBridgeClient.cs — new interface methods
  • CopilotService.Bridge.cs — ToWebSocketUrl helper, smart connect routing, connectivity monitor
  • CopilotService.cs — accept LanUrl-only configs
  • Dashboard.razor — parse/save LanUrl from QR scan
  • Settings.razor — QR generation includes both URLs, validation accepts LanUrl
  • TestStubs.cs — stub updates
  • SmartUrlResolutionTests.cs25 new tests

Tests

All 1169 tests pass (25 new + 1144 existing). 1 pre-existing locale-dependent failure unrelated.

@PureWeen PureWeen force-pushed the feat/smart-network-url-switching branch from 2364276 to 5e29bef Compare February 26, 2026 02:38
jfversluis and others added 3 commits February 26, 2026 19:21
When mobile connects to a desktop server, the app now stores both URLs
(tunnel and LAN) and automatically picks the best one:

- LAN first: On WiFi, probes the LAN address with a 2s HTTP timeout.
  If reachable, uses the fast local connection.
- Tunnel fallback: If LAN probe fails or device is on cellular, uses
  the DevTunnel/ngrok URL.
- Smart reconnect: On each reconnect attempt, re-resolves which URL
  to use based on current network state.

Implementation:
- ConnectionSettings: new LanUrl/LanToken fields
- WsBridgeClient: ConnectSmartAsync with LAN probe + ResolveUrlAsync
- QR codes: encode both URLs when both sharing modes are active
- QR scanning: parse lanUrl/lanToken with backward compatibility
- Connectivity monitor (iOS/Android): debounced network change events
  via MAUI Connectivity API
- CopilotService: accepts LanUrl-only configs (no tunnel required)

Bug fixes (code review):
- StopConnectivityMonitoring: use Interlocked.Exchange to atomically
  clear _connectivityDebounce, preventing race with OnConnectivityChanged
- OnConnectivityChanged: call _bridgeClient.Stop() on WiFi loss while
  using LAN, triggering immediate reconnect via tunnel instead of
  waiting for TCP keepalive timeout (~11 min)
- ProbeLanAsync: use shared static HttpClient to avoid socket exhaustion
  from per-call instances under sustained reconnect failures

Includes 25 new tests covering URL resolution, LAN probe, QR parsing,
settings serialization, and backward compatibility.

Co-authored-by: Gerald Versluis <gerald.versluis@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stop() cancels the CTS, which blocks the auto-reconnect guard in
WiFi was lost while connected via LAN, the bridge would permanently
disconnect with no recovery path.

Add AbortForReconnect() to IWsBridgeClient / WsBridgeClient that calls
_ws.Abort() without touching the CTS. The resulting WebSocketException
causes ReceiveLoopAsync to exit with a live CTS, which triggers the
auto-reconnect loop. The loop calls ResolveUrlAsync, which re-probes
LAN (fails, no WiFi) and falls back to tunnel.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…, QR backward compat

- StartConnectivityMonitoring: call StopConnectivityMonitoring() first to prevent
  double-registration of ConnectivityChanged handler across reconnects
- OnConnectivityChanged: add reverse case (gained WiFi while on tunnel → switch to LAN)
- GenerateDirectQrCode: include 'url'+'token' fallback for old apps that don't understand
  lanUrl (backward compat when no tunnel is running)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen force-pushed the feat/smart-network-url-switching branch from 12494c9 to 0575b18 Compare February 27, 2026 01:24
@PureWeen PureWeen merged commit b00dc11 into main Feb 27, 2026
@PureWeen PureWeen deleted the feat/smart-network-url-switching branch February 27, 2026 03:22
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