WEFT_OS/crates/weft-appd/src/ws.rs

55 lines
2 KiB
Rust
Raw Normal View History

feat(appd): add WebSocket UI endpoint for Servo shell integration Implements the weft-appd WebSocket server that allows the system-ui.html page running inside Servo to send requests and receive push notifications without requiring custom SpiderMonkey bindings. ws.rs — WebSocket connection handler: - Accepts a tokio TcpStream, performs WebSocket handshake via tokio-tungstenite accept_async. - Reads JSON Text frames, deserializes as Request (serde_json), calls dispatch(), sends Response as JSON Text. - Subscribes to a broadcast::Receiver<Response> for server-push notifications (APP_READY, etc.); forwards to client via select!. - Handles close frames, partial errors, and lagged broadcast gracefully. main.rs — server changes: - broadcast::channel(16) created at startup; WebSocket handlers subscribe for push delivery. - TcpListener bound on 127.0.0.1:7410 (default) or WEFT_APPD_WS_PORT. - ws_port() / write_ws_port(): port written to XDG_RUNTIME_DIR/weft/appd.wsport for runtime discovery. - WS accept branch added to the main select! loop alongside Unix socket. ipc.rs — Response and AppStateKind now derive Clone (required by broadcast::Sender<Response>). system-ui.html — appd WebSocket client: - appdConnect(): opens ws://127.0.0.1:<port>/appd with exponential backoff reconnect (1s → 16s max). - On open: sends QUERY_RUNNING to populate taskbar with live sessions. - handleAppdMessage(): maps LAUNCH_ACK and RUNNING_APPS to taskbar entries; APP_READY shows a timed notification; APP_STATE::stopped removes the taskbar entry. - WEFT_APPD_WS_PORT window global overrides the default port. New deps: tokio-tungstenite 0.24, futures-util 0.3 (sink+std), serde_json 1.
2026-03-11 08:01:54 +00:00
use futures_util::{SinkExt, StreamExt};
use tokio::sync::broadcast;
use tokio_tungstenite::{accept_async, tungstenite::Message};
use crate::{Registry, dispatch, ipc::Request, ipc::Response};
pub async fn handle_ws_connection(
stream: tokio::net::TcpStream,
registry: Registry,
broadcast_rx: broadcast::Receiver<Response>,
) -> anyhow::Result<()> {
let ws_stream = accept_async(stream).await?;
let (mut ws_write, mut ws_read) = ws_stream.split();
let mut broadcast_rx = broadcast_rx;
loop {
tokio::select! {
msg = ws_read.next() => {
match msg {
Some(Ok(Message::Text(text))) => {
let req: Request = match serde_json::from_str(&text) {
Ok(r) => r,
Err(e) => {
tracing::warn!(error = %e, "invalid WS request");
continue;
}
};
tracing::debug!(?req, "ws request");
let resp = dispatch(req, &registry).await;
let json = serde_json::to_string(&resp)?;
ws_write.send(Message::Text(json)).await?;
}
Some(Ok(Message::Close(_))) | None => break,
Some(Ok(_)) => {}
Some(Err(e)) => {
tracing::warn!(error = %e, "ws error");
break;
}
}
}
notification = broadcast_rx.recv() => {
match notification {
Ok(resp) => {
let json = serde_json::to_string(&resp)?;
ws_write.send(Message::Text(json)).await?;
}
Err(broadcast::error::RecvError::Lagged(_)) => {}
Err(broadcast::error::RecvError::Closed) => break,
}
}
}
}
Ok(())
}