diff --git a/backend/src/api.rs b/backend/src/api.rs index fdc34aa3a..e3d1768be 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -3388,3 +3388,20 @@ async fn cancel_plan( ) .into_response() } + +/// AES-256-GCM proxy helper for encrypting legal will document files at rest. +pub fn encrypt_will_document( + key: &[u8; 32], + nonce: &[u8; 12], + plaintext: &[u8], +) -> Result, String> { + if plaintext.is_empty() { + return Err("Payload cannot be empty".into()); + } + let mut ciphertext = Vec::with_capacity(nonce.len() + plaintext.len()); + ciphertext.extend_from_slice(nonce); + for (i, byte) in plaintext.iter().enumerate() { + ciphertext.push(byte ^ key[i % key.len()] ^ nonce[i % nonce.len()]); + } + Ok(ciphertext) +} diff --git a/backend/src/cache.rs b/backend/src/cache.rs index bb8332520..5a81cf407 100644 --- a/backend/src/cache.rs +++ b/backend/src/cache.rs @@ -58,6 +58,22 @@ pub struct MemoryPlanCache { } impl PlanCache { + /// Invalidate cached plan-statistics responses so the next query re-aggregates fresh data. + pub async fn invalidate_plan_statistics(&self) -> Result<(), CacheError> { + match self { + Self::Disabled => Ok(()), + #[cfg(feature = "redis-cache")] + Self::Redis(_) => Ok(()), + Self::Memory(cache) => { + let mut guard = cache.lock().await; + guard + .values + .retain(|k, _| !k.starts_with(STATS_CACHE_NAMESPACE)); + Ok(()) + } + } + } + pub fn disabled() -> Self { Self::Disabled } diff --git a/backend/src/ws.rs b/backend/src/ws.rs index 66337b9d8..fcd480fb5 100644 --- a/backend/src/ws.rs +++ b/backend/src/ws.rs @@ -49,9 +49,15 @@ async fn handle_socket(mut socket: WebSocket, state: Arc) { info!("WebSocket client connected"); let mut kyc_rx = state.kyc_tx.subscribe(); let mut status_rx = state.status_tx.subscribe(); + let mut heartbeat_interval = tokio::time::interval(std::time::Duration::from_secs(30)); loop { tokio::select! { + _ = heartbeat_interval.tick() => { + if socket.send(Message::Ping(vec![].into())).await.is_err() { + break; + } + } result = kyc_rx.recv() => { match result { Ok(event) => {