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, viewsRemoteAccessPlatform.Windows.Application: view models and client-side orchestration servicesRemoteAccessPlatform.Windows.Contracts: service abstractionsRemoteAccessPlatform.Windows.Models: DTOs and persisted settings modelsRemoteAccessPlatform.Windows.Transport: HTTP and WebSocket transportsRemoteAccessPlatform.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, defaulttrue - Client environment:
environment, defaultdevelopment; useproductionorprodfor production trust behavior - Direct data-plane connect timeout:
direct_data_plane_connect_timeout_ms, default750 - Direct data-plane color mode:
direct_data_plane_color_mode, defaultfull_color; smoke can setgrayscalewhen 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 withtls_trust_mode=platform_ca - Smoke-only direct TLS override:
allow_insecure_direct_data_plane_tls_for_smoke, defaultfalse
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_v1ortraffic_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=trueor usestls_trust_modeofpublic_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 againstdirect_data_plane_platform_ca_bundlewith 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=truebypasses certificate validation only in non-production and only for smoke-only direct candidates.- When direct candidate metadata advertises
render_transport=binary_v1orbinary_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 requestsdirect_data_plane_color_mode; unsupported modes fall back tofull_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_overcloses 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_keyvalues through the same localization helper and fall back to backendfallback_messageif 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 compat raw strings.
- WebSocket envelopes may now include
eventalongside the existingtypeandpayload. BackendApiClientpreserves compatibility with compat"error": "..."responses, but prefers the structured form when available.SessionWindowViewModelresolves websocket event messages bymessage_keyfirst and only then falls back tofallback_message.- Runtime smoke now proves both paths against the live backend:
message_keyresolution 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.resxfirst. - Keep English as the source language during development.
- When introducing a new backend user-facing error or websocket event, define a stable
codeandmessage_key. - Do not bind UI directly to raw backend English messages when a structured
message_keyis 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\ToClientdrop-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-order2baseline. - 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_atand 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:
SessionWindowcreation, 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.