1use crate::CancellationToken;
5use std::fmt;
6use wasm_bindgen::{JsCast, JsValue};
7use wasm_bindgen_futures::JsFuture;
8use web_sys::{RequestInit, Response};
9
10#[wasm_bindgen::prelude::wasm_bindgen(
11 inline_js = "export async function fusorFetch(url, options) { const base = globalThis.__fusor_worker_base_url; return globalThis.fetch(base ? new URL(url, base).href : url, options); }"
12)]
13extern "C" {
14 #[wasm_bindgen::prelude::wasm_bindgen(js_name = fusorFetch)]
15 fn fetch(url: &str, options: &RequestInit) -> js_sys::Promise;
16}
17
18#[derive(Debug)]
20pub enum FetchError {
21 Cancelled,
23 Status { url: String, status: u16 },
25 Js(JsValue),
27}
28impl From<JsValue> for FetchError {
29 fn from(error: JsValue) -> Self {
30 Self::Js(error)
31 }
32}
33impl fmt::Display for FetchError {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::Cancelled => f.write_str("request cancelled"),
37 Self::Status { url, status } => write!(f, "GET {url}: HTTP {status}"),
38 Self::Js(error) => match error.as_string() {
39 Some(message) => f.write_str(&message),
40 None => write!(f, "{error:?}"),
41 },
42 }
43 }
44}
45impl std::error::Error for FetchError {}
46
47pub async fn get_text(url: &str, cancel: &CancellationToken) -> Result<String, FetchError> {
50 let result: Result<String, FetchError> = async {
51 let options = RequestInit::new();
52 options.set_signal(Some(&cancel.abort_signal()?));
53 let response: Response = JsFuture::from(fetch(url, &options)).await?.dyn_into()?;
54 if !response.ok() {
55 return Err(FetchError::Status {
56 url: url.to_owned(),
57 status: response.status(),
58 });
59 }
60 JsFuture::from(response.text()?)
61 .await?
62 .as_string()
63 .ok_or_else(|| JsValue::from_str("Fetch returned a non-text body").into())
64 }
65 .await;
66 result.map_err(|error| match error {
68 FetchError::Js(_) if cancel.is_cancelled() => FetchError::Cancelled,
69 error => error,
70 })
71}