Files
rdp-proxy/clients/windows/README.md
T
2026-05-12 21:02:29 +03:00

12 KiB

Windows Access Client

This project is the first native end-user Access Client for the platform. It sits on top of the proven backend/session/worker foundation and now includes a real RDP session window with direct worker data-plane support.

Current scope

  • WPF desktop shell in clients/windows/src/RemoteAccessPlatform.Windows.App
  • application services for auth, organizations, resources, sessions, and gateway attach flows
  • secure local token storage via DPAPI for MVP
  • organization selection persisted locally
  • remote-desktop-first shell: the selected server/session surface is primary, while organization, server catalog, and active sessions stay in compact controls or collapsible side panels
  • resource list, active session list, and session window
  • direct worker WSS data-plane integration with backend gateway fallback
  • binary render receive path for direct worker WSS
  • keyboard/mouse input forwarding
  • text clipboard actions
  • client-to-server file upload actions
  • WebSocket state updates and takeover handling

Solution layout

  • RemoteAccessPlatform.Windows.App: WPF shell, composition root, windows, views
  • RemoteAccessPlatform.Windows.Application: view models and client-side orchestration services
  • RemoteAccessPlatform.Windows.Contracts: service abstractions
  • RemoteAccessPlatform.Windows.Models: DTOs and persisted settings models
  • RemoteAccessPlatform.Windows.Transport: HTTP and WebSocket transports
  • RemoteAccessPlatform.Windows.Settings: DPAPI token storage and local settings persistence

Build assumptions

  • Current repository toolchain is .NET SDK 10.0.101
  • The solution file is RemoteAccessPlatform.Windows.slnx
  • WPF target framework is net10.0-windows

Build

dotnet build clients/windows/RemoteAccessPlatform.Windows.slnx

Run

dotnet run --project clients/windows/src/RemoteAccessPlatform.Windows.App/RemoteAccessPlatform.Windows.App.csproj

For a live desktop smoke pass against an already running backend/worker:

pwsh -Sta -ExecutionPolicy Bypass -File scripts/windows-smoke/desktop-smoke.ps1

For the worker-death/stale-lease verification variant against the current smoke host:

pwsh -Sta -ExecutionPolicy Bypass -File scripts/windows-smoke/desktop-smoke.ps1 `
  -VerifyWorkerDeath `
  -RemoteDockerHost 192.168.200.61 `
  -RemoteDockerUser test `
  -RemoteDockerPassword <ssh-password>

The same worker-death command also covers input-fidelity regression checks. It proves:

  • focused session-surface lifecycle in the real WPF window
  • keyboard input forwarding through the gateway to the worker
  • mouse input forwarding through the gateway to the worker
  • attach, detach, reattach, takeover, and worker-death handling without breaking the proven session lifecycle

Backend endpoints

Default endpoints are defined in appsettings.json.

  • API: http://192.168.200.61:8080/api/v1
  • WebSocket gateway: ws://192.168.200.61:8080/api/v1/gateway/ws
  • Direct data-plane preference: prefer_direct_data_plane, default true
  • Client environment: environment, default development; use production or prod for production trust behavior
  • Direct data-plane connect timeout: direct_data_plane_connect_timeout_ms, default 750
  • Direct data-plane color mode: direct_data_plane_color_mode, default full_color; smoke can set grayscale when the worker candidate advertises it
  • Direct data-plane platform CA bundle: direct_data_plane_platform_ca_bundle, optional app-local PEM/DER CA bundle used only for direct worker WSS candidates with tls_trust_mode=platform_ca
  • Smoke-only direct TLS override: allow_insecure_direct_data_plane_tls_for_smoke, default false

The project default test backend runs on Docker host 192.168.200.61 through Docker context test-ubuntu / SSH alias docker-test.

MVP behavior

  • Login stores access and refresh tokens in DPAPI-protected local storage
  • Refresh rotation is handled by the auth service and transport retry path
  • Organization selection is restored per user from local settings
  • Resource and session actions rely on backend business rules; the client does not reimplement org or session policies
  • Session windows connect to the existing gateway using backend-issued attach tokens
  • When a session response includes a direct worker WSS data-plane candidate, the client uses it only if the candidate metadata explicitly marks it data-capable (runtime_transport=json_v1 or traffic_ready=true); otherwise it stays on the backend gateway with no user-visible delay
  • In production client environment, direct worker WSS is used only when the candidate is marked production_trusted=true or uses tls_trust_mode of public_ca/platform_ca. Smoke-only or untrusted direct candidates are skipped and backend gateway fallback is used.
  • For tls_trust_mode=platform_ca, the client may validate the worker certificate against direct_data_plane_platform_ca_bundle with normal hostname/SAN validation still enforced. This override is scoped only to the direct worker WSS connection and is never applied to backend API/gateway traffic.
  • allow_insecure_direct_data_plane_tls_for_smoke=true bypasses certificate validation only in non-production and only for smoke-only direct candidates.
  • When direct candidate metadata advertises render_transport=binary_v1 or binary_render=true, the client requests binary direct render frames and feeds them into the same latest-frame presenter path
  • When direct candidate metadata advertises supported_color_modes, the client requests direct_data_plane_color_mode; unsupported modes fall back to full_color
  • Backend gateway fallback continues to use JSON/base64 render frames for debug/fallback compatibility
  • Direct worker WSS attempts are bounded by a short timeout and automatically fall back to the backend gateway
  • session.taken_over closes the current transport and updates the window status

