// services/frontend-leptos/frontend/src/ws/context.rs use leptos::prelude::*; use crate::ws::socket::{WsHandle, WsStatus, WsEvent}; use shared_types::message::MessageRecord; use shared_types::voice::ActiveSpeaker; use shared_types::media::MediaState; use shared_types::recording::VoiceRecording; #[derive(Clone)] pub struct WsContext { pub handle: std::rc::Rc, pub status: ReadSignal, // Per-event callbacks (set externally by feature components) // Wrapped in Rc so cloning shares the same callback slots pub on_message_created: std::rc::Rc>>>, pub on_message_updated: std::rc::Rc>>>, pub on_message_deleted: std::rc::Rc>>>, pub on_message_analyzed: std::rc::Rc>>>, pub on_voice_active_user: std::rc::Rc>>>, pub on_voice_recording_uploaded: std::rc::Rc>>>, pub on_media_state: std::rc::Rc>>>, pub on_binary: std::rc::Rc)>>>>, } impl WsContext { pub fn new(url: &str) -> Self { let ws_handle = std::rc::Rc::new(WsHandle::new(url)); let status = ws_handle.status; let ctx = Self { status, handle: ws_handle, on_message_created: std::rc::Rc::new(std::cell::RefCell::new(None)), on_message_updated: std::rc::Rc::new(std::cell::RefCell::new(None)), on_message_deleted: std::rc::Rc::new(std::cell::RefCell::new(None)), on_message_analyzed: std::rc::Rc::new(std::cell::RefCell::new(None)), on_voice_active_user: std::rc::Rc::new(std::cell::RefCell::new(None)), on_voice_recording_uploaded: std::rc::Rc::new(std::cell::RefCell::new(None)), on_media_state: std::rc::Rc::new(std::cell::RefCell::new(None)), on_binary: std::rc::Rc::new(std::cell::RefCell::new(None)), }; // Wire up the main event dispatcher let ctx_clone = ctx.clone(); ctx.handle.on_event(move |event| { ctx_clone.dispatch_event(event); }); ctx } fn dispatch_event(&self, event: WsEvent) { match event { WsEvent::Text(text) => { // Parse JSON envelope: { type: string, data?: any } if let Ok(parsed) = serde_json::from_str::(&text) { let event_type = parsed["type"].as_str().unwrap_or("").to_string(); let data = parsed.get("data"); match event_type.as_str() { "message_created" => { if let Some(d) = data.and_then(|v| serde_json::from_value::(v.clone()).ok()) { if let Some(cb) = self.on_message_created.borrow().as_ref() { cb(d); } } } "message_updated" => { if let Some(d) = data.and_then(|v| serde_json::from_value::(v.clone()).ok()) { if let Some(cb) = self.on_message_updated.borrow().as_ref() { cb(d); } } } "message_deleted" => { if let Some(d) = data.and_then(|v| v.as_str().map(String::from)) { if let Some(cb) = self.on_message_deleted.borrow().as_ref() { cb(d); } } } "message_analyzed" => { if let Some(d) = data.and_then(|v| serde_json::from_value::(v.clone()).ok()) { if let Some(cb) = self.on_message_analyzed.borrow().as_ref() { cb(d); } } } "voice_active_user" => { if let Some(d) = data.and_then(|v| serde_json::from_value::(v.clone()).ok()) { if let Some(cb) = self.on_voice_active_user.borrow().as_ref() { cb(d); } } } "voice_recording_uploaded" => { if let Some(d) = data.and_then(|v| serde_json::from_value::(v.clone()).ok()) { if let Some(cb) = self.on_voice_recording_uploaded.borrow().as_ref() { cb(d); } } } "media_state" => { if let Some(d) = data.and_then(|v| serde_json::from_value::(v.clone()).ok()) { if let Some(cb) = self.on_media_state.borrow().as_ref() { cb(d); } } } _ => { // Unknown event type — log and ignore web_sys::console::log_1(&format!("[WS] unhandled event: {}", event_type).into()); } } } } WsEvent::Binary(data) => { if let Some(cb) = self.on_binary.borrow().as_ref() { cb(data); } } } } pub fn connect(&self) { self.handle.connect(); } pub fn disconnect(&self) { self.handle.disconnect(); } pub fn send_text(&self, text: &str) { let _ = self.handle.send_text(text); } pub fn send_binary(&self, data: &[u8]) { let _ = self.handle.send_binary(data); } }