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#[derive(Debug, Clone)]
16pub enum ConnectionType {
17 Local,
19 Stun { urls: String },
21 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}