use alloc::format;
use alloc::string::ToString;
use alloc::sync::Arc;
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use lightyear_link::{Link, LinkPlugin, LinkStart, Linked, Linking};
use lightyear_link::server::{Server, ServerLinkPlugin};
use tracing::{error, info};
use crate::connection::{
PendingResult, PendingWebRtcConnection,
build_rtc_config, create_data_channel_init, wait_for_ice_gathering, wire_data_channel,
};
use crate::signaling::SignalingClient;
use crate::IceConfig;
pub struct WebRtcServerPlugin;
#[derive(Component)]
#[require(Server, Link)]
pub struct WebRtcServerIo {
pub ice_config: IceConfig,
pub signaling: Option<Box<dyn SignalingClient>>,
}
impl Plugin for WebRtcServerPlugin {
fn build(&self, app: &mut App) {
if !app.is_plugin_added::<crate::WebRtcPlugin>() {
app.add_plugins(crate::WebRtcPlugin);
}
if !app.is_plugin_added::<LinkPlugin>() {
app.add_plugins(LinkPlugin);
}
if !app.is_plugin_added::<ServerLinkPlugin>() {
app.add_plugins(ServerLinkPlugin);
}
app.add_observer(Self::link);
}
}
impl WebRtcServerPlugin {
fn link(
trigger: On<LinkStart>,
mut query: Query<(Entity, &mut WebRtcServerIo), (Without<Linking>, Without<Linked>)>,
mut commands: Commands,
) {
let Ok((entity, mut server_io)) = query.get_mut(trigger.entity) else {
return;
};
let Some(signaling) = server_io.signaling.take() else {
error!("WebRtcServerIo signaling already consumed");
return;
};
let ice_config = server_io.ice_config.clone();
let pending_result = Arc::new(std::sync::Mutex::new(None));
let pending_error = Arc::new(std::sync::Mutex::new(None));
let result_clone = pending_result.clone();
let error_clone = pending_error.clone();
commands.entity(entity).insert((
Linking,
PendingWebRtcConnection {
result: pending_result,
error: pending_error,
},
));
wasm_bindgen_futures::spawn_local(async move {
match setup_host(ice_config, signaling).await {
Ok(result) => {
*result_clone.lock().unwrap() = Some(result);
info!("WebRTC host connection established");
}
Err(e) => {
*error_clone.lock().unwrap() = Some(e.to_string());
error!("WebRTC host setup failed: {e}");
}
}
});
}
}
async fn setup_host(
ice_config: IceConfig,
mut signaling: Box<dyn SignalingClient>,
) -> Result<PendingResult, crate::WebRtcError> {
use wasm_bindgen_futures::JsFuture;
let config = build_rtc_config(&ice_config);
let pc = web_sys::RtcPeerConnection::new_with_configuration(&config)
.map_err(|e| crate::WebRtcError::Connection(format!("{e:?}")))?;
let dc_init = create_data_channel_init();
let dc = pc.create_data_channel_with_data_channel_dict("lightyear", &dc_init);
let (data_tx, data_rx, event_rx) = wire_data_channel(&dc);
let offer = JsFuture::from(pc.create_offer())
.await
.map_err(|e| crate::WebRtcError::Connection(format!("create_offer failed: {e:?}")))?;
let offer_sdp = js_sys::Reflect::get(&offer, &"sdp".into())
.map_err(|e| crate::WebRtcError::Connection(format!("get sdp failed: {e:?}")))?
.as_string()
.ok_or_else(|| crate::WebRtcError::Connection("offer sdp not a string".into()))?;
let offer_desc = web_sys::RtcSessionDescriptionInit::new(web_sys::RtcSdpType::Offer);
offer_desc.set_sdp(&offer_sdp);
JsFuture::from(pc.set_local_description(&offer_desc))
.await
.map_err(|e| crate::WebRtcError::Connection(format!("set_local_description failed: {e:?}")))?;
let full_offer_sdp = wait_for_ice_gathering(&pc).await?;
info!("WebRTC host: ICE gathering complete");
let answer_sdp = signaling
.publish_offer(full_offer_sdp)
.await
.map_err(|e| crate::WebRtcError::Signaling(e.to_string()))?;
let answer_desc = web_sys::RtcSessionDescriptionInit::new(web_sys::RtcSdpType::Answer);
answer_desc.set_sdp(&answer_sdp);
JsFuture::from(pc.set_remote_description(&answer_desc))
.await
.map_err(|e| crate::WebRtcError::Connection(format!("set_remote_description failed: {e:?}")))?;
info!("WebRTC host: remote description set, waiting for DataChannel to open");
Ok(PendingResult { data_tx, data_rx, event_rx })
}