use super::connection_manager::{ConnectionManager, ConnectionManagerOptions, ConnectionState};
use crate::crypto::aes::Aes128State;
use anyhow::{anyhow, Result};
use gloo::timers::callback::Interval;
use log::{debug, info};
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use videocall_types::protos::packet_wrapper::PacketWrapper;
#[derive(Debug)]
pub struct ConnectionController {
inner: Rc<RefCell<ConnectionControllerInner>>,
_timers: Vec<Interval>, }
#[derive(Debug)]
struct ConnectionControllerInner {
connection_manager: ConnectionManager,
}
impl ConnectionController {
pub fn new(options: ConnectionManagerOptions, aes: Rc<Aes128State>) -> Result<Self> {
info!("Creating ConnectionController with timer management");
let connection_manager = ConnectionManager::new(options.clone(), aes.clone())?;
let inner = Rc::new(RefCell::new(ConnectionControllerInner {
connection_manager,
}));
let timers = Self::start_timers(Rc::downgrade(&inner));
info!("ConnectionController created with all timers started");
Ok(Self {
inner,
_timers: timers,
})
}
fn start_timers(inner_weak: Weak<RefCell<ConnectionControllerInner>>) -> Vec<Interval> {
let mut timers = Vec::new();
let inner_ref = inner_weak.clone();
timers.push(Interval::new(1000, move || {
if let Some(inner) = inner_ref.upgrade() {
if let Ok(mut inner) = inner.try_borrow_mut() {
inner.connection_manager.trigger_diagnostics_report();
if matches!(
inner.connection_manager.get_connection_state(),
ConnectionState::Connected { .. }
) {
if let Err(e) = inner.connection_manager.send_rtt_probes() {
debug!("Failed to send 1Hz RTT probe post-election: {e}");
}
}
}
}
}));
let inner_ref = inner_weak.clone();
timers.push(Interval::new(200, move || {
if let Some(inner) = inner_ref.upgrade() {
if let Ok(mut inner) = inner.try_borrow_mut() {
if matches!(
inner.connection_manager.get_connection_state(),
ConnectionState::Testing { .. }
) {
if let Err(e) = inner.connection_manager.send_rtt_probes() {
debug!("Failed to send RTT probes during election: {e}");
}
}
}
}
}));
let inner_ref = inner_weak.clone();
timers.push(Interval::new(100, move || {
if let Some(inner) = inner_ref.upgrade() {
if let Ok(mut inner) = inner.try_borrow_mut() {
inner.connection_manager.check_and_complete_election();
}
}
}));
info!("All ConnectionController timers started");
timers
}
pub fn send_packet(&self, packet: PacketWrapper) -> Result<()> {
let inner = self
.inner
.try_borrow()
.map_err(|_| anyhow!("Failed to borrow ConnectionController inner"))?;
inner.connection_manager.send_packet(packet)
}
pub fn set_video_enabled(&self, enabled: bool) -> Result<()> {
let inner = self
.inner
.try_borrow()
.map_err(|_| anyhow!("Failed to borrow ConnectionController inner"))?;
inner.connection_manager.set_video_enabled(enabled)
}
pub fn set_audio_enabled(&self, enabled: bool) -> Result<()> {
let inner = self
.inner
.try_borrow()
.map_err(|_| anyhow!("Failed to borrow ConnectionController inner"))?;
inner.connection_manager.set_audio_enabled(enabled)
}
pub fn set_screen_enabled(&self, enabled: bool) -> Result<()> {
let inner = self
.inner
.try_borrow()
.map_err(|_| anyhow!("Failed to borrow ConnectionController inner"))?;
inner.connection_manager.set_screen_enabled(enabled)
}
pub fn set_own_session_id(&self, session_id: u64) -> Result<()> {
let inner = self
.inner
.try_borrow()
.map_err(|_| anyhow!("Failed to borrow ConnectionController inner"))?;
inner.connection_manager.set_own_session_id(session_id);
Ok(())
}
pub fn is_connected(&self) -> bool {
if let Ok(inner) = self.inner.try_borrow() {
inner.connection_manager.is_connected()
} else {
false
}
}
pub fn disconnect(&self) -> anyhow::Result<()> {
let mut inner = self
.inner
.try_borrow_mut()
.map_err(|_| anyhow!("Failed to borrow ConnectionController inner"))?;
inner.connection_manager.disconnect()
}
pub fn get_connection_state(&self) -> ConnectionState {
if let Ok(inner) = self.inner.try_borrow() {
inner.connection_manager.get_connection_state()
} else {
ConnectionState::Failed {
error: "Failed to borrow ConnectionController inner".to_string(),
last_known_server: None,
}
}
}
pub fn get_rtt_measurements_clone(
&self,
) -> std::collections::HashMap<String, super::connection_manager::ServerRttMeasurement> {
if let Ok(inner) = self.inner.try_borrow() {
inner.connection_manager.get_rtt_measurements().clone()
} else {
std::collections::HashMap::new()
}
}
}
impl Drop for ConnectionController {
fn drop(&mut self) {
info!("Dropping ConnectionController and cleaning up timers");
}
}
#[cfg(all(test, target_arch = "wasm32"))]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
use videocall_types::Callback;
#[derive(Debug, Clone)]
struct StateCapture {
states: Arc<Mutex<Vec<ConnectionState>>>,
}
impl StateCapture {
fn new() -> Self {
Self {
states: Arc::new(Mutex::new(Vec::new())),
}
}
fn callback(&self) -> Callback<ConnectionState> {
let states = self.states.clone();
Callback::from(move |state: ConnectionState| {
states.lock().unwrap().push(state);
})
}
fn get_states(&self) -> Vec<ConnectionState> {
self.states.lock().unwrap().clone()
}
fn last_state(&self) -> Option<ConnectionState> {
self.states.lock().unwrap().last().cloned()
}
}
fn create_test_options(state_capture: &StateCapture) -> ConnectionManagerOptions {
ConnectionManagerOptions {
websocket_urls: vec!["ws://localhost:8080".to_string()],
webtransport_urls: vec!["https://localhost:8443".to_string()],
userid: "test_user".to_string(),
on_inbound_media: Callback::from(|_| {}),
on_state_changed: state_capture.callback(),
peer_monitor: Callback::from(|_| {}),
election_period_ms: 1000,
}
}
fn create_test_aes() -> Rc<Aes128State> {
let key = vec![1u8; 16];
let iv = vec![2u8; 16];
Rc::new(Aes128State::from_vecs(key, iv, true))
}
#[test]
fn test_connection_controller_creation() {
let state_capture = StateCapture::new();
let options = create_test_options(&state_capture);
let aes = create_test_aes();
let _controller = ConnectionController::new(options, aes);
}
#[test]
fn test_connection_controller_delegation() {
let state_capture = StateCapture::new();
let options = create_test_options(&state_capture);
let aes = create_test_aes();
if let Ok(controller) = ConnectionController::new(options, aes) {
let _is_connected = controller.is_connected();
let _state = controller.get_connection_state();
let _measurements = controller.get_rtt_measurements_clone();
}
}
}