diff --git a/crates/xhr/Cargo.toml b/crates/xhr/Cargo.toml index 4a6f8f77..cb4c35a1 100644 --- a/crates/xhr/Cargo.toml +++ b/crates/xhr/Cargo.toml @@ -9,6 +9,7 @@ wasm-bindgen = "0.2.37" js-sys = "0.3.14" http = "0.1.16" futures = "0.1.25" +gloo-events = { git = "https://github.com/Pauan/gloo", branch = "events" } [dependencies.web-sys] version = "0.3.14" diff --git a/crates/xhr/src/lib.rs b/crates/xhr/src/lib.rs index c8e7312e..ce8b3ade 100644 --- a/crates/xhr/src/lib.rs +++ b/crates/xhr/src/lib.rs @@ -4,21 +4,86 @@ XmlHttpRequest. */ -/// The raw XmlHttpRequest API, wrapped with convenient Rust types. +/// The raw XmlHttpRequest (XHR) API, wrapped with convenient Rust types. /// -/// Synchronous requests are not supported (because deprecated). -pub mod raw { - use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; +/// ### Example: +/// +/// ``` +/// use gloo_xhr::callback::XmlHttpRequest; +/// use futures::sync::mpsc; +/// use gloo_events::EventListener; +/// +/// let (sender, receiver) = mpsc::unbounded::<()>(); +/// let request = XmlHttpRequest::new(); +/// +/// let load_listener = EventListener::new(request.as_ref(), "load", move |_event| { +/// sender.unbounded_send(()).unwrap(); +/// }); +/// +/// load_listener.forget(); +/// +/// request.open(&http::Method::GET, "/"); +/// +/// request.send_no_body(); +/// ``` +/// +/// ### Events +/// +/// The [`XmlHttpRequest`](crate::callback::XmlHttpRequest) object is an event target. The most frequently used are: +/// +/// - `load`: when the response is received +/// - `error`: when a network error occurred +/// - `progress`: when progress is made on the **response download**. For progress on the request +/// upload, see "Upload progress" below. +/// +/// You can conveniently attach event listeners with [gloo-events][gloo-events]. +/// +/// ``` +/// use gloo_xhr::callback::XmlHttpRequest; +/// +/// let request = XmlHttpRequest::new(); +/// +/// gloo_events::EventListener::new(request.as_ref(), "load", move |_event| { +/// // do something with the event +/// }); +/// ``` +/// +/// ### Upload progress +/// +/// XHR supports progress events on uploads. You can access these events by attaching an event +/// listener to the [`XmlHttpRequestUpload`](crate::callback::XmlHttpRequestUpload) object (returned by the `upload` method). +/// +/// For further documentation on the browser API, see the [MDN guide][MDN using XHR]. +/// +/// ### Unsupported features +/// +/// Synchronous requests are not supported because they are considered deprecated by most browsers. +/// +/// You can access the underlying [`web_sys::XmlHttpRequest`](web_sys::XmlHttpRequest) through the [`as_raw`](crate::callback::XmlHttpRequest::as_raw) method. +/// +/// [MDN using XHR]: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest +/// [gloo-events]: https://github.com/rustwasm/gloo +pub mod callback { + use wasm_bindgen::{prelude::*, JsCast}; use web_sys; - /// See MDN docs. + /// The main interface to the XmlHttpRequest API. See [module docs](crate::callback) for more extensive + /// documentation. #[derive(Debug, Clone)] pub struct XmlHttpRequest { xhr: web_sys::XmlHttpRequest, } + // This is so it can be used as an event target by gloo-events. + impl AsRef for XmlHttpRequest { + fn as_ref(&self) -> &web_sys::EventTarget { + self.xhr.as_ref() + } + } + impl XmlHttpRequest { - /// Initialize an XmlHttpRequest. + /// Initialize an XmlHttpRequest. To actually perform the request, you will need to + /// call at least `open()` and `send()`. pub fn new() -> Self { // we assume this is safe because all browsers that support webassembly // implement XmlHttpRequest. @@ -31,37 +96,39 @@ pub mod raw { &self.xhr } - /// See MDN docs. + /// Returns an event target for upload events. /// - /// Upload events are fired there. + /// See the [module docs](crate::callback) and `XmlHttpRequestUpload` for more information. pub fn upload(&self) -> XmlHttpRequestUpload { XmlHttpRequestUpload { upload: self.xhr.upload().unwrap_throw(), } } - /// Open? + /// The `XMLHttpRequest` method `open()` initializes a newly-created request, or + /// re-initializes an existing one. + /// + /// [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/open) pub fn open(&self, method: &http::Method, url: &str) { - self.xhr - .open(&method.to_string(), url) - .expect_throw("opening XHR") + self.xhr.open(&method.to_string(), url).unwrap_throw() } /// Abort the request. + /// + /// This will fire an `abort` event on the `XmlHttpRequest`. pub fn abort(&self) { - self.xhr.abort().expect_throw("aborting XHR") + self.xhr.abort().unwrap_throw() } - /// Send without a body. - /// - /// Should probably be renamed. + /// `send()` without a body. pub fn send_no_body(&self) { self.xhr .send() .expect_throw("Error sending request. Did you forget to call `open`?") } - /// Send! + /// Actually send the request. In order to know the outcome, you have to attach + /// `load`, `abort` and `error` event listeners. pub fn send(&self, body: B) { body.send(&self.xhr) .expect_throw("Error sending request. Did you forget to call `open`?") @@ -100,40 +167,6 @@ pub mod raw { .collect() } - /// The error callback can fire in cases such as CORS errors. - pub fn set_onerror(&self, callback: C) - where - C: FnMut(web_sys::ProgressEvent) + 'static, - { - let closure = Closure::wrap(Box::new(callback) as Box); - self.xhr.set_onerror(Some(closure.as_ref().unchecked_ref())); - closure.forget(); - } - - /// see mdn - /// - /// This takes an FnMut because the callback can be called more than once (if - /// `send` is called more than once) - pub fn set_onload(&self, callback: C) - where - C: FnMut(web_sys::ProgressEvent) + 'static, - { - let closure = Closure::wrap(Box::new(callback) as Box); - self.xhr.set_onload(Some(closure.as_ref().unchecked_ref())); - closure.forget(); - } - - /// see mdn - pub fn set_onprogress(&self, callback: C) - where - C: FnMut(web_sys::ProgressEvent) + 'static, - { - let closure = Closure::wrap(Box::new(callback) as Box); - self.xhr - .set_onprogress(Some(closure.as_ref().unchecked_ref())); - closure.forget(); - } - /// Get the response body, assuming `responseType` was set to text (the default). pub fn response_as_string(&self) -> Option { // unwrap_throw is safe, because `response` is an accessor. If the response @@ -250,7 +283,7 @@ pub mod raw { Text, } - /// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState + /// [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState) #[derive(Debug)] pub enum ReadyState { /// Client has been created. `open()` not called yet. @@ -290,33 +323,22 @@ pub mod raw { } } - /// See MDN docs. + /// The main interface to the XmlHttpRequestUpload API. + /// + /// This is mainly used as an event target for uploads (see [module docs](super)), and is + /// constructed by the [`upload()`](crate::callback::XmlHttpRequest::upload) method on + /// [XmlHttpRequest](crate::callback::XmlHttpRequest)). + /// + /// [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/upload) #[derive(Debug, Clone)] pub struct XmlHttpRequestUpload { upload: web_sys::XmlHttpRequestUpload, } - impl XmlHttpRequestUpload { - /// see mdn - pub fn set_onload(&self, callback: C) - where - C: FnMut(web_sys::ProgressEvent) + 'static, - { - let closure = Closure::wrap(Box::new(callback) as Box); - self.upload - .set_onload(Some(closure.as_ref().unchecked_ref())); - closure.forget(); - } - - /// see mdn - pub fn set_onprogress(&self, callback: C) - where - C: FnMut(web_sys::ProgressEvent) + 'static, - { - let closure = Closure::wrap(Box::new(callback) as Box); - self.upload - .set_onprogress(Some(closure.as_ref().unchecked_ref())); - closure.forget(); + // This is so it can be used as an event target by gloo-events. + impl AsRef for XmlHttpRequestUpload { + fn as_ref(&self) -> &web_sys::EventTarget { + self.upload.as_ref() } } } diff --git a/crates/xhr/tests/web.rs b/crates/xhr/tests/web.rs index 204f763d..dce4255e 100644 --- a/crates/xhr/tests/web.rs +++ b/crates/xhr/tests/web.rs @@ -3,7 +3,8 @@ #![cfg(target_arch = "wasm32")] use futures::prelude::*; -use gloo_xhr::raw::XmlHttpRequest; +use gloo_events::EventListener; +use gloo_xhr::callback::XmlHttpRequest; use wasm_bindgen::*; use wasm_bindgen_test::*; @@ -23,11 +24,14 @@ fn minimal_request_empty_body() -> impl Future impl Future let request = XmlHttpRequest::new(); - request.set_onerror(move |_event| { + let error_listener = EventListener::new(request.as_ref(), "error", move |_event| { sender.take().map(|sender| sender.send(()).unwrap()); }); + error_listener.forget(); + // this will trigger a CORS error. request.open(&http::Method::GET, "https://example.com/");