diff options
author | 2025-04-30 16:18:05 +0100 | |
---|---|---|
committer | 2025-04-30 16:18:05 +0100 | |
commit | ccae86c3b38f829828adc40ab1695b137dc2b070 (patch) | |
tree | 5b87d2599297cf1bd5140246eeb9d9d6b19a0d19 | |
parent | 74e5ab6702bbbe292fcb38ccb97036ce5b376956 (diff) | |
download | luz-ccae86c3b38f829828adc40ab1695b137dc2b070.tar.gz luz-ccae86c3b38f829828adc40ab1695b137dc2b070.tar.bz2 luz-ccae86c3b38f829828adc40ab1695b137dc2b070.zip |
feat(luz): xep-0156: discovering websocket connection url
-rw-r--r-- | luz/Cargo.toml | 9 | ||||
-rw-r--r-- | luz/src/connection/ws.rs | 71 | ||||
-rw-r--r-- | luz/src/error.rs | 12 |
3 files changed, 81 insertions, 11 deletions
diff --git a/luz/Cargo.toml b/luz/Cargo.toml index 0543768..4ab1a6e 100644 --- a/luz/Cargo.toml +++ b/luz/Cargo.toml @@ -24,7 +24,7 @@ rsasl = { version = "2.0.1", default-features = false, features = [ tokio = { workspace = true, features = ["io-util"] } tracing = "0.1.40" try_map = "0.3.1" -stanza = { version = "0.1.0", path = "../stanza" } +stanza = { version = "0.1.0", path = "../stanza", features = ["xep_0156"] } peanuts = { version = "0.1.0", git = "https://bunny.garden/peanuts" } # peanuts = { version = "0.1.0", path = "../../peanuts" } jid = { version = "0.1.0", path = "../jid" } @@ -35,13 +35,14 @@ pin-project = "1.1.7" thiserror = "2.0.11" [target.'cfg(target_arch = "wasm32")'.dependencies] -tokio = { workspace = true, features = ["io-util", "sync"] } +tokio = { workspace = true, features = ["io-util", "sync", "macros"] } uuid = { version = "1.13.1", features = ["js", "v4"] } getrandom = { version = "0.2.15", features = ["js"] } -stanza = { version = "0.1.0", path = "../stanza", features = ["rfc_7395"] } -web-sys = { version = "0.3", features = ["Request", "WebSocket"] } +stanza = { version = "0.1.0", path = "../stanza", features = ["rfc_7395", "xep_0156"] } +web-sys = { version = "0.3", features = ["Request", "WebSocket", "RequestInit", "Request", "Window", "Response", "ErrorEvent"] } js-sys = "0.3" wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4.50" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { workspace = true, features = ["io-util", "sync"] } diff --git a/luz/src/connection/ws.rs b/luz/src/connection/ws.rs index 74e3223..caecf4a 100644 --- a/luz/src/connection/ws.rs +++ b/luz/src/connection/ws.rs @@ -1,8 +1,12 @@ +use js_sys::Error; +use peanuts::{reader::ReadableString, Reader}; +use stanza::xep_0156::XRD; use tokio::sync::mpsc; use tracing::debug; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; -use web_sys::{ErrorEvent, WebSocket}; +use wasm_bindgen_futures::JsFuture; +use web_sys::{ErrorEvent, Request, RequestInit, Response, WebSocket}; use crate::error::ConnectionError; @@ -17,11 +21,62 @@ impl Connection { ) -> Result<Self, ConnectionError> { // TODO: get the connection url here debug!("creating websocket"); - let url = String::from("wss://xmpp.bunny.garden/ws"); - let ws = WebSocket::new_with_str(&url, "xmpp").map_err(|error| { + + let opts = RequestInit::new(); + opts.set_method("GET"); + let host_meta_url = format!("https://{}/.well-known/host-meta", server.as_ref()); + + let request = Request::new_with_str_and_init(&host_meta_url, &opts).map_err(|e| { + let error: Error = e.into(); + ConnectionError::CreateHostMeta(error.message().as_string().unwrap()) + })?; + + let window = web_sys::window().unwrap(); + let resp_value = JsFuture::from(window.fetch_with_request(&request)) + .await + .map_err(|e| { + let error: Error = e.into(); + ConnectionError::FetchHostMeta(error.message().as_string().unwrap()) + })?; + + let resp: Response = resp_value.dyn_into().unwrap(); + + let text = JsFuture::from(resp.text().map_err(|e| { + let error: Error = e.into(); + ConnectionError::FetchHostMeta(error.message().as_string().unwrap()) + })?) + .await + .map_err(|e| { + let error: Error = e.into(); + ConnectionError::FetchHostMeta(error.message().as_string().unwrap()) + })?; + + let host_meta = text.as_string().ok_or(ConnectionError::HostMetaText)?; + + let readable_string = ReadableString(host_meta); + let mut reader = Reader::new(readable_string); + + reader.read_prolog().await.unwrap(); + + let xrd: XRD = reader.read().await?; + + let link = xrd + .links + .iter() + .find(|link| link.rel.as_deref() == Some("urn:xmpp:alt-connections:websocket")) + .ok_or(ConnectionError::MissingWebsocketLink)?; + + let url = link.href.as_ref().ok_or(ConnectionError::LinkMissingHref)?; + + Connection::connect_url(url).await + } + + pub async fn connect_url(url: impl AsRef<str>) -> Result<Self, ConnectionError> { + let url = url.as_ref(); + let ws = WebSocket::new_with_str(url, "xmpp").map_err(|error| { let error: js_sys::Error = error.into(); let message = error.message().as_string().unwrap(); - ConnectionError::Create(url.clone(), message) + ConnectionError::Create(url.to_string(), message) })?; debug!("created websocket"); let (send, mut open_recv) = mpsc::unbounded_channel(); @@ -50,11 +105,13 @@ impl Connection { ws.set_onerror(Some(onerror.as_ref().unchecked_ref())); tokio::select! { - _error = error_recv.recv() => { Err(ConnectionError::Connect(url))? }, + _error = error_recv.recv() => { Err(ConnectionError::Connect(url.to_string()))? }, Some(open) = open_recv.recv() => { }, - else => { Err(ConnectionError::Connect(url))? } + else => { Err(ConnectionError::Connect(url.to_string()))? } } - debug!("um what"); + + ws.set_onopen(None); + ws.set_onerror(None); // TODO: check reply if it's xmpp too Ok(Self(ws)) diff --git a/luz/src/error.rs b/luz/src/error.rs index 3fcca57..fcb32a0 100644 --- a/luz/src/error.rs +++ b/luz/src/error.rs @@ -68,4 +68,16 @@ pub enum ConnectionError { Create(String, String), #[error("failed to connect to \"{0}\"")] Connect(String), + #[error("failed to create host-meta request: {0}")] + CreateHostMeta(String), + #[error("fetching host meta: {0}")] + FetchHostMeta(String), + #[error("getting host meta text")] + HostMetaText, + #[error("parsing host meta xrd: {0}")] + XRDParse(#[from] peanuts::Error), + #[error("XRD missing websocket Link")] + MissingWebsocketLink, + #[error("XRD websocket Link missing href")] + LinkMissingHref, } |