wasm_peers/
utils.rs

1use js_sys::{Array, Object, Reflect};
2use serde::{Deserialize, Serialize};
3use wasm_bindgen::JsValue;
4use wasm_bindgen_futures::JsFuture;
5use web_sys::{RtcConfiguration, RtcPeerConnection, RtcSdpType, RtcSessionDescriptionInit};
6
7#[derive(Serialize, Deserialize, Debug, Clone)]
8pub(crate) struct IceCandidate {
9    pub candidate: String,
10    pub sdp_mid: Option<String>,
11    pub sdp_m_line_index: Option<u16>,
12}
13
14/// Specifies what kind of peer connection to create
15#[derive(Debug, Clone)]
16pub enum ConnectionType {
17    /// Within local network
18    Local,
19    /// Setup with STUN server, WAN capabilities but can fail
20    Stun { urls: String },
21    /// Setup with STUN and TURN servers and fallback to TURN if needed, most stable connection
22    StunAndTurn {
23        stun_urls: String,
24        turn_urls: String,
25        username: String,
26        credential: String,
27    },
28}
29
30pub(crate) fn create_peer_connection(
31    connection_type: &ConnectionType,
32) -> Result<RtcPeerConnection, JsValue> {
33    match connection_type {
34        ConnectionType::Local => RtcPeerConnection::new(),
35        ConnectionType::Stun { urls } => {
36            let ice_servers = Array::new();
37            {
38                let server_entry = Object::new();
39
40                Reflect::set(&server_entry, &"urls".into(), &urls.into())?;
41
42                ice_servers.push(&server_entry);
43            }
44
45            let mut rtc_configuration = RtcConfiguration::new();
46            rtc_configuration.ice_servers(&ice_servers);
47
48            RtcPeerConnection::new_with_configuration(&rtc_configuration)
49        }
50        ConnectionType::StunAndTurn {
51            stun_urls,
52            turn_urls,
53            username,
54            credential,
55        } => {
56            let ice_servers = Array::new();
57            {
58                let stun_server_entry = Object::new();
59
60                Reflect::set(&stun_server_entry, &"urls".into(), &stun_urls.into())?;
61
62                ice_servers.push(&stun_server_entry);
63            }
64            {
65                let turn_server_entry = Object::new();
66
67                Reflect::set(&turn_server_entry, &"urls".into(), &turn_urls.into())?;
68                Reflect::set(&turn_server_entry, &"username".into(), &username.into())?;
69                Reflect::set(&turn_server_entry, &"credential".into(), &credential.into())?;
70
71                ice_servers.push(&turn_server_entry);
72            }
73
74            let mut rtc_configuration = RtcConfiguration::new();
75            rtc_configuration.ice_servers(&ice_servers);
76
77            RtcPeerConnection::new_with_configuration(&rtc_configuration)
78        }
79    }
80}
81
82pub(crate) async fn create_sdp_offer(
83    peer_connection: &RtcPeerConnection,
84) -> Result<String, JsValue> {
85    let offer = JsFuture::from(peer_connection.create_offer())
86        .await
87        .map_err(|error| {
88            JsValue::from_str(&format!(
89                "failed to create an SDP offer: {}",
90                error.as_string().unwrap_or_default()
91            ))
92        })?;
93    let offer = Reflect::get(&offer, &JsValue::from_str("sdp"))?
94        .as_string()
95        .expect("failed to create JS object for SDP offer");
96    let mut local_session_description = RtcSessionDescriptionInit::new(RtcSdpType::Offer);
97    local_session_description.sdp(&offer);
98    JsFuture::from(peer_connection.set_local_description(&local_session_description))
99        .await
100        .map_err(|error| {
101            JsValue::from_str(&format!(
102                "failed to set local description: {}",
103                error.as_string().unwrap_or_default()
104            ))
105        })?;
106
107    Ok(offer)
108}
109
110pub(crate) async fn create_sdp_answer(
111    peer_connection: &RtcPeerConnection,
112    offer: String,
113) -> Result<String, JsValue> {
114    let mut remote_session_description = RtcSessionDescriptionInit::new(RtcSdpType::Offer);
115    remote_session_description.sdp(&offer);
116    JsFuture::from(peer_connection.set_remote_description(&remote_session_description)).await?;
117
118    let answer = JsFuture::from(peer_connection.create_answer()).await?;
119    let answer = Reflect::get(&answer, &JsValue::from_str("sdp"))?
120        .as_string()
121        .expect("failed to create JS object for SPD answer");
122
123    let mut local_session_description = RtcSessionDescriptionInit::new(RtcSdpType::Answer);
124    local_session_description.sdp(&answer);
125    JsFuture::from(peer_connection.set_local_description(&local_session_description)).await?;
126
127    Ok(answer)
128}
129
130#[cfg(test)]
131mod test {
132    use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
133    use web_sys::{RtcIceConnectionState, RtcIceGatheringState};
134
135    use super::*;
136
137    wasm_bindgen_test_configure!(run_in_browser);
138
139    #[wasm_bindgen_test]
140    fn test_create_stun_peer_connection_is_successful() {
141        let peer_connection = create_peer_connection(&ConnectionType::Local)
142            .expect("creating peer connection failed!");
143        assert_eq!(
144            peer_connection.ice_connection_state(),
145            RtcIceConnectionState::New
146        );
147        assert_eq!(
148            peer_connection.ice_gathering_state(),
149            RtcIceGatheringState::New
150        );
151    }
152
153    #[wasm_bindgen_test]
154    async fn test_create_sdp_offer_is_successful() {
155        let peer_connection = RtcPeerConnection::new().expect("failed to create peer connection");
156        let _offer = create_sdp_offer(&peer_connection).await.unwrap();
157        assert!(peer_connection.local_description().is_some());
158    }
159
160    #[wasm_bindgen_test]
161    async fn test_create_sdp_answer_is_successful() {
162        let peer_connection = RtcPeerConnection::new().expect("failed to create peer connection");
163        let offer = create_sdp_offer(&peer_connection).await.unwrap();
164        let _answer = create_sdp_answer(&peer_connection, offer).await.unwrap();
165        assert!(peer_connection.local_description().is_some());
166        assert!(peer_connection.remote_description().is_some());
167    }
168}