use crate::api::models::response::ProgressMessage;
use axum::{
extract::{
State, WebSocketUpgrade,
ws::{Message, WebSocket},
},
response::Response,
};
use futures::{SinkExt, StreamExt};
use std::sync::Arc;
use tokio::sync::broadcast;
use tracing::{debug, error, info};
pub struct WsState {
pub progress_tx: broadcast::Sender<ProgressMessage>,
}
pub async fn handle_websocket(ws: WebSocketUpgrade, State(state): State<Arc<WsState>>) -> Response {
ws.on_upgrade(move |socket| websocket_handler(socket, state))
}
async fn websocket_handler(socket: WebSocket, state: Arc<WsState>) {
info!("WebSocket connection established");
let (mut sender, mut receiver) = socket.split();
let mut progress_rx = state.progress_tx.subscribe();
let mut send_task = tokio::spawn(async move {
while let Ok(progress) = progress_rx.recv().await {
let json = match serde_json::to_string(&progress) {
Ok(j) => j,
Err(e) => {
error!("Failed to serialize progress message: {}", e);
continue;
}
};
if sender.send(Message::Text(json)).await.is_err() {
debug!("Client disconnected");
break;
}
}
});
let mut recv_task = tokio::spawn(async move {
while let Some(msg) = receiver.next().await {
match msg {
Ok(Message::Close(_)) => {
debug!("Client sent close message");
break;
}
Ok(Message::Ping(_data)) => {
debug!("Received ping, sending pong");
}
Ok(Message::Pong(_)) => {
debug!("Received pong");
}
Ok(Message::Text(text)) => {
debug!("Received text message: {}", text);
}
Err(e) => {
error!("WebSocket error: {}", e);
break;
}
_ => {}
}
}
});
tokio::select! {
_ = (&mut send_task) => {
recv_task.abort();
}
_ = (&mut recv_task) => {
send_task.abort();
}
}
info!("WebSocket connection closed");
}
pub async fn handle_scan_websocket(
ws: WebSocketUpgrade,
scan_id: String,
State(state): State<Arc<WsState>>,
) -> Response {
ws.on_upgrade(move |socket| scan_websocket_handler(socket, scan_id, state))
}
pub async fn scan_websocket_handler(socket: WebSocket, scan_id: String, state: Arc<WsState>) {
info!("WebSocket connection established for scan: {}", scan_id);
let (mut sender, mut receiver) = socket.split();
let mut progress_rx = state.progress_tx.subscribe();
let init_msg = serde_json::json!({
"type": "connected",
"scan_id": scan_id,
"message": "Connected to scan progress stream"
});
if let Ok(json) = serde_json::to_string(&init_msg) {
let _ = sender.send(Message::Text(json)).await;
}
let scan_id_clone = scan_id.clone();
let mut send_task = tokio::spawn(async move {
while let Ok(progress) = progress_rx.recv().await {
if progress.scan_id != scan_id_clone {
continue;
}
let json = match serde_json::to_string(&progress) {
Ok(j) => j,
Err(e) => {
error!("Failed to serialize progress message: {}", e);
continue;
}
};
if sender.send(Message::Text(json)).await.is_err() {
debug!("Client disconnected");
break;
}
if progress.msg_type == "completed" || progress.msg_type == "failed" {
debug!("Scan {} finished, closing WebSocket", scan_id_clone);
let _ = sender.send(Message::Close(None)).await;
break;
}
}
});
let mut recv_task = tokio::spawn(async move {
while let Some(msg) = receiver.next().await {
match msg {
Ok(Message::Close(_)) => {
debug!("Client sent close message");
break;
}
Ok(Message::Ping(_)) => {
debug!("Received ping");
}
Ok(Message::Text(text)) => {
debug!("Received text message: {}", text);
}
Err(e) => {
error!("WebSocket error: {}", e);
break;
}
_ => {}
}
}
});
tokio::select! {
_ = (&mut send_task) => {
recv_task.abort();
}
_ = (&mut recv_task) => {
send_task.abort();
}
}
info!("WebSocket connection closed for scan: {}", scan_id);
}