cyberdeck_client_web_sys/
lib.rs

1use std::{collections::HashMap, cell::RefCell, rc::Rc};
2
3use js_sys::{Reflect, JSON, Object, Array, JsString};
4use wasm_bindgen::{prelude::Closure, JsValue, JsCast};
5use web_sys::{Request, RequestInit, RequestMode, Response, RtcPeerConnection, RtcDataChannel, RtcConfiguration, RtcSessionDescriptionInit, window };
6
7/// Create an RtcPeerConnection with the given ICE/STUN server, defaulting to Google's STUN server
8pub fn create_peer_connection(ice_server: Option<String>) -> Rc<RefCell<RtcPeerConnection>> {
9    let mut config = RtcConfiguration::new();
10    let config_servers = Array::new(); 
11    let ice_server_js = Object::new();
12    Reflect::set(&ice_server_js, &"urls".into(), &ice_server.unwrap_or("stun:stun.l.google.com:19302".to_string()).into()).unwrap();
13    config_servers.push(&ice_server_js);
14    config.ice_servers(&config_servers);
15
16    Rc::new(RefCell::new(RtcPeerConnection::new_with_configuration(&config).expect("Failed to create RTCPeerConnection")))
17}
18
19/// Initialize RtcPeerConnection using selected signalling server endpoint, defaulting to "http://localhost:3000/connect"
20pub async fn init_peer_connection(pc: Rc<RefCell<RtcPeerConnection>>, connect_url: Option<String>, oniceconnectionstatechange: Closure<dyn Fn(JsValue)>) {
21    pc.borrow().set_oniceconnectionstatechange(Some(&oniceconnectionstatechange.into_js_value().unchecked_into()));
22
23    let pc_clone = pc.clone();
24    let connect_url = connect_url.unwrap_or("http://localhost:3000/connect".to_string()).clone();
25
26    let onicecandidate = Closure::<dyn Fn(JsValue)>::new(move |event: JsValue| {    
27        if Reflect::get(&event, &"candidate".into()).unwrap().is_null() {
28            let local_description = window().unwrap().btoa(&JSON::stringify(&pc_clone.borrow().local_description().unwrap().unchecked_into()).unwrap().as_string().unwrap()).unwrap();
29            let mut opts = RequestInit::new();
30            opts.method("POST");
31            opts.mode(RequestMode::Cors);
32            opts.body(Some(&JSON::stringify(&local_description.into()).unwrap()));
33            
34            let mut headers = HashMap::new();
35            headers.insert("Content-Type", "application/json");
36
37            opts.headers(&serde_wasm_bindgen::to_value(&headers).unwrap());
38            let request = Request::new_with_str_and_init(&connect_url, &opts).unwrap();
39
40            let pc_clone_2 = pc_clone.clone();
41            let then = Closure::<dyn FnMut(JsValue)>::new(move |answer: JsValue| {
42                let answer: Response = answer.unchecked_into();
43                
44                let pc_clone_3 = pc_clone_2.clone();
45                let then = Closure::<dyn FnMut(JsValue)>::new(move |answer: JsValue| {
46                    let answer: String = answer.unchecked_into::<JsString>().into();
47                    let answer = answer.replace("\"", "");
48                    let atob = window().unwrap().atob(&answer).unwrap();
49                    let parsed = JSON::parse(&atob).unwrap();
50                    pc_clone_3.borrow().set_remote_description(
51                        &RtcSessionDescriptionInit::unchecked_from_js(parsed)
52                    );
53                });
54
55                answer.text().unwrap().then(&then);
56                then.into_js_value();
57            });
58
59            let _fetch = wasm_bindgen_futures::JsFuture::from(window().unwrap().fetch_with_request(&request).then(&then));
60            then.into_js_value();
61        }
62    });
63
64    pc.borrow().set_onicecandidate(Some(&onicecandidate.into_js_value().unchecked_into()));
65
66    let pc_clone = pc.clone();
67    let onnegotiationneeded = Closure::<dyn Fn()>::new(move || {
68        let pc_clone_2 = pc_clone.clone();
69        let then = Closure::<dyn FnMut(JsValue)>::new(move |d: JsValue| {
70            pc_clone_2.borrow().set_local_description(&d.unchecked_into());
71        });
72        pc_clone.borrow().create_offer().then(&then);
73        then.into_js_value();
74    });
75    pc.borrow().set_onnegotiationneeded(Some(&onnegotiationneeded.into_js_value().unchecked_into()));
76}
77
78/// Create a data channel using the given RtcPeerConnection, assigned the given label
79pub fn create_data_channel(pc: Rc<RefCell<RtcPeerConnection>>, label: &str) -> Rc<RefCell<RtcDataChannel>> {
80    Rc::new(RefCell::new(pc.borrow().create_data_channel(label)))
81}
82
83/// Initialize an RtcDataChannel, with the given callback Closures
84pub fn init_data_channel(channel: Rc<RefCell<RtcDataChannel>>, onclose: Closure<dyn Fn()>, onopen: Closure<dyn Fn()>, onmessage: Closure<dyn Fn(JsValue)>) {
85    channel.borrow().set_onclose(Some(&onclose.into_js_value().unchecked_into()));
86    channel.borrow().set_onclose(Some(&onopen.into_js_value().unchecked_into()));
87    channel.borrow().set_onmessage(Some(&onmessage.into_js_value().unchecked_into()));
88}