Upgrade Dioxus and auto-connect signaling
This commit is contained in:
parent
573abae5e3
commit
a917b4142b
2333
Cargo.lock
generated
2333
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,8 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Dioxus Framework
|
# Dioxus Framework
|
||||||
dioxus = { version = "0.6.0", features = ["web"] }
|
dioxus = { version = "0.7", features = ["web"] }
|
||||||
dioxus-logger = "0.6.2"
|
dioxus-logger = "0.7"
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
|
|
||||||
# WebAssembly and Browser APIs
|
# WebAssembly and Browser APIs
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"server": {
|
"server": {
|
||||||
"stun_server": "stun:stun.l.google.com:19302"
|
"stun_server": "stun:stun.l.google.com:19302",
|
||||||
|
"signaling_url": "ws://localhost:3478/ws"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
|
use crate::config::Config;
|
||||||
use crate::models::SignalingMessage;
|
use crate::models::SignalingMessage;
|
||||||
use crate::utils::MediaManager;
|
use crate::utils::MediaManager;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use std::rc::Rc;
|
||||||
use wasm_bindgen::prelude::Closure;
|
use wasm_bindgen::prelude::Closure;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
@ -19,9 +21,10 @@ pub fn ConnectionPanel(
|
|||||||
initiator_connection: Signal<Option<RtcPeerConnection>>, // Initiator PC (wird für eingehende Answers verwendet)
|
initiator_connection: Signal<Option<RtcPeerConnection>>, // Initiator PC (wird für eingehende Answers verwendet)
|
||||||
local_media: Signal<Option<MediaStream>>,
|
local_media: Signal<Option<MediaStream>>,
|
||||||
) -> Element {
|
) -> Element {
|
||||||
let mut ws_status = use_signal(|| "Nicht verbunden".to_string());
|
let ws_status = use_signal(|| "Nicht verbunden".to_string());
|
||||||
// Buffer for an incoming Answer SDP if the initiator PC isn't ready yet
|
// Buffer for an incoming Answer SDP if the initiator PC isn't ready yet
|
||||||
let mut pending_answer = use_signal(|| None::<String>);
|
let pending_answer = use_signal(|| None::<String>);
|
||||||
|
let cfg_signal: Signal<Config> = use_context();
|
||||||
|
|
||||||
// **COROUTINE** für Offer-Handling (Responder empfängt Offers)
|
// **COROUTINE** für Offer-Handling (Responder empfängt Offers)
|
||||||
let offer_handler = use_coroutine(move |mut rx| async move {
|
let offer_handler = use_coroutine(move |mut rx| async move {
|
||||||
@ -174,11 +177,41 @@ pub fn ConnectionPanel(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// WebSocket verbinden
|
// WebSocket verbinden
|
||||||
let connect_websocket = move |_| {
|
let connect_logic: Rc<dyn Fn()> = {
|
||||||
log::info!("🔌 Verbinde WebSocket...");
|
let ws_status_signal = ws_status.clone();
|
||||||
|
let connected_signal = connected.clone();
|
||||||
|
let websocket_signal = websocket.clone();
|
||||||
|
let offer_handler = offer_handler.clone();
|
||||||
|
let pending_answer_handle = pending_answer.clone();
|
||||||
|
let cfg_signal_handle = cfg_signal.clone();
|
||||||
|
let initiator_connection_signal = initiator_connection.clone();
|
||||||
|
let peer_connection_signal = peer_connection.clone();
|
||||||
|
|
||||||
|
Rc::new(move || {
|
||||||
|
let mut ws_status = ws_status_signal.clone();
|
||||||
|
let connected = connected_signal.clone();
|
||||||
|
let mut websocket = websocket_signal.clone();
|
||||||
|
let cfg_signal = cfg_signal_handle.clone();
|
||||||
|
let initiator_connection = initiator_connection_signal.clone();
|
||||||
|
let peer_connection = peer_connection_signal.clone();
|
||||||
|
let pending_answer = pending_answer_handle.clone();
|
||||||
|
|
||||||
|
if *connected.read() || websocket.read().is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ws_status.set("Verbinde...".to_string());
|
ws_status.set("Verbinde...".to_string());
|
||||||
|
|
||||||
match BrowserWebSocket::new("ws://localhost:3478/ws") {
|
let endpoint = cfg_signal.read().server.signaling_url.trim().to_string();
|
||||||
|
let target = if endpoint.is_empty() {
|
||||||
|
crate::constants::DEFAULT_SIGNALING_URL.to_string()
|
||||||
|
} else {
|
||||||
|
endpoint
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!("🔌 Verbinde WebSocket zu {}", target);
|
||||||
|
|
||||||
|
match BrowserWebSocket::new(&target) {
|
||||||
Ok(socket) => {
|
Ok(socket) => {
|
||||||
socket.set_binary_type(BinaryType::Arraybuffer);
|
socket.set_binary_type(BinaryType::Arraybuffer);
|
||||||
|
|
||||||
@ -189,7 +222,8 @@ pub fn ConnectionPanel(
|
|||||||
log::info!("✅ WebSocket verbunden!");
|
log::info!("✅ WebSocket verbunden!");
|
||||||
ws_status_clone.set("Verbunden".to_string());
|
ws_status_clone.set("Verbunden".to_string());
|
||||||
connected_clone.set(true);
|
connected_clone.set(true);
|
||||||
}) as Box<dyn FnMut(web_sys::Event)>);
|
})
|
||||||
|
as Box<dyn FnMut(web_sys::Event)>);
|
||||||
|
|
||||||
// onclose Handler
|
// onclose Handler
|
||||||
let mut ws_status_clone2 = ws_status.clone();
|
let mut ws_status_clone2 = ws_status.clone();
|
||||||
@ -203,6 +237,7 @@ pub fn ConnectionPanel(
|
|||||||
|
|
||||||
// **MESSAGE ROUTER** - Leitet Messages an die richtigen Handler weiter
|
// **MESSAGE ROUTER** - Leitet Messages an die richtigen Handler weiter
|
||||||
let offer_tx = offer_handler.clone();
|
let offer_tx = offer_handler.clone();
|
||||||
|
let pending_answer_signal = pending_answer.clone();
|
||||||
let onmessage = Closure::wrap(Box::new(move |e: MessageEvent| {
|
let onmessage = Closure::wrap(Box::new(move |e: MessageEvent| {
|
||||||
if let Some(text) = e.data().as_string() {
|
if let Some(text) = e.data().as_string() {
|
||||||
log::info!("📨 WebSocket Nachricht: {}", text);
|
log::info!("📨 WebSocket Nachricht: {}", text);
|
||||||
@ -228,7 +263,9 @@ pub fn ConnectionPanel(
|
|||||||
} else {
|
} else {
|
||||||
// Buffer the answer until an initiator PC exists
|
// Buffer the answer until an initiator PC exists
|
||||||
log::warn!("⚠️ Keine Initiator-PeerConnection vorhanden - buffer Answer");
|
log::warn!("⚠️ Keine Initiator-PeerConnection vorhanden - buffer Answer");
|
||||||
pending_answer.set(Some(data_clone));
|
let mut pending_answer_slot =
|
||||||
|
pending_answer_signal.clone();
|
||||||
|
pending_answer_slot.set(Some(data_clone));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"candidate" => {
|
"candidate" => {
|
||||||
@ -271,7 +308,8 @@ pub fn ConnectionPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) as Box<dyn FnMut(MessageEvent)>);
|
})
|
||||||
|
as Box<dyn FnMut(MessageEvent)>);
|
||||||
|
|
||||||
socket.set_onopen(Some(onopen.as_ref().unchecked_ref()));
|
socket.set_onopen(Some(onopen.as_ref().unchecked_ref()));
|
||||||
socket.set_onclose(Some(onclose.as_ref().unchecked_ref()));
|
socket.set_onclose(Some(onclose.as_ref().unchecked_ref()));
|
||||||
@ -287,8 +325,16 @@ pub fn ConnectionPanel(
|
|||||||
log::error!("❌ WebSocket Fehler: {:?}", e);
|
log::error!("❌ WebSocket Fehler: {:?}", e);
|
||||||
ws_status.set("Verbindungsfehler".to_string());
|
ws_status.set("Verbindungsfehler".to_string());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let connect_logic = connect_logic.clone();
|
||||||
|
use_effect(move || {
|
||||||
|
connect_logic();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Wenn eine gepufferte Answer vorhanden ist und später eine Initiator-PC gesetzt wird,
|
// Wenn eine gepufferte Answer vorhanden ist und später eine Initiator-PC gesetzt wird,
|
||||||
// verarbeite die gepufferte Answer.
|
// verarbeite die gepufferte Answer.
|
||||||
@ -381,22 +427,6 @@ pub fn ConnectionPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section { class: "connection-card",
|
|
||||||
header { class: "connection-card__header",
|
|
||||||
h3 { "Networking" }
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
class: if *connected.read() { "btn btn--connected" } else { "btn" },
|
|
||||||
disabled: *connected.read(),
|
|
||||||
onclick: connect_websocket,
|
|
||||||
if *connected.read() {
|
|
||||||
"Connected"
|
|
||||||
} else {
|
|
||||||
"Connect"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,18 @@ use std::path::Path;
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct ServerOptions {
|
pub struct ServerOptions {
|
||||||
|
#[serde(default = "default_stun_server")]
|
||||||
pub stun_server: String,
|
pub stun_server: String,
|
||||||
|
#[serde(default = "default_signaling_url")]
|
||||||
|
pub signaling_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_stun_server() -> String {
|
||||||
|
crate::constants::DEFAULT_STUN_SERVER.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_signaling_url() -> String {
|
||||||
|
crate::constants::DEFAULT_SIGNALING_URL.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
@ -32,7 +43,9 @@ pub async fn load_config_from_server() -> Result<Config, Box<dyn std::error::Err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to fetching appsettings.json from the hosting origin
|
// Fallback to fetching appsettings.json from the hosting origin
|
||||||
let resp = gloo_net::http::Request::get("appsettings.json").send().await?;
|
let resp = gloo_net::http::Request::get("appsettings.json")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
let text = resp.text().await?;
|
let text = resp.text().await?;
|
||||||
let cfg: Config = serde_json::from_str(&text)?;
|
let cfg: Config = serde_json::from_str(&text)?;
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
@ -41,7 +54,6 @@ pub async fn load_config_from_server() -> Result<Config, Box<dyn std::error::Err
|
|||||||
// Try to read a JSON config injected into index.html inside a script tag
|
// Try to read a JSON config injected into index.html inside a script tag
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
async fn load_config_from_html() -> Result<Option<Config>, Box<dyn std::error::Error>> {
|
async fn load_config_from_html() -> Result<Option<Config>, Box<dyn std::error::Error>> {
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use web_sys::window;
|
use web_sys::window;
|
||||||
|
|
||||||
let win = window().ok_or("no window")?;
|
let win = window().ok_or("no window")?;
|
||||||
@ -58,10 +70,9 @@ async fn load_config_from_html() -> Result<Option<Config>, Box<dyn std::error::E
|
|||||||
// Synchronous HTML fast-path for WASM: read script#app-config synchronously if present.
|
// Synchronous HTML fast-path for WASM: read script#app-config synchronously if present.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn load_config_from_html_sync() -> Option<Config> {
|
pub fn load_config_from_html_sync() -> Option<Config> {
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use web_sys::window;
|
use web_sys::window;
|
||||||
|
|
||||||
let win = window().ok()?;
|
let win = window()?;
|
||||||
let doc = win.document()?;
|
let doc = win.document()?;
|
||||||
let elem = doc.get_element_by_id("app-config")?;
|
let elem = doc.get_element_by_id("app-config")?;
|
||||||
if let Some(text) = elem.text_content() {
|
if let Some(text) = elem.text_content() {
|
||||||
@ -91,7 +102,12 @@ pub fn load_config_sync_or_default() -> Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback default
|
// Fallback default
|
||||||
Config { server: ServerOptions { stun_server: crate::constants::DEFAULT_STUN_SERVER.to_string() } }
|
Config {
|
||||||
|
server: ServerOptions {
|
||||||
|
stun_server: crate::constants::DEFAULT_STUN_SERVER.to_string(),
|
||||||
|
signaling_url: crate::constants::DEFAULT_SIGNALING_URL.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Native loader convenience wrapper (blocking-friendly)
|
// Native loader convenience wrapper (blocking-friendly)
|
||||||
@ -109,5 +125,10 @@ pub async fn load_config_or_default() -> Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback default
|
// Fallback default
|
||||||
Config { server: ServerOptions { stun_server: crate::constants::DEFAULT_STUN_SERVER.to_string() } }
|
Config {
|
||||||
|
server: ServerOptions {
|
||||||
|
stun_server: crate::constants::DEFAULT_STUN_SERVER.to_string(),
|
||||||
|
signaling_url: crate::constants::DEFAULT_SIGNALING_URL.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
// Central constants for niom-webrtc
|
// Central constants for niom-webrtc
|
||||||
pub const DEFAULT_STUN_SERVER: &str = "stun:stun.l.google.com:19302";
|
pub const DEFAULT_STUN_SERVER: &str = "stun:stun.l.google.com:19302";
|
||||||
|
pub const DEFAULT_SIGNALING_URL: &str = "ws://localhost:3478/ws";
|
||||||
pub const ASSET_FAVICON: &str = "/assets/favicon.ico";
|
pub const ASSET_FAVICON: &str = "/assets/favicon.ico";
|
||||||
pub const ASSET_MAIN_CSS: &str = "/assets/main.css";
|
pub const ASSET_MAIN_CSS: &str = "/assets/main.css";
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
// Library root for niom-webrtc so integration tests and other crates can depend on the modules.
|
// Library root for niom-webrtc so integration tests and other crates can depend on the modules.
|
||||||
pub mod components;
|
pub mod components;
|
||||||
pub mod models;
|
|
||||||
pub mod utils;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
|
pub mod models;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
// Re-export commonly used items if needed in the future
|
// Re-export commonly used items if needed in the future
|
||||||
// pub use config::*;
|
// pub use config::*;
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
|
use crate::models::MediaState;
|
||||||
|
use js_sys::Reflect;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen_futures::JsFuture;
|
use wasm_bindgen_futures::JsFuture;
|
||||||
use web_sys::{
|
use web_sys::{
|
||||||
MediaStream, MediaStreamConstraints,
|
MediaStream, MediaStreamConstraints, RtcConfiguration, RtcIceServer, RtcPeerConnection,
|
||||||
RtcPeerConnection, RtcConfiguration, RtcIceServer,
|
RtcSdpType, RtcSessionDescriptionInit,
|
||||||
RtcSessionDescriptionInit, RtcSdpType,
|
|
||||||
};
|
};
|
||||||
use js_sys::Reflect;
|
|
||||||
use crate::models::MediaState;
|
|
||||||
|
|
||||||
pub struct MediaManager {
|
pub struct MediaManager {
|
||||||
pub state: MediaState,
|
pub state: MediaState,
|
||||||
@ -59,7 +58,10 @@ impl MediaManager {
|
|||||||
.map_err(|e| format!("set_local_description failed: {:?}", e))?;
|
.map_err(|e| format!("set_local_description failed: {:?}", e))?;
|
||||||
|
|
||||||
log::info!("✅ Offer SDP length: {}", sdp.len());
|
log::info!("✅ Offer SDP length: {}", sdp.len());
|
||||||
log::debug!("📋 SDP-Preview: {}...", &sdp[..std::cmp::min(150, sdp.len())]);
|
log::debug!(
|
||||||
|
"📋 SDP-Preview: {}...",
|
||||||
|
&sdp[..std::cmp::min(150, sdp.len())]
|
||||||
|
);
|
||||||
|
|
||||||
Ok(sdp)
|
Ok(sdp)
|
||||||
}
|
}
|
||||||
@ -129,17 +131,18 @@ impl MediaManager {
|
|||||||
return Err("WebRTC not supported".into());
|
return Err("WebRTC not supported".into());
|
||||||
}
|
}
|
||||||
self.state = MediaState::Requesting;
|
self.state = MediaState::Requesting;
|
||||||
let navigator = web_sys::window()
|
let navigator = web_sys::window().ok_or("No window")?.navigator();
|
||||||
.ok_or("No window")?
|
|
||||||
.navigator();
|
|
||||||
let devices = navigator
|
let devices = navigator
|
||||||
.media_devices()
|
.media_devices()
|
||||||
.map_err(|_| "MediaDevices not available")?;
|
.map_err(|_| "MediaDevices not available")?;
|
||||||
let constraints = MediaStreamConstraints::new();
|
let constraints = MediaStreamConstraints::new();
|
||||||
constraints.set_audio(&JsValue::from(true));
|
constraints.set_audio(&JsValue::from(true));
|
||||||
constraints.set_video(&JsValue::from(false));
|
constraints.set_video(&JsValue::from(false));
|
||||||
let js_stream = JsFuture::from(devices.get_user_media_with_constraints(&constraints)
|
let js_stream = JsFuture::from(
|
||||||
.map_err(|e| format!("getUserMedia error: {:?}", e))?)
|
devices
|
||||||
|
.get_user_media_with_constraints(&constraints)
|
||||||
|
.map_err(|e| format!("getUserMedia error: {:?}", e))?,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("getUserMedia promise rejected: {:?}", e))?;
|
.map_err(|e| format!("getUserMedia promise rejected: {:?}", e))?;
|
||||||
let stream: MediaStream = js_stream
|
let stream: MediaStream = js_stream
|
||||||
@ -156,9 +159,8 @@ impl MediaManager {
|
|||||||
// 1. JsValue holen
|
// 1. JsValue holen
|
||||||
let js_val = tracks.get(i);
|
let js_val = tracks.get(i);
|
||||||
// 2. In MediaStreamTrack casten
|
// 2. In MediaStreamTrack casten
|
||||||
let track: web_sys::MediaStreamTrack = js_val
|
let track: web_sys::MediaStreamTrack =
|
||||||
.dyn_into()
|
js_val.dyn_into().expect("Expected MediaStreamTrack");
|
||||||
.expect("Expected MediaStreamTrack");
|
|
||||||
// 3. Stoppen
|
// 3. Stoppen
|
||||||
track.stop();
|
track.stop();
|
||||||
log::info!("\u{1F6D1} Track gestoppt: {}", track.label());
|
log::info!("\u{1F6D1} Track gestoppt: {}", track.label());
|
||||||
@ -184,9 +186,16 @@ impl MediaManager {
|
|||||||
// RtcRtpSender zurück; wir ignorieren den Rückgabewert hier.
|
// RtcRtpSender zurück; wir ignorieren den Rückgabewert hier.
|
||||||
// `addTrack` ist in manchen web-sys-Versionen nicht direkt verfügbar.
|
// `addTrack` ist in manchen web-sys-Versionen nicht direkt verfügbar.
|
||||||
// Wir rufen die JS-Funktion dynamisch auf: pc.addTrack(track, stream)
|
// Wir rufen die JS-Funktion dynamisch auf: pc.addTrack(track, stream)
|
||||||
let add_fn = js_sys::Reflect::get(pc.as_ref(), &JsValue::from_str("addTrack")).map_err(|_| "Failed to get addTrack function".to_string())?;
|
let add_fn = js_sys::Reflect::get(pc.as_ref(), &JsValue::from_str("addTrack"))
|
||||||
let func: js_sys::Function = add_fn.dyn_into().map_err(|_| "addTrack is not a function".to_string())?;
|
.map_err(|_| "Failed to get addTrack function".to_string())?;
|
||||||
let _ = func.call2(pc.as_ref(), &JsValue::from(track.clone()), &JsValue::from(stream.clone()));
|
let func: js_sys::Function = add_fn
|
||||||
|
.dyn_into()
|
||||||
|
.map_err(|_| "addTrack is not a function".to_string())?;
|
||||||
|
let _ = func.call2(
|
||||||
|
pc.as_ref(),
|
||||||
|
&JsValue::from(track.clone()),
|
||||||
|
&JsValue::from(stream.clone()),
|
||||||
|
);
|
||||||
log::info!("\u{2705} Track hinzugefügt: {}", track.label());
|
log::info!("\u{2705} Track hinzugefügt: {}", track.label());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -220,7 +229,9 @@ impl MediaManager {
|
|||||||
// Call pc.addIceCandidate(obj) dynamically
|
// Call pc.addIceCandidate(obj) dynamically
|
||||||
let add_fn = js_sys::Reflect::get(pc.as_ref(), &JsValue::from_str("addIceCandidate"))
|
let add_fn = js_sys::Reflect::get(pc.as_ref(), &JsValue::from_str("addIceCandidate"))
|
||||||
.map_err(|_| "Failed to get addIceCandidate function".to_string())?;
|
.map_err(|_| "Failed to get addIceCandidate function".to_string())?;
|
||||||
let func: js_sys::Function = add_fn.dyn_into().map_err(|_| "addIceCandidate is not a function".to_string())?;
|
let func: js_sys::Function = add_fn
|
||||||
|
.dyn_into()
|
||||||
|
.map_err(|_| "addIceCandidate is not a function".to_string())?;
|
||||||
let _ = func.call1(pc.as_ref(), &obj);
|
let _ = func.call1(pc.as_ref(), &obj);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,14 @@ fn sync_loader_returns_default_when_no_file() {
|
|||||||
// Ensure there's no appsettings.json in CWD for this test
|
// Ensure there's no appsettings.json in CWD for this test
|
||||||
let _ = fs::remove_file("appsettings.json");
|
let _ = fs::remove_file("appsettings.json");
|
||||||
let cfg = load_config_sync_or_default();
|
let cfg = load_config_sync_or_default();
|
||||||
assert_eq!(cfg.server.stun_server, niom_webrtc::constants::DEFAULT_STUN_SERVER.to_string());
|
assert_eq!(
|
||||||
|
cfg.server.stun_server,
|
||||||
|
niom_webrtc::constants::DEFAULT_STUN_SERVER.to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
cfg.server.signaling_url,
|
||||||
|
niom_webrtc::constants::DEFAULT_SIGNALING_URL.to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test ensures the function compiles and returns a Config; on native it will use the file-path,
|
// This test ensures the function compiles and returns a Config; on native it will use the file-path,
|
||||||
@ -16,4 +23,5 @@ fn sync_loader_api_callable() {
|
|||||||
let cfg = load_config_sync_or_default();
|
let cfg = load_config_sync_or_default();
|
||||||
// At minimum we have a non-empty stun_server
|
// At minimum we have a non-empty stun_server
|
||||||
assert!(!cfg.server.stun_server.is_empty());
|
assert!(!cfg.server.stun_server.is_empty());
|
||||||
|
assert!(!cfg.server.signaling_url.is_empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use niom_webrtc::constants::DEFAULT_STUN_SERVER;
|
|
||||||
use niom_webrtc::config::Config;
|
use niom_webrtc::config::Config;
|
||||||
|
use niom_webrtc::constants::{DEFAULT_SIGNALING_URL, DEFAULT_STUN_SERVER};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_stun_server_present() {
|
fn default_stun_server_present() {
|
||||||
@ -10,8 +10,16 @@ fn default_stun_server_present() {
|
|||||||
fn config_from_file_roundtrip() {
|
fn config_from_file_roundtrip() {
|
||||||
// Create a temporary JSON in /tmp and read it via Config::from_file
|
// Create a temporary JSON in /tmp and read it via Config::from_file
|
||||||
let tmp = tempfile::NamedTempFile::new().expect("tempfile");
|
let tmp = tempfile::NamedTempFile::new().expect("tempfile");
|
||||||
let cfg_json = r#"{ "server": { "stun_server": "stun:example.org:3478" } }"#;
|
let cfg_json = r#"{ "server": { "stun_server": "stun:example.org:3478", "signaling_url": "ws://example.org/ws" } }"#;
|
||||||
std::fs::write(tmp.path(), cfg_json).expect("write");
|
std::fs::write(tmp.path(), cfg_json).expect("write");
|
||||||
let cfg = Config::from_file(tmp.path()).expect("load");
|
let cfg = Config::from_file(tmp.path()).expect("load");
|
||||||
assert_eq!(cfg.server.stun_server, "stun:example.org:3478");
|
assert_eq!(cfg.server.stun_server, "stun:example.org:3478");
|
||||||
|
assert_eq!(cfg.server.signaling_url, "ws://example.org/ws");
|
||||||
|
|
||||||
|
// Missing fields fall back to defaults
|
||||||
|
let cfg_json_missing = r#"{ "server": { "stun_server": "stun:another.org:9999" } }"#;
|
||||||
|
std::fs::write(tmp.path(), cfg_json_missing).expect("write missing");
|
||||||
|
let cfg_missing = Config::from_file(tmp.path()).expect("load missing");
|
||||||
|
assert_eq!(cfg_missing.server.stun_server, "stun:another.org:9999");
|
||||||
|
assert_eq!(cfg_missing.server.signaling_url, DEFAULT_SIGNALING_URL);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user