Localization Foundation

  • User-facing UI strings now live in Strings.resx.
  • A placeholder Strings.ru.resx exists only to keep the structure ready for future translations.
  • XAML uses strongly named static accessors from Strings.cs.
  • View models resolve backend message_key values through the same localization helper and fall back to backend fallback_message if a resource key is not available.
  • The client should never display raw backend internal text directly as the primary UI contract.

Structured Messaging

  • Backend HTTP errors are parsed as structured envelopes instead of legacy raw strings.
  • WebSocket envelopes may now include event alongside the existing type and payload.
  • BackendApiClient preserves compatibility with legacy "error": "..." responses, but prefers the structured form when available.
  • SessionWindowViewModel resolves websocket event messages by message_key first and only then falls back to fallback_message.
  • Runtime smoke now proves both paths against the live backend: message_key resolution for invalid-login errors and fallback rendering for websocket state events whose keys are intentionally not present in client resources.

Developer Rules

  • Add every new user-visible client string to Strings.resx first.
  • Keep English as the source language during development.
  • When introducing a new backend user-facing error or websocket event, define a stable code and message_key.
  • Do not bind UI directly to raw backend English messages when a structured message_key is available.

Known limitations

  • RDP rendering is usable but still has small redraw artifacts that must be hardened in the next RDP visual correctness pass.
  • Advanced graphics modes, codecs, tiles, GPU acceleration, fullscreen polish, audio, printer, webcam, and multi-monitor are not implemented.
  • Clipboard is text-only.
  • File upload is accepted client-to-server. Server-to-client download has a runtime-proven core data path and lifecycle blocking through the restricted RAP_Transfers\ToClient drop-zone model; manual Windows-client UI proof is still pending before full Stage 5.2 acceptance.
  • Restricted drive visibility is accepted with the current rap-rdp-worker:rdp-p1-region-order2 baseline.
  • Linux and mobile clients are not implemented.
  • Mesh, node-agent updater runtime, VPN/IP tunnel runtime, and identity sync runtime are not implemented here.

Desktop smoke status

  • proven against a live backend: login, organization switching, org-scoped resource refresh, and token refresh after forced short access-token expiry
  • proven against the live localization-ready messaging model: invalid-login UI error rendering, structured websocket event rendering, fallback websocket state message rendering, takeover event messaging, and worker-death failure messaging
  • proven backend/runtime bug fixed during smoke: auth device upsert now casts trusted_at and other timestamps explicitly in backend/internal/modules/auth/postgres_store.go
  • proven client runtime bugs fixed during smoke:
    • empty organization/resource/session views now initialize correctly via code-behind constructors
    • dynamic status/session text controls no longer override automation-visible text with static automation names
  • proven from the desktop client in Stage 2: focused session-surface lifecycle, keyboard forwarding, and mouse forwarding against the live backend/worker
  • proven from the desktop client: SessionWindow creation, first render, DataContext binding, gateway connect, attach, detach, reattach, and takeover open path against the live backend
  • proven via desktop smoke plus client diagnostics: takeover reuses the same session id and does not create a new desktop session shell for a different remote session
  • proven backend fix for desktop failure semantics: backend/internal/modules/sessiongateway/module.go now distinguishes terminal session states from controller takeover instead of treating missing live binding as session.taken_over
  • proven client hardening for gateway-close fallback: SessionWindowViewModel.cs now refreshes authoritative session state after abrupt gateway closure
  • proven end-to-end in the current smoke harness: worker-death desktop handling now reaches the failed session state in the real client UI
  • current smoke harness for the remaining path is in scripts/windows-smoke/desktop-smoke.ps1

Clipboard Text MVP

The session window exposes explicit text-only clipboard actions. The client does not install global clipboard hooks and does not watch the OS clipboard in the background. Clipboard text is read or written only when the user invokes the session-window clipboard buttons inside an active session context.

Blocked clipboard transfers are surfaced through localized session event-log messages. The client treats backend message_key as the localization key and falls back to the backend English fallback_message only when the key is not present locally.

The client never sends images, HTML, RTF, binary blobs, or file-like clipboard payloads. Only Unicode text is read from or written to the local clipboard.