use std::fmt; use gloo_net::http::{Request, RequestBuilder, Response}; use serde::Serialize; use crate::i18n::tr; use serde::de::DeserializeOwned; #[derive(Clone)] pub struct Error { pub status: u16, pub msg: String, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.msg) } } pub async fn get(path: &str) -> Result { read(path, Request::get(path).send().await).await } /// For the app's device mode. A 401 there means the device was removed, not that a session ended. pub async fn get_bearer(path: &str, token: &str) -> Result { let res = Request::get(path) .header("Authorization", &format!("Bearer {token}")) .send() .await; read("", res).await } pub async fn post(path: &str, body: &impl Serialize) -> Result { send(path, Request::post(path), body).await } pub async fn put(path: &str, body: &impl Serialize) -> Result { send(path, Request::put(path), body).await } async fn send( path: &str, req: RequestBuilder, body: &impl Serialize, ) -> Result { let req = req.json(body).map_err(|e| Error { status: 0, msg: e.to_string(), })?; read(path, req.send().await).await } pub async fn delete(path: &str) -> Result<(), Error> { read(path, Request::delete(path).send().await).await } async fn read( path: &str, res: Result, ) -> Result { let res = res.map_err(|e| Error { status: 0, msg: tr(&format!("network error: {e}")), })?; // The session has ended. A reload shows the login form. if res.status() == 401 && !matches!(path, "" | "/api/login" | "/api/me") && !path.starts_with("/api/guest") { let _ = web_sys::window().unwrap().location().reload(); } if !res.ok() { let body = res.text().await.unwrap_or_default(); // The server writes its messages in English. They are translated here, in one place. let msg = if body.is_empty() { res.status_text() } else { tr(&body) }; return Err(Error { status: res.status(), msg, }); } res.json().await.map_err(|e| Error { status: 0, msg: e.to_string(), }) }