From 147ec2b0a90b93effe9f4b41683cb69168d64ce5 Mon Sep 17 00:00:00 2001 From: Joey Hobbs Date: Fri, 12 Jun 2026 17:02:56 +1200 Subject: [PATCH] feat: add --ws-url flag and wss:// scheme support Adds a --ws-url CLI flag (and BITMEX_WS_URL env var, which previously existed but was silently ignored) to override the WebSocket endpoint, mirroring the existing --api-url / BITMEX_API_URL pattern. - Add --ws-url global flag to Cli struct - Wire cli.ws_url through resolve() so BITMEX_WS_URL env var is also respected (previously the env var was read but never used) - Use ctx.ws_url in the Ws command dispatch, falling back to mainnet/testnet defaults when unset - Extend resolve_url_override to accept wss:// and ws:// schemes; ws:// is subject to the same localhost-only restriction as http:// Co-Authored-By: Claude Sonnet 4.6 (1M context) --- src/exchange/client.rs | 14 ++++++++------ src/lib.rs | 16 +++++++++++----- src/main.rs | 2 +- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/exchange/client.rs b/src/exchange/client.rs index f28a176..d1290de 100644 --- a/src/exchange/client.rs +++ b/src/exchange/client.rs @@ -447,7 +447,9 @@ mod tests { /// Validate and normalise a URL override from a CLI flag or environment variable. /// -/// Accepts `http://` (for local test servers) and `https://` schemes only. +/// Accepts `https://`, `http://`, `wss://`, and `ws://` schemes. +/// Unencrypted schemes (`http://`, `ws://`) are restricted to localhost unless +/// `BITMEX_DANGER_ALLOW_ANY_URL_HOST=1` is set. /// Returns `None` if `cli_flag` and `env_val` are both `None`, so callers fall /// back to their compiled-in default. pub fn resolve_url_override( @@ -462,14 +464,14 @@ pub fn resolve_url_override( let url = url::Url::parse(raw)?; let scheme = url.scheme(); - if scheme != "https" && scheme != "http" { + if !matches!(scheme, "https" | "http" | "wss" | "ws") { return Err(BitmexError::Validation { message: format!( - "URL must use https:// or http://, got: {raw}" + "URL must use https://, http://, wss://, or ws://, got: {raw}" ) }); } - // Block arbitrary internet hosts unless the danger env var is set. - if scheme == "http" { + // Block arbitrary internet hosts for unencrypted schemes unless the danger env var is set. + if matches!(scheme, "http" | "ws") { let host = url.host_str().unwrap_or(""); let is_local = host == "localhost" || host == "127.0.0.1" @@ -479,7 +481,7 @@ pub fn resolve_url_override( env::var("BITMEX_DANGER_ALLOW_ANY_URL_HOST").is_ok_and(|v| v == "1"); if !is_local && !danger_allowed { return Err(BitmexError::Validation { message: format!( - "http:// is only allowed for localhost/127.0.0.1 or when \ + "{scheme}:// is only allowed for localhost/127.0.0.1 or when \ BITMEX_DANGER_ALLOW_ANY_URL_HOST=1 is set. Got: {raw}" ) }); } diff --git a/src/lib.rs b/src/lib.rs index 5dd81e6..b912109 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,10 @@ pub struct Cli { #[arg(long, global = true)] pub api_url: Option, + /// Override WebSocket URL. + #[arg(long, global = true)] + pub ws_url: Option, + /// API key override (takes precedence over env/config). #[arg(long, global = true)] pub api_key: Option, @@ -435,11 +439,13 @@ pub(crate) async fn execute_command(ctx: &AppContext, command: Command) -> Resul } Command::Ws(args) => { - let ws_url = if ctx.testnet { - exchange::client::WS_TESTNET_URL.to_string() - } else { - exchange::client::WS_MAINNET_URL.to_string() - }; + let ws_url = ctx.ws_url.clone().unwrap_or_else(|| { + if ctx.testnet { + exchange::client::WS_TESTNET_URL.to_string() + } else { + exchange::client::WS_MAINNET_URL.to_string() + } + }); let needs_auth = args.auth || args.topics.iter().any(|t| ws::is_private_topic(t)); let creds = if needs_auth { config::resolve_credentials( diff --git a/src/main.rs b/src/main.rs index e008c16..97173e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,7 +70,7 @@ async fn main() { }; let api_url = resolve(cli.api_url.as_deref(), "BITMEX_API_URL", "--api-url"); - let ws_url = resolve(None, "BITMEX_WS_URL", "BITMEX_WS_URL"); + let ws_url = resolve(cli.ws_url.as_deref(), "BITMEX_WS_URL", "--ws-url"); // Merge CLI --testnet with the active profile's testnet flag let testnet = bitmex_cli::config::effective_testnet(cli.profile.as_deref(), cli.testnet);