facett-simview/src/backend.rs
//! **How a request leaves the app.** egui draws synchronously and a fetch is async, so
//! the app never waits: it [`send`](Backend::send)s and, each frame,
//! [`drain`](Backend::drain)s whatever came back. Two implementations: the browser's
//! `fetch` (wasm, same-origin, the session cookie rides along) and the in-process
//! [`MockBackend`](crate::mock::MockBackend) the tests and the native window use.
use crate::contract::{Req, Resp};
pub trait Backend {
/// Start a request; its answer arrives through a later [`drain`](Self::drain).
fn send(&self, req: Req);
/// Every answer that has arrived since the last call, in arrival order.
fn drain(&self) -> Vec<Resp>;
}
/// The browser: `window.fetch`, same-origin credentials.
#[cfg(target_arch = "wasm32")]
pub mod fetch {
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::JsCast as _;
use wasm_bindgen_futures::JsFuture;
use super::Backend;
use crate::contract::{Req, Resp};
#[derive(Default, Clone)]
pub struct FetchBackend {
inbox: Rc<RefCell<Vec<Resp>>>,
}
impl FetchBackend {
pub fn new() -> Self {
Self::default()
}
}
async fn run(req: &Req) -> Result<(u16, Vec<u8>), String> {
let (method, url) = req.http();
let opts = web_sys::RequestInit::new();
opts.set_method(method);
opts.set_credentials(web_sys::RequestCredentials::SameOrigin);
let request = web_sys::Request::new_with_str_and_init(&url, &opts).map_err(|e| format!("{e:?}"))?;
let window = web_sys::window().ok_or("no window")?;
let resp: web_sys::Response = JsFuture::from(window.fetch_with_request(&request))
.await
.map_err(|e| format!("{e:?}"))?
.dyn_into()
.map_err(|_| "not a Response".to_string())?;
let buf = JsFuture::from(resp.array_buffer().map_err(|e| format!("{e:?}"))?).await.map_err(|e| format!("{e:?}"))?;
Ok((resp.status(), js_sys::Uint8Array::new(&buf).to_vec()))
}
impl Backend for FetchBackend {
fn send(&self, req: Req) {
let inbox = self.inbox.clone();
wasm_bindgen_futures::spawn_local(async move {
let (status, body) = match run(&req).await {
Ok(x) => x,
// 0 = the request never got an HTTP answer (network, CORS, abort).
Err(e) => (0, e.into_bytes()),
};
inbox.borrow_mut().push(Resp { req, status, body });
});
}
fn drain(&self) -> Vec<Resp> {
std::mem::take(&mut *self.inbox.borrow_mut())
}
}
}