1use std::time::Duration;
4
5use axum::{
6 extract::{
7 ws::{Message, WebSocket, WebSocketUpgrade},
8 Path, State,
9 },
10 response::IntoResponse,
11};
12use futures_util::{SinkExt, StreamExt};
13
14use super::handlers::AppState;
15use super::types::{WsClientMessage, WsServerMessage};
16use crate::execution::Command;
17use crate::session::SessionId;
18
19pub async fn ws_handler(
21 ws: WebSocketUpgrade,
22 State(state): State<AppState>,
23 Path(session_id): Path<u64>,
24 identity: Option<axum::Extension<crate::audit::Identity>>,
25) -> impl IntoResponse {
26 let identity = identity.map(|axum::Extension(id)| id);
29 ws.on_upgrade(move |socket| handle_socket(socket, state, session_id, identity))
30}
31
32async fn handle_socket(
34 socket: WebSocket,
35 state: AppState,
36 session_id: u64,
37 identity: Option<crate::audit::Identity>,
38) {
39 let id = SessionId::from_raw(session_id);
40
41 if state.store.get(&id).ok().flatten().is_none() {
43 let (mut sink, _) = socket.split();
44 let err = WsServerMessage::Error {
45 code: "SESSION_NOT_FOUND".to_string(),
46 message: format!("Session {} not found", session_id),
47 };
48 if let Ok(json) = serde_json::to_string(&err) {
49 let _ = sink.send(Message::Text(json.into())).await;
50 }
51 return;
52 }
53
54 let (mut sink, mut stream) = socket.split();
55
56 while let Some(msg) = stream.next().await {
58 let msg = match msg {
59 Ok(Message::Text(text)) => text.to_string(),
60 Ok(Message::Close(_)) => break,
61 Ok(Message::Ping(data)) => {
62 let _ = sink.send(Message::Pong(data)).await;
63 continue;
64 }
65 Ok(_) => continue,
66 Err(_) => break,
67 };
68
69 let ws_msg: WsClientMessage = match serde_json::from_str(&msg) {
71 Ok(m) => m,
72 Err(e) => {
73 let err = WsServerMessage::Error {
74 code: "PARSE_ERROR".to_string(),
75 message: e.to_string(),
76 };
77 if let Ok(json) = serde_json::to_string(&err) {
78 let _ = sink.send(Message::Text(json.into())).await;
79 }
80 continue;
81 }
82 };
83
84 match ws_msg {
85 WsClientMessage::Execute {
86 command,
87 timeout_secs,
88 } => {
89 let mut cmd = Command::new(&command);
91 if let Some(secs) = timeout_secs {
92 cmd = cmd.timeout(Duration::from_secs(secs));
93 }
94
95 match state.executor.execute_async(&cmd).await {
97 Ok((mut rx, handle)) => {
98 while let Some(chunk) = rx.recv().await {
100 let output = WsServerMessage::Output {
101 data: String::from_utf8_lossy(&chunk.raw).to_string(),
102 is_final: false,
103 };
104 if let Ok(json) = serde_json::to_string(&output) {
105 if sink.send(Message::Text(json.into())).await.is_err() {
106 break;
107 }
108 }
109 }
110
111 match handle.await {
113 Ok(Ok(result)) => {
114 state.audit.record(
115 crate::audit::AuditEvent::new("execute")
116 .with_identity(identity.clone())
117 .with_route("WS /api/v1/sessions/{id}/ws")
118 .with_command(&command)
119 .with_session(session_id)
120 .with_outcome(
121 result.exit_code,
122 result.timed_out,
123 result.duration.as_millis() as u64,
124 ),
125 );
126
127 state
129 .store
130 .update(&id, |s| {
131 s.context.record_execution(&command, result.exit_code);
132 })
133 .ok();
134
135 let result_msg = WsServerMessage::Result {
136 success: result.exit_code.map(|c| c == 0).unwrap_or(false)
137 && !result.timed_out,
138 exit_code: result.exit_code,
139 duration_ms: result.duration.as_millis() as u64,
140 timed_out: result.timed_out,
141 total_bytes: result.total_bytes,
142 };
143 if let Ok(json) = serde_json::to_string(&result_msg) {
144 let _ = sink.send(Message::Text(json.into())).await;
145 }
146 }
147 Ok(Err(e)) => {
148 let err = WsServerMessage::Error {
149 code: "EXECUTION_ERROR".to_string(),
150 message: e.to_string(),
151 };
152 if let Ok(json) = serde_json::to_string(&err) {
153 let _ = sink.send(Message::Text(json.into())).await;
154 }
155 }
156 Err(e) => {
157 let err = WsServerMessage::Error {
158 code: "TASK_ERROR".to_string(),
159 message: e.to_string(),
160 };
161 if let Ok(json) = serde_json::to_string(&err) {
162 let _ = sink.send(Message::Text(json.into())).await;
163 }
164 }
165 }
166 }
167 Err(e) => {
168 let err = WsServerMessage::Error {
169 code: "EXECUTION_ERROR".to_string(),
170 message: e.to_string(),
171 };
172 if let Ok(json) = serde_json::to_string(&err) {
173 let _ = sink.send(Message::Text(json.into())).await;
174 }
175 }
176 }
177 }
178 WsClientMessage::Ping => {
179 let pong = WsServerMessage::Pong;
180 if let Ok(json) = serde_json::to_string(&pong) {
181 let _ = sink.send(Message::Text(json.into())).await;
182 }
183 }
184 _ => {
185 }
187 }
188 }
189}
190
191pub async fn ws_oneshot_handler(
193 ws: WebSocketUpgrade,
194 State(state): State<AppState>,
195 identity: Option<axum::Extension<crate::audit::Identity>>,
196) -> impl IntoResponse {
197 let identity = identity.map(|axum::Extension(id)| id);
198 ws.on_upgrade(move |socket| handle_oneshot_socket(socket, state, identity))
199}
200
201async fn handle_oneshot_socket(
203 socket: WebSocket,
204 state: AppState,
205 identity: Option<crate::audit::Identity>,
206) {
207 let (mut sink, mut stream) = socket.split();
208
209 while let Some(msg) = stream.next().await {
210 let msg = match msg {
211 Ok(Message::Text(text)) => text.to_string(),
212 Ok(Message::Close(_)) => break,
213 Ok(Message::Ping(data)) => {
214 let _ = sink.send(Message::Pong(data)).await;
215 continue;
216 }
217 Ok(_) => continue,
218 Err(_) => break,
219 };
220
221 let ws_msg: WsClientMessage = match serde_json::from_str(&msg) {
222 Ok(m) => m,
223 Err(e) => {
224 let err = WsServerMessage::Error {
225 code: "PARSE_ERROR".to_string(),
226 message: e.to_string(),
227 };
228 if let Ok(json) = serde_json::to_string(&err) {
229 let _ = sink.send(Message::Text(json.into())).await;
230 }
231 continue;
232 }
233 };
234
235 match ws_msg {
236 WsClientMessage::Execute {
237 command,
238 timeout_secs,
239 } => {
240 let mut cmd = Command::new(&command);
241 if let Some(secs) = timeout_secs {
242 cmd = cmd.timeout(Duration::from_secs(secs));
243 }
244
245 match state.executor.execute_async(&cmd).await {
246 Ok((mut rx, handle)) => {
247 while let Some(chunk) = rx.recv().await {
248 let output = WsServerMessage::Output {
249 data: String::from_utf8_lossy(&chunk.raw).to_string(),
250 is_final: false,
251 };
252 if let Ok(json) = serde_json::to_string(&output) {
253 if sink.send(Message::Text(json.into())).await.is_err() {
254 break;
255 }
256 }
257 }
258
259 match handle.await {
260 Ok(Ok(result)) => {
261 state.audit.record(
262 crate::audit::AuditEvent::new("execute")
263 .with_identity(identity.clone())
264 .with_route("WS /api/v1/ws")
265 .with_command(&command)
266 .with_outcome(
267 result.exit_code,
268 result.timed_out,
269 result.duration.as_millis() as u64,
270 ),
271 );
272
273 let result_msg = WsServerMessage::Result {
274 success: result.exit_code.map(|c| c == 0).unwrap_or(false)
275 && !result.timed_out,
276 exit_code: result.exit_code,
277 duration_ms: result.duration.as_millis() as u64,
278 timed_out: result.timed_out,
279 total_bytes: result.total_bytes,
280 };
281 if let Ok(json) = serde_json::to_string(&result_msg) {
282 let _ = sink.send(Message::Text(json.into())).await;
283 }
284 }
285 Ok(Err(e)) => {
286 let err = WsServerMessage::Error {
287 code: "EXECUTION_ERROR".to_string(),
288 message: e.to_string(),
289 };
290 if let Ok(json) = serde_json::to_string(&err) {
291 let _ = sink.send(Message::Text(json.into())).await;
292 }
293 }
294 Err(e) => {
295 let err = WsServerMessage::Error {
296 code: "TASK_ERROR".to_string(),
297 message: e.to_string(),
298 };
299 if let Ok(json) = serde_json::to_string(&err) {
300 let _ = sink.send(Message::Text(json.into())).await;
301 }
302 }
303 }
304 }
305 Err(e) => {
306 let err = WsServerMessage::Error {
307 code: "EXECUTION_ERROR".to_string(),
308 message: e.to_string(),
309 };
310 if let Ok(json) = serde_json::to_string(&err) {
311 let _ = sink.send(Message::Text(json.into())).await;
312 }
313 }
314 }
315 }
316 WsClientMessage::Ping => {
317 let pong = WsServerMessage::Pong;
318 if let Ok(json) = serde_json::to_string(&pong) {
319 let _ = sink.send(Message::Text(json.into())).await;
320 }
321 }
322 _ => {}
323 }
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn test_ws_message_execute_parse() {
333 let json = r#"{"type": "execute", "command": "echo hello"}"#;
334 let msg: WsClientMessage = serde_json::from_str(json).unwrap();
335 match msg {
336 WsClientMessage::Execute { command, .. } => assert_eq!(command, "echo hello"),
337 _ => panic!("Expected Execute message"),
338 }
339 }
340
341 #[test]
342 fn test_ws_message_ping_parse() {
343 let json = r#"{"type": "ping"}"#;
344 let msg: WsClientMessage = serde_json::from_str(json).unwrap();
345 assert!(matches!(msg, WsClientMessage::Ping));
346 }
347}