feat: add reactive streaming and remove dead panel stream code
- Subscribe to StreamCascadeReactiveUpdates for real-time cascade state diffs - Fall back to timer-based polling if streaming RPC unavailable - Remove StreamCascadePanelReactiveUpdates code (dead end, only has plan_status/user_settings) - Remove debug diff file-saving code - Add stream_reactive_rpc() helper to backend
This commit is contained in:
105
src/backend.rs
105
src/backend.rs
@@ -366,6 +366,111 @@ impl Backend {
|
||||
let body = serde_json::json!({"cascadeId": cascade_id});
|
||||
self.call_json("GetCascadeTrajectory", &body).await
|
||||
}
|
||||
|
||||
/// Open a server-streaming reactive updates RPC.
|
||||
/// `rpc_method` is the ConnectRPC method name, e.g. "StreamCascadeReactiveUpdates".
|
||||
async fn stream_reactive_rpc(
|
||||
&self,
|
||||
rpc_method: &str,
|
||||
cascade_id: &str,
|
||||
) -> Result<tokio::sync::mpsc::Receiver<serde_json::Value>, String> {
|
||||
let (base, csrf) = {
|
||||
let guard = self.inner.read().await;
|
||||
(
|
||||
format!("https://127.0.0.1:{}", guard.https_port),
|
||||
guard.csrf.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
let url = format!("{base}/{LS_SERVICE}/{rpc_method}");
|
||||
let body = serde_json::json!({
|
||||
"protocolVersion": 1,
|
||||
"id": cascade_id,
|
||||
});
|
||||
|
||||
let mut headers = Self::common_headers(&csrf);
|
||||
headers.insert("Content-Type", HeaderValue::from_static("application/connect+json"));
|
||||
headers.insert("Connect-Protocol-Version", HeaderValue::from_static("1"));
|
||||
|
||||
// Connect protocol envelope: [flags:1][length:4][payload]
|
||||
let json_bytes = serde_json::to_vec(&body).unwrap();
|
||||
let mut envelope = Vec::with_capacity(5 + json_bytes.len());
|
||||
envelope.push(0x00);
|
||||
envelope.extend_from_slice(&(json_bytes.len() as u32).to_be_bytes());
|
||||
envelope.extend_from_slice(&json_bytes);
|
||||
|
||||
let mut resp = self
|
||||
.client
|
||||
.post(&url)
|
||||
.headers(headers)
|
||||
.body(envelope)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("{rpc_method} HTTP error: {e}"))?;
|
||||
|
||||
let status = resp.status().as_u16();
|
||||
if status != 200 {
|
||||
let err_body = resp.bytes().await.unwrap_or_default();
|
||||
let err_text = String::from_utf8_lossy(&err_body);
|
||||
return Err(format!("{rpc_method} failed: {status} — {err_text}"));
|
||||
}
|
||||
|
||||
let resp_ct = resp.headers()
|
||||
.get("content-type")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
debug!("{rpc_method}: connected for cascade {cascade_id}, content-type: {resp_ct}");
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<serde_json::Value>(64);
|
||||
let method = rpc_method.to_string();
|
||||
let cid = cascade_id.to_string();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
while let Ok(Some(chunk)) = resp.chunk().await {
|
||||
if chunk.is_empty() {
|
||||
continue;
|
||||
}
|
||||
buf.extend_from_slice(&chunk);
|
||||
|
||||
while buf.len() >= 5 {
|
||||
let flags = buf[0];
|
||||
let len = u32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]) as usize;
|
||||
|
||||
if buf.len() < 5 + len {
|
||||
break;
|
||||
}
|
||||
|
||||
let payload = &buf[5..5 + len];
|
||||
|
||||
if flags == 0x02 {
|
||||
let text = String::from_utf8_lossy(payload);
|
||||
debug!("{method}: end frame: {text}");
|
||||
} else if let Ok(json) = serde_json::from_slice::<serde_json::Value>(payload) {
|
||||
if tx.send(json).await.is_err() {
|
||||
buf.drain(..5 + len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buf.drain(..5 + len);
|
||||
}
|
||||
}
|
||||
debug!("{method}: stream ended for {cid}");
|
||||
});
|
||||
|
||||
Ok(rx)
|
||||
}
|
||||
|
||||
/// StreamCascadeReactiveUpdates — real-time cascade state diffs.
|
||||
pub async fn stream_cascade_updates(
|
||||
&self,
|
||||
cascade_id: &str,
|
||||
) -> Result<tokio::sync::mpsc::Receiver<serde_json::Value>, String> {
|
||||
self.stream_reactive_rpc("StreamCascadeReactiveUpdates", cascade_id).await
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Discovery helpers ───────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user