Introduce the independent Rust + Flutter desktop client with CDAP/RustDesk session support, packaging via build.py, and tag-triggered Windows/Linux release artifacts. Align server CDAP desktop interop (view_only, desktop_end, capabilities), protocol docs, pre-release checklist, version bump rules, and WAN allowlist for /api/health and /api/server/pubkey. Thanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
6.8 KiB
CDAP Protocol Specification
Version 0.4.0 — BetterDesk Desktop desktop-session interoperability
The Go implementation in
betterdesk-server/cdapis the normative wire contract. This document describes the public subset used by native clients.
Transport
- WebSocket on port
21122, path/cdap - Subprotocol:
cdap-v1 - Text frames: JSON messages
- Binary frames: desktop media uses a 64-byte NUL-padded session ID followed by the encoded payload. Do not use the legacy 1-byte prefix for desktop frames.
Authentication
{
"type": "auth",
"device_id": "CDAP-6A9A5452",
"method": "api_key",
"token": "your-api-key-here",
"protocol_version": "0.3.0"
}
Server responds with:
{
"type": "auth_result",
"success": true,
"server_version": "3.0.0"
}
Auth Methods
| Method | Credentials field |
|---|---|
api_key |
API key string |
device_token |
Device-specific token |
user_password |
"username:password" |
Manifest Registration
After auth, the device sends a register message containing its manifest:
{
"type": "register",
"manifest": {
"manifest_version": "1.0",
"device": {
"name": "BetterDesk Desktop",
"type": "desktop",
"vendor": "BetterDesk"
},
"capabilities": [
"remote_desktop", "keyboard_input", "mouse_input",
"multi_monitor", "clipboard", "file_transfer", "audio"
],
"widgets": [
{
"id": "sys_cpu",
"type": "gauge",
"label": "CPU Usage",
"group": "System",
"unit": "%",
"min": 0,
"max": 100,
"danger": 90,
"warning": 70
},
{
"id": "sys_memory",
"type": "gauge",
"label": "Memory Usage",
"group": "System",
"unit": "%",
"min": 0,
"max": 100
},
{
"id": "reboot_btn",
"type": "button",
"label": "Reboot",
"group": "Actions",
"command": "system_reboot",
"confirm": true
}
],
"heartbeat_interval": 15
}
}
Message Types
Agent → Server
| Type | Description | Payload |
|---|---|---|
auth |
Authentication request | device_id, method, token or username + password |
register |
Device manifest + widgets | manifest: {...} |
heartbeat |
Periodic health check | metrics: { cpu, memory, disk }, widget_values: {...} |
state_update |
Single widget value change | widget_id, value |
bulk_update |
Multiple widget values | values: { widget_id: value, ... } |
command_response |
Command execution result | command_id, success, result, error |
terminal_output |
Terminal PTY output | session_id, data (base64) |
terminal_end |
Terminal session ended | session_id, exit_code |
file_list_response |
Directory listing | request_id, entries: [{ name, size, is_dir, modified }] |
file_read_response |
File content chunk | request_id, data (base64), offset, total |
file_write_response |
Write confirmation | request_id, success, bytes_written |
file_delete_response |
Delete confirmation | request_id, success |
clipboard_update |
Clipboard content from device | format, data |
audio_frame |
Audio data | codec, data (base64), timestamp, sequence |
audio_end |
Audio session ended | session_id |
cursor_update |
Cursor image data | format, width, height, hotspot_x, hotspot_y, data |
codec_answer |
Codec negotiation response | codec, parameters |
monitor_list |
Available monitors | monitors: [{ id, name, width, height, primary }] |
key_exchange |
Encryption key relay | public_key, algorithm |
alert |
Device-initiated alert | severity, message, source |
pong |
Keepalive response | — |
Server → Agent
| Type | Description | Payload |
|---|---|---|
auth_result |
Auth result | success, error, session_token? |
command |
Execute command | command_id, command, args |
terminal_start |
Start terminal session | session_id, shell, cols, rows |
terminal_input |
Terminal input data | session_id, data (base64) |
terminal_resize |
Resize terminal | session_id, cols, rows |
terminal_kill |
Kill terminal session | session_id |
file_list |
Request directory listing | request_id, path |
file_read |
Request file content | request_id, path, offset, length |
file_write |
Write file content | request_id, path, data (base64), offset |
file_delete |
Delete file/directory | request_id, path |
clipboard_set |
Set device clipboard | format, data |
screenshot_capture |
Request screenshot | request_id |
state_request |
Request current state | — |
config_update |
Push config changes | config: {...} |
alert_ack |
Acknowledge alert | alert_id |
codec_offer |
Codec negotiation offer | codecs: [...] |
monitor_select |
Select active monitor | monitor_id |
keyframe_request |
Request video keyframe | — |
quality_report |
Quality metrics from browser | bandwidth_kb, latency_ms, frame_loss, fps |
ping |
Keepalive request | — |
Desktop Session
desktop_start is sent to the authenticated device and includes
session_id, width, height, quality, fps, codecs, and optional
view_only. The device returns desktop_frame JSON messages with a base64
JPEG for compatibility clients, or the binary fast path described above.
The server forwards desktop_input, desktop_resize, clipboard_set,
file_*, and audio_input to the device. The device may return
clipboard_update, file_*_response, audio_frame, desktop_meta,
monitor_list, and desktop_end. File access must remain bounded to an
operator-approved root; a client must reject arbitrary host paths.
Widget Types
| Type | Fields | Description |
|---|---|---|
gauge |
min, max, unit, danger, warning |
Horizontal bar gauge |
toggle |
on_command, off_command |
On/off switch |
button |
command, confirm |
Action button |
led |
on_color, off_color |
Status LED indicator |
text |
— | Read-only text display |
slider |
min, max, step, command |
Adjustable slider |
select |
options, command |
Dropdown selector |
chart |
max_points |
Time-series bar chart |
Error Handling
Error messages use the error type:
{
"type": "error",
"code": "AUTH_FAILED",
"message": "Invalid API key"
}
| Code | Description |
|---|---|
AUTH_FAILED |
Authentication failure |
INVALID_MESSAGE |
Malformed message |
UNKNOWN_COMMAND |
Unrecognized command |
RATE_LIMITED |
Too many messages |
SESSION_EXPIRED |
Session timed out |
Keepalive
Server sends ping every 30 seconds. Agent must respond with pong within 10 seconds or connection is closed.