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

187 lines
12 KiB
Markdown

# 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
```powershell
dotnet build clients/windows/RemoteAccessPlatform.Windows.slnx
```
## Run
```powershell
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:
```powershell
pwsh -Sta -ExecutionPolicy Bypass -File scripts/windows-smoke/desktop-smoke.ps1
```
For the worker-death/stale-lease verification variant against the current smoke host:
```powershell
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](/\\?\UNC\192.168.220.200\mst\codex\rdp-proxy\clients\windows\src\RemoteAccessPlatform.Windows.App\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](/\\?\UNC\192.168.220.200\mst\codex\rdp-proxy\clients\windows\src\RemoteAccessPlatform.Windows.Application\Resources\Strings.resx).
- A placeholder [Strings.ru.resx](/\\?\UNC\192.168.220.200\mst\codex\rdp-proxy\clients\windows\src\RemoteAccessPlatform.Windows.Application\Resources\Strings.ru.resx) exists only to keep the structure ready for future translations.
- XAML uses strongly named static accessors from [Strings.cs](/\\?\UNC\192.168.220.200\mst\codex\rdp-proxy\clients\windows\src\RemoteAccessPlatform.Windows.Application\Localization\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](/\\?\UNC\192.168.220.200\mst\codex\rdp-proxy\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](/\\?\UNC\192.168.220.200\mst\codex\rdp-proxy\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](/\\?\UNC\192.168.220.200\mst\codex\rdp-proxy\clients\windows\src\RemoteAccessPlatform.Windows.Application\ViewModels\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](/\\?\UNC\192.168.220.200\mst\codex\rdp-proxy\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.