use h2ts_client::ConnectOptions;
use wasm_bindgen::JsValue;
use crate::client::Client;
use crate::status::Status;
pub fn connect(base_url: &str) -> Result<Client, Status> {
connect_with(base_url, ConnectOptions::default())
}
pub fn connect_with(base_url: &str, options: ConnectOptions) -> Result<Client, Status> {
let (ws_url, authority) = websocket_url(base_url)?;
let connector: crate::Connector = std::rc::Rc::new(move || {
let ws_url = ws_url.clone();
let options = options.clone();
Box::pin(async move {
h2ts_client::connect_websocket(&ws_url, &[crate::H2TS_SUBPROTOCOL], options)
.await
.map_err(|e| {
Status::unavailable(format!("could not open the tunnel: {}", describe(&e)))
})
})
});
Ok(Client::with_connector(connector, authority))
}
fn describe(value: &JsValue) -> String {
value.as_string().unwrap_or_else(|| format!("{value:?}"))
}
fn websocket_url(base_url: &str) -> Result<(String, String), Status> {
let trimmed = base_url.trim_end_matches('/');
let (scheme, rest) = trimmed
.split_once("://")
.ok_or_else(|| Status::unavailable(format!("base URL has no scheme: {base_url}")))?;
let ws_scheme = match scheme {
"https" | "wss" => "wss",
"http" | "ws" => "ws",
other => {
return Err(Status::unavailable(format!("unsupported URL scheme: {other}")));
}
};
let authority = rest.split('/').next().unwrap_or(rest).to_string();
Ok((format!("{ws_scheme}://{rest}"), authority))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn maps_scheme_and_extracts_authority() {
for (input, ws, authority) in [
("http://localhost:8080", "ws://localhost:8080", "localhost:8080"),
("https://api.example.com", "wss://api.example.com", "api.example.com"),
("https://api.example.com/grpc/", "wss://api.example.com/grpc", "api.example.com"),
] {
let (got_ws, got_authority) = websocket_url(input).unwrap();
assert_eq!((got_ws.as_str(), got_authority.as_str()), (ws, authority), "{input}");
}
}
#[test]
fn rejects_a_url_it_cannot_map() {
assert!(websocket_url("localhost:8080").is_err());
assert!(websocket_url("ftp://host").is_err());
}
}