freya-components 0.5.0-beta.12

Components for Freya apps
Documentation
//! HTTP requests with the `remote-asset` feature enabled.
//!
//! ```rust,no_run
//! use freya_components::{
//!     Url,
//!     http,
//! };
//!
//! # async fn load() -> anyhow::Result<()> {
//! let url = Url::parse("https://example.com")?;
//! let bytes = http::fetch(url).await?;
//! # Ok(())
//! # }
//! ```
//!
//! Native requests use a default client unless you provide one at launch:
//!
//! ```rust,no_run
//! # use freya::prelude::*;
//! # fn app() -> impl IntoElement { "Hello" }
//! fn main() -> Result<(), reqwest::Error> {
//!     let client = reqwest::blocking::Client::builder()
//!         .user_agent("MyApp/1.0")
//!         .build()?;
//!     launch(
//!         LaunchConfig::new()
//!             .with_global_context(client)
//!             .with_window(WindowConfig::new(app)),
//!     );
//!     Ok(())
//! }
//! ```
//!
//! On web, requests use the browser and follow its CORS rules.

use bytes::Bytes;
use url::Url;

/// Fetches `url` using the app-wide HTTP client on native platforms.
#[cfg(not(target_os = "emscripten"))]
pub async fn fetch(url: Url) -> anyhow::Result<Bytes> {
    let client: reqwest::blocking::Client = freya_core::prelude::GlobalContexts::get()
        .get_context_or_insert(reqwest::blocking::Client::new);
    freya_core::prelude::thread(move || Ok(client.get(url).send()?.error_for_status()?.bytes()?))
        .await
}

/// Fetches `url` using the browser's HTTP client on web.
///
/// Browser CORS rules apply. Returns an error if the request fails.
#[cfg(target_os = "emscripten")]
pub async fn fetch(url: Url) -> anyhow::Result<Bytes> {
    use std::ffi::{
        CString,
        c_char,
        c_int,
        c_void,
    };

    use anyhow::Context;
    use futures_channel::oneshot::{
        Sender,
        channel,
    };

    unsafe extern "C" {
        fn emscripten_async_wget_data(
            url: *const c_char,
            argument: *mut c_void,
            on_load: extern "C" fn(*mut c_void, *mut c_void, c_int),
            on_error: extern "C" fn(*mut c_void),
        );
    }

    extern "C" fn on_load(argument: *mut c_void, data: *mut c_void, size: c_int) {
        let sender = unsafe { Box::from_raw(argument.cast::<Sender<Option<Bytes>>>()) };
        let data = unsafe { std::slice::from_raw_parts(data.cast::<u8>(), size as usize) };
        sender.send(Some(Bytes::copy_from_slice(data))).ok();
    }

    extern "C" fn on_error(argument: *mut c_void) {
        let sender = unsafe { Box::from_raw(argument.cast::<Sender<Option<Bytes>>>()) };
        sender.send(None).ok();
    }

    let c_url = CString::new(url.as_str())?;
    let (sender, receiver) = channel::<Option<Bytes>>();
    let argument = Box::into_raw(Box::new(sender)).cast();

    unsafe { emscripten_async_wget_data(c_url.as_ptr(), argument, on_load, on_error) };

    receiver
        .await
        .ok()
        .flatten()
        .with_context(|| format!("Failed to fetch {url}."))
}