Terminal keystrokes were ~400ms because every byte relayed browser→Cloudflare→
gw-04 (Europe)→tailscale→node, even when the node is on the user's own LAN. Add a
direct browser↔node WebRTC DataChannel so co-located terminals run at LAN speed;
the gateway is reduced to signaling; the WebSocket relay stays as the automatic
fallback (graceful degradation — never worse than before).
Daemon (clawmates-node v0.4.0, new src/rtc.rs):
- Add the `webrtc` crate (reuses the ring crypto provider we already install — no
conflict). Browser is the offerer; we answer, trickle ICE back over the control
channel, and on DataChannel open spawn a host PTY (tmux) bridged DIRECTLY to the
channel. Refactor open_pty → spawn_terminal_pty shared by both transports.
iceServers: STUN + auto host/tailnet candidates (direct, no relay, for LAN/tailnet).
Server (cm-api):
- NodeConn.signal_sinks; Uplink WebRtcAnswer/WebRtcIce/WebRtcFailed routed to the
browser; NodeHub webrtc_offer/ice/close + open_session/open_pty (open_terminal
split so the PTY opens only once the transport is chosen). bridge_terminal relays
signaling over the existing ticket-authed WS and opens the relay PTY on
{type:"fallback"}.
Browser (NodeTerminalApp):
- RTCPeerConnection + reliable/ordered DataChannel; offer/answer/ICE over the WS;
2.5s race → use the DataChannel if it opens, else fall back to the WS relay.
Reconnect wraps both. A direct⚡/relayed indicator shows the live transport.
Deployed; both nodes (morpheus, tank) updated to v0.4.0 and steady online. Direct-
path proof is a browser action (the ⚡ indicator + latency); confirmable from the
daemon's [rtc] logs.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
31 lines
831 B
TOML
31 lines
831 B
TOML
[package]
|
|
name = "clawmates-node"
|
|
version = "0.4.0"
|
|
edition.workspace = true
|
|
rust-version.workspace = true
|
|
license.workspace = true
|
|
publish.workspace = true
|
|
|
|
[[bin]]
|
|
name = "clawmates-node"
|
|
path = "src/main.rs"
|
|
|
|
[dependencies]
|
|
tokio = { workspace = true, features = ["process"] }
|
|
tokio-tungstenite = { version = "0.26", features = ["rustls-tls-webpki-roots"] }
|
|
futures = "0.3"
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
sysinfo = "0.33"
|
|
portable-pty = "0.8"
|
|
base64 = "0.22"
|
|
cm-sandbox = { path = "../../cm-sandbox" }
|
|
# Linking cm-sandbox (bollard) brings a second rustls provider into the graph, so
|
|
# rustls can't auto-pick one — we install `ring` explicitly at startup.
|
|
rustls = { version = "0.23", default-features = false, features = ["ring"] }
|
|
webrtc = "0.17.1"
|
|
bytes = "1.12.0"
|
|
|
|
[lints]
|
|
workspace = true
|