libp2p_webrtc_websys/
transport.rs

1use std::{
2    future::Future,
3    pin::Pin,
4    task::{Context, Poll},
5};
6
7use futures::future::FutureExt;
8use libp2p_core::{
9    multiaddr::Multiaddr,
10    muxing::StreamMuxerBox,
11    transport::{Boxed, DialOpts, ListenerId, Transport as _, TransportError, TransportEvent},
12};
13use libp2p_identity::{Keypair, PeerId};
14
15use super::{upgrade, Connection, Error};
16
17/// Config for the [`Transport`].
18#[derive(Clone)]
19pub struct Config {
20    keypair: Keypair,
21}
22
23/// A WebTransport [`Transport`](libp2p_core::Transport) that works with `web-sys`.
24pub struct Transport {
25    config: Config,
26}
27
28impl Config {
29    /// Constructs a new configuration for the [`Transport`].
30    pub fn new(keypair: &Keypair) -> Self {
31        Config {
32            keypair: keypair.to_owned(),
33        }
34    }
35}
36
37impl Transport {
38    /// Constructs a new `Transport` with the given [`Config`].
39    pub fn new(config: Config) -> Transport {
40        Transport { config }
41    }
42
43    /// Wraps `Transport` in [`Boxed`] and makes it ready to be consumed by
44    /// SwarmBuilder.
45    pub fn boxed(self) -> Boxed<(PeerId, StreamMuxerBox)> {
46        self.map(|(peer_id, muxer), _| (peer_id, StreamMuxerBox::new(muxer)))
47            .boxed()
48    }
49}
50
51impl libp2p_core::Transport for Transport {
52    type Output = (PeerId, Connection);
53    type Error = Error;
54    type ListenerUpgrade = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
55    type Dial = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
56
57    fn listen_on(
58        &mut self,
59        _id: ListenerId,
60        addr: Multiaddr,
61    ) -> Result<(), TransportError<Self::Error>> {
62        Err(TransportError::MultiaddrNotSupported(addr))
63    }
64
65    fn remove_listener(&mut self, _id: ListenerId) -> bool {
66        false
67    }
68
69    fn dial(
70        &mut self,
71        addr: Multiaddr,
72        dial_opts: DialOpts,
73    ) -> Result<Self::Dial, TransportError<Self::Error>> {
74        if dial_opts.role.is_listener() {
75            return Err(TransportError::MultiaddrNotSupported(addr));
76        }
77
78        if maybe_local_firefox() {
79            return Err(TransportError::Other(
80                "Firefox does not support WebRTC over localhost or 127.0.0.1"
81                    .to_string()
82                    .into(),
83            ));
84        }
85
86        let (sock_addr, server_fingerprint) = libp2p_webrtc_utils::parse_webrtc_dial_addr(&addr)
87            .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?;
88
89        if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() {
90            return Err(TransportError::MultiaddrNotSupported(addr));
91        }
92
93        let config = self.config.clone();
94
95        Ok(async move {
96            let (peer_id, connection) =
97                upgrade::outbound(sock_addr, server_fingerprint, config.keypair.clone()).await?;
98
99            Ok((peer_id, connection))
100        }
101        .boxed())
102    }
103
104    fn poll(
105        self: Pin<&mut Self>,
106        _cx: &mut Context<'_>,
107    ) -> Poll<TransportEvent<Self::ListenerUpgrade, Self::Error>> {
108        Poll::Pending
109    }
110}
111
112/// Checks if local Firefox.
113///
114/// See: `<https://bugzilla.mozilla.org/show_bug.cgi?id=1659672>` for more details
115fn maybe_local_firefox() -> bool {
116    let window = &web_sys::window().expect("window should be available");
117    let ua = match window.navigator().user_agent() {
118        Ok(agent) => agent.to_lowercase(),
119        Err(_) => return false,
120    };
121
122    let hostname = match window
123        .document()
124        .expect("should be valid document")
125        .location()
126    {
127        Some(location) => match location.hostname() {
128            Ok(hostname) => hostname,
129            Err(_) => return false,
130        },
131        None => return false,
132    };
133
134    // check if web_sys::Navigator::user_agent() matches any of the following:
135    // - firefox
136    // - seamonkey
137    // - iceape
138    // AND hostname is either localhost or  "127.0.0.1"
139    (ua.contains("firefox") || ua.contains("seamonkey") || ua.contains("iceape"))
140        && (hostname == "localhost" || hostname == "127.0.0.1" || hostname == "[::1]")
141}