Doug A f5e644da8f
feat(webrtc): add WebRTC for WASM environments
This PR implements `Transport` for WebRTC for browsers by using web-sys. Only the `webrtc-direct` spec is implemented. The `webrtc` spec for connecting two browsers with each other is left to a future PR.

Related: https://github.com/libp2p/specs/issues/475.
Related #2617.
Supersedes: #4229.

Pull-Request: #4248.

Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
2023-09-17 19:13:11 +00:00

105 lines
3.0 KiB
Rust

#![cfg(target_arch = "wasm32")]
use futures::StreamExt;
use js_sys::Date;
use libp2p::core::Multiaddr;
use libp2p::identity::{Keypair, PeerId};
use libp2p::ping;
use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmBuilder, SwarmEvent};
use std::convert::From;
use std::io;
use wasm_bindgen::prelude::*;
use web_sys::{Document, HtmlElement};
#[wasm_bindgen]
pub async fn run(libp2p_endpoint: String) -> Result<(), JsError> {
wasm_logger::init(wasm_logger::Config::default());
let body = Body::from_current_window()?;
body.append_p("Let's ping the WebRTC Server!")?;
let local_key = Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());
let mut swarm = SwarmBuilder::with_wasm_executor(
libp2p_webrtc_websys::Transport::new(libp2p_webrtc_websys::Config::new(&local_key)).boxed(),
Behaviour {
ping: ping::Behaviour::new(ping::Config::new()),
keep_alive: keep_alive::Behaviour,
},
local_peer_id,
)
.build();
log::info!("Initialize swarm with identity: {local_peer_id}");
let addr = libp2p_endpoint.parse::<Multiaddr>()?;
log::info!("Dialing {addr}");
swarm.dial(addr)?;
loop {
match swarm.next().await.unwrap() {
SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event { result: Err(e), .. })) => {
log::error!("Ping failed: {:?}", e);
break;
}
SwarmEvent::Behaviour(BehaviourEvent::Ping(ping::Event {
peer,
result: Ok(rtt),
..
})) => {
log::info!("Ping successful: RTT: {rtt:?}, from {peer}");
body.append_p(&format!("RTT: {rtt:?} at {}", Date::new_0().to_string()))?;
}
evt => log::info!("Swarm event: {:?}", evt),
}
}
Ok(())
}
#[derive(NetworkBehaviour)]
struct Behaviour {
ping: ping::Behaviour,
keep_alive: keep_alive::Behaviour,
}
/// Convenience wrapper around the current document body
struct Body {
body: HtmlElement,
document: Document,
}
impl Body {
fn from_current_window() -> Result<Self, JsError> {
// Use `web_sys`'s global `window` function to get a handle on the global
// window object.
let document = web_sys::window()
.ok_or(js_error("no global `window` exists"))?
.document()
.ok_or(js_error("should have a document on window"))?;
let body = document
.body()
.ok_or(js_error("document should have a body"))?;
Ok(Self { body, document })
}
fn append_p(&self, msg: &str) -> Result<(), JsError> {
let val = self
.document
.create_element("p")
.map_err(|_| js_error("failed to create <p>"))?;
val.set_text_content(Some(msg));
self.body
.append_child(&val)
.map_err(|_| js_error("failed to append <p>"))?;
Ok(())
}
}
fn js_error(msg: &str) -> JsError {
io::Error::new(io::ErrorKind::Other, msg).into()
}