187 lines
12 KiB
Markdown
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.
|