browser_webrtc_example/
lib.rs

1#![cfg(target_arch = "wasm32")]
2
3use std::{io, time::Duration};
4
5use futures::StreamExt;
6use js_sys::Date;
7use libp2p::{core::Multiaddr, ping, swarm::SwarmEvent};
8use libp2p_webrtc_websys as webrtc_websys;
9use wasm_bindgen::prelude::*;
10use web_sys::{Document, HtmlElement};
11
12#[wasm_bindgen]
13pub async fn run(libp2p_endpoint: String) -> Result<(), JsError> {
14    tracing_wasm::set_as_global_default();
15
16    let ping_duration = Duration::from_secs(30);
17
18    let body = Body::from_current_window()?;
19    body.append_p(&format!(
20        "Let's ping the rust-libp2p server over WebRTC for {:?}:",
21        ping_duration
22    ))?;
23
24    let mut swarm = libp2p::SwarmBuilder::with_new_identity()
25        .with_wasm_bindgen()
26        .with_other_transport(|key| {
27            webrtc_websys::Transport::new(webrtc_websys::Config::new(&key))
28        })?
29        .with_behaviour(|_| ping::Behaviour::new(ping::Config::new()))?
30        .with_swarm_config(|c| c.with_idle_connection_timeout(ping_duration))
31        .build();
32
33    let addr = libp2p_endpoint.parse::<Multiaddr>()?;
34    tracing::info!("Dialing {addr}");
35    swarm.dial(addr)?;
36
37    loop {
38        match swarm.next().await.unwrap() {
39            SwarmEvent::Behaviour(ping::Event { result: Err(e), .. }) => {
40                tracing::error!("Ping failed: {:?}", e);
41
42                break;
43            }
44            SwarmEvent::Behaviour(ping::Event {
45                peer,
46                result: Ok(rtt),
47                ..
48            }) => {
49                tracing::info!("Ping successful: RTT: {rtt:?}, from {peer}");
50                body.append_p(&format!("RTT: {rtt:?} at {}", Date::new_0().to_string()))?;
51            }
52            SwarmEvent::ConnectionClosed {
53                cause: Some(cause), ..
54            } => {
55                tracing::info!("Swarm event: {:?}", cause);
56
57                if let libp2p::swarm::ConnectionError::KeepAliveTimeout = cause {
58                    body.append_p("All done with pinging! ")?;
59
60                    break;
61                }
62                body.append_p(&format!("Connection closed due to: {:?}", cause))?;
63            }
64            evt => tracing::info!("Swarm event: {:?}", evt),
65        }
66    }
67
68    Ok(())
69}
70
71/// Convenience wrapper around the current document body
72struct Body {
73    body: HtmlElement,
74    document: Document,
75}
76
77impl Body {
78    fn from_current_window() -> Result<Self, JsError> {
79        // Use `web_sys`'s global `window` function to get a handle on the global
80        // window object.
81        let document = web_sys::window()
82            .ok_or(js_error("no global `window` exists"))?
83            .document()
84            .ok_or(js_error("should have a document on window"))?;
85        let body = document
86            .body()
87            .ok_or(js_error("document should have a body"))?;
88
89        Ok(Self { body, document })
90    }
91
92    fn append_p(&self, msg: &str) -> Result<(), JsError> {
93        let val = self
94            .document
95            .create_element("p")
96            .map_err(|_| js_error("failed to create <p>"))?;
97        val.set_text_content(Some(msg));
98        self.body
99            .append_child(&val)
100            .map_err(|_| js_error("failed to append <p>"))?;
101
102        Ok(())
103    }
104}
105
106fn js_error(msg: &str) -> JsError {
107    io::Error::new(io::ErrorKind::Other, msg).into()
108}