diff --git a/crates/humanode-peer/src/cli/config.rs b/crates/humanode-peer/src/cli/config.rs index 727fb55ac..54f7deb55 100644 --- a/crates/humanode-peer/src/cli/config.rs +++ b/crates/humanode-peer/src/cli/config.rs @@ -2,7 +2,7 @@ use sc_chain_spec::get_extension; -use super::{params, BioauthFlowParams}; +use super::{params, BioauthFlowParams, RpcUrlSchemePreference}; use crate::{ chain_spec::Extensions, configuration::{self, Configuration}, @@ -28,8 +28,9 @@ pub trait CliConfigurationExt: SubstrateCliConfigurationProvider { .unwrap_or_default(); let bioauth_flow = self.bioauth_params().map(|params| { - let rpc_port = substrate.rpc_http.map(|v| v.port()); - let rpc_url = rpc_url_from_params(params, rpc_port); + let rpc_http_port = substrate.rpc_http.map(|v| v.port()); + let rpc_ws_port = substrate.rpc_ws.map(|v| v.port()); + let rpc_url = rpc_url_from_params(params, rpc_http_port, rpc_ws_port); configuration::BioauthFlow { rpc_url_resolver: Default::default(), @@ -86,7 +87,11 @@ impl SubstrateCliConfigurationProvider for T { } /// Construct an RPC URL from the bioauth flow params and an RPC endpoint port. -fn rpc_url_from_params(params: &BioauthFlowParams, rpc_port: Option) -> RpcUrl { +fn rpc_url_from_params( + params: &BioauthFlowParams, + rpc_http_port: Option, + rpc_ws_port: Option, +) -> RpcUrl { if let Some(val) = ¶ms.rpc_url { return RpcUrl::Set(val.clone()); } @@ -94,14 +99,37 @@ fn rpc_url_from_params(params: &BioauthFlowParams, rpc_port: Option) -> Rpc return RpcUrl::Unset; } if params.rpc_url_ngrok_detect { + let ws_rpc_endpoint_port = match params.rpc_url_scheme_preference { + // If there's no preference - try switching to WebSocket if it's available. + RpcUrlSchemePreference::NoPreference | RpcUrlSchemePreference::Ws => rpc_ws_port, + RpcUrlSchemePreference::Http => None, + }; return RpcUrl::DetectFromNgrok { tunnel_name: params.rpc_url_ngrok_detect_from.clone(), + ws_rpc_endpoint_port, }; } - if let Some(rpc_endpoint_port) = rpc_port { - return RpcUrl::LocalhostWithPort { rpc_endpoint_port }; + match ( + ¶ms.rpc_url_scheme_preference, + rpc_http_port, + rpc_ws_port, + ) { + // Try WebSocket first if the user has no preference. + (RpcUrlSchemePreference::Ws | RpcUrlSchemePreference::NoPreference, _, Some(port)) => { + RpcUrl::LocalhostWithPort { + rpc_endpoint_port: port, + scheme: "ws", + } + } + // Try HTTP second if the user has no preference. + (RpcUrlSchemePreference::Http | RpcUrlSchemePreference::NoPreference, Some(port), _) => { + RpcUrl::LocalhostWithPort { + rpc_endpoint_port: port, + scheme: "http", + } + } + // If everything fails - fallback to unset. + _ => RpcUrl::Unset, } - - RpcUrl::Unset } diff --git a/crates/humanode-peer/src/cli/params.rs b/crates/humanode-peer/src/cli/params.rs index 9053898e0..ec095ca11 100644 --- a/crates/humanode-peer/src/cli/params.rs +++ b/crates/humanode-peer/src/cli/params.rs @@ -1,5 +1,16 @@ //! Shared CLI parameters. +/// Possible RPC URL scheme preference options. +#[derive(Debug, clap::ArgEnum, Clone)] +pub enum RpcUrlSchemePreference { + /// Prefer HTTP (http or https). + Http, + /// Prefer WebSocket (ws or wss). + Ws, + /// No preference, use opinionated defaults. + NoPreference, +} + /// Shared CLI parameters used to configure bioauth flow. #[derive(Debug, clap::Parser, Clone)] pub struct BioauthFlowParams { @@ -10,15 +21,25 @@ pub struct BioauthFlowParams { /// The URL to pass to the web app to connect to the node RPC. /// If not passed, a URL with `localhost` and the HTTP RPC port will be used. - #[clap(long, value_name = "RPC_URL", conflicts_with_all = &["rpc-url-ngrok-detect", "rpc-url-unset"])] + #[clap(long, value_name = "RPC_URL", conflicts_with_all = &["rpc-url-scheme-preference", "rpc-url-ngrok-detect", "rpc-url-unset"])] pub rpc_url: Option, + /// What RPC URL scheme to prefer. + #[clap( + long, + arg_enum, + value_name = "RPC_URL_SCHEME_PREFERENCE", + default_value = "no-preference", + conflicts_with_all = &["rpc-url", "rpc-url-unset"] + )] + pub rpc_url_scheme_preference: RpcUrlSchemePreference, + /// Detect RPC URL from ngrok. #[clap(long, conflicts_with_all = &["rpc-url", "rpc-url-unset"])] pub rpc_url_ngrok_detect: bool, /// Explicitly unset the RPC URL. - #[clap(long, conflicts_with_all = &["rpc-url", "rpc-url-ngrok-detect"])] + #[clap(long, conflicts_with_all = &["rpc-url", "rpc-url-scheme-preference", "rpc-url-ngrok-detect"])] pub rpc_url_unset: bool, /// The tunnel name at ngrok to detect RPC URL from, if ngrok is used to detect the RPC URL. diff --git a/crates/humanode-peer/src/rpc_url.rs b/crates/humanode-peer/src/rpc_url.rs index 92db539f7..46fdb6020 100644 --- a/crates/humanode-peer/src/rpc_url.rs +++ b/crates/humanode-peer/src/rpc_url.rs @@ -13,13 +13,18 @@ pub enum RpcUrl { /// The URL is to be constructed from the RPC endpoint port. /// This is typically used as a fallback. LocalhostWithPort { - /// The port of the RPC enpoint our peer binds the socket to. + /// The port of the RPC endpoint our peer binds the socket to. rpc_endpoint_port: u16, + /// The scheme to use for the RPC URL. + scheme: &'static str, }, /// Detect the RPC URL from ngrok. DetectFromNgrok { /// The tunnel name to get the public URL from. tunnel_name: String, + /// The WebSocket port to match against, and switch protocol to WebSocket if the tunnel + /// address has this port. + ws_rpc_endpoint_port: Option, }, } @@ -49,19 +54,28 @@ impl RpcUrlResolver { match val { RpcUrl::Unset => Err(Cow::Borrowed("RPC URL was not set")), RpcUrl::Set(url) => Ok(Cow::Borrowed(url)), - RpcUrl::LocalhostWithPort { rpc_endpoint_port } => { - Ok(format!("http://localhost:{}", rpc_endpoint_port).into()) - } - RpcUrl::DetectFromNgrok { tunnel_name } => { - Ok(self.detect_from_ngrok(&*tunnel_name).await?.into()) - } + RpcUrl::LocalhostWithPort { + rpc_endpoint_port, + scheme, + } => Ok(format!("{}://localhost:{}", scheme, rpc_endpoint_port).into()), + RpcUrl::DetectFromNgrok { + tunnel_name, + ws_rpc_endpoint_port, + } => Ok(self + .detect_from_ngrok(&*tunnel_name, *ws_rpc_endpoint_port) + .await? + .into()), } } /// Detect the RPC URL from the `ngrok`'s API. /// Returns the public URL from the specified tunnel. /// Assumes that the tunnel has a proper protocol and points to a proper port. - async fn detect_from_ngrok(&self, tunnel_name: &str) -> Result { + async fn detect_from_ngrok( + &self, + tunnel_name: &str, + ws_rpc_endpoint_port: Option, + ) -> Result { let mut attempts_left = 10; let res = loop { let tunnel_name = std::borrow::Cow::Owned(tunnel_name.to_owned()); @@ -81,6 +95,16 @@ impl RpcUrlResolver { Err(err) => return Err(err.to_string()), } }; - Ok(res.public_url) + let mut public_url = res.public_url; + if let Some(ws_rpc_endpoint_port) = ws_rpc_endpoint_port { + if res + .config + .addr + .ends_with(&format!(":{}", ws_rpc_endpoint_port)) + { + public_url = public_url.replacen("https", "wss", 1); + } + } + Ok(public_url) } }