1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! provides functions for retrieving data using http network request
use crate::dom::Callback;
use crate::{Application, Cmd, Dispatch, Program};
use js_sys::TypeError;
use std::fmt::Debug;
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use web_sys::RequestInit;
use web_sys::Response;

/// Provides functions for doing http network request
#[derive(Copy, Clone, Debug)]
pub struct Http;

impl Http {
    /// fetch text document from the url and decode the result with the supplied
    /// response_text_decoder function
    pub fn fetch_with_text_response_decoder<APP, MSG, SUCCESS, ERROR>(
        url: &str,
        fetch_cb: SUCCESS,
        error_cb: ERROR,
    ) -> Cmd<APP, MSG>
    where
        SUCCESS: Fn(String) -> MSG + Clone + 'static,
        ERROR: Fn(TypeError) -> MSG + Clone + 'static,
        APP: Application<MSG> + 'static,
        MSG: 'static,
    {
        let fetch_cb = Callback::from(fetch_cb);

        let decoder_dispatcher =
            move |(response, program): (Response, Program<APP, MSG>)| {
                let response_promise =
                    response.text().expect("must be a promise text");

                let fetch_cb = fetch_cb.clone();

                let dispatcher: Closure<dyn FnMut(JsValue)> =
                    Closure::once(move |js_value: JsValue| {
                        let response_text = js_value
                            .as_string()
                            .expect("There's no string value");
                        let msg = fetch_cb.emit(response_text);
                        program.dispatch(msg);
                    });

                let _ = response_promise.then(&dispatcher);

                dispatcher.forget();
            };

        let error_cb = move |type_error| error_cb(type_error);
        Self::fetch_with_request_and_response_decoder(
            url,
            None,
            decoder_dispatcher,
            error_cb,
        )
    }

    /// API for fetching http rest request
    /// error_cb - request failed, in cases where a network is down, server is dead, etc.
    pub fn fetch_with_request_and_response_decoder<APP, MSG, DECODER, ERROR>(
        url: &str,
        request_init: Option<RequestInit>,
        decoder_dispatcher: DECODER,
        error_cb: ERROR,
    ) -> Cmd<APP, MSG>
    where
        APP: Application<MSG> + 'static,
        MSG: 'static,
        DECODER: Fn((Response, Program<APP, MSG>)) + 'static,
        ERROR: Fn(TypeError) -> MSG + 'static,
    {
        let url_clone = url.to_string();

        let error_cb = Callback::from(error_cb);
        let decoder_dispatcher_cb = Callback::from(decoder_dispatcher);
        Cmd::new(move |program| {
            let program_clone = program.clone();
            let error_cb = error_cb.clone();

            let window =
                web_sys::window().expect("should a refernce to window");

            let fetch_promise = if let Some(ref request_init) = request_init {
                window.fetch_with_str_and_init(&url_clone, request_init)
            } else {
                window.fetch_with_str(&url_clone)
            };

            let decoder_dispatcher_cb = decoder_dispatcher_cb.clone();
            let fetch_cb_closure: Closure<dyn FnMut(JsValue)> =
                Closure::once(move |js_value: JsValue| {
                    let response: Response = js_value.unchecked_into();
                    decoder_dispatcher_cb.emit((response, program_clone));
                });

            let error_cb_closure: Closure<dyn FnMut(JsValue)> =
                Closure::once(move |js_value: JsValue| {
                    let type_error: TypeError = js_value.unchecked_into();
                    program.dispatch(error_cb.emit(type_error));
                });

            let _ = fetch_promise
                .then(&fetch_cb_closure)
                .catch(&error_cb_closure);

            fetch_cb_closure.forget();
            error_cb_closure.forget();
        })
    }
}