tonic_web_wasm_client/
call.rs1use http::{
2 header::{ACCEPT, CONTENT_TYPE},
3 response::Builder,
4 HeaderMap, HeaderValue, Request, Response,
5};
6use http_body_util::BodyExt;
7use js_sys::{Array, Uint8Array};
8use tonic::body::Body;
9use wasm_bindgen::JsValue;
10use web_sys::{Headers, RequestCredentials, RequestInit};
11
12use crate::{fetch::fetch, options::FetchOptions, Error, ResponseBody};
13
14pub async fn call(
15 mut base_url: String,
16 request: Request<Body>,
17 options: FetchOptions,
18) -> Result<Response<ResponseBody>, Error> {
19 base_url.push_str(&request.uri().to_string());
20
21 let headers = prepare_headers(request.headers())?;
22 let body = prepare_body(request).await?;
23
24 let request = prepare_request(&base_url, headers, body)?;
25 let (init, abort) = options.request_init()?;
26 let response = fetch(&request, &init).await?;
27
28 let result = Response::builder().status(response.status());
29 let (result, content_type) = set_response_headers(result, &response)?;
30
31 let content_type = content_type.ok_or(Error::MissingContentTypeHeader)?;
32 let body_stream = response.body().ok_or(Error::MissingResponseBody)?;
33
34 let body = ResponseBody::new(body_stream, &content_type, abort)?;
35
36 result.body(body).map_err(Into::into)
37}
38
39fn prepare_headers(header_map: &HeaderMap<HeaderValue>) -> Result<Headers, Error> {
40 let headers = Headers::new().map_err(Error::js_error)?;
42 headers
43 .append(CONTENT_TYPE.as_str(), "application/grpc-web+proto")
44 .map_err(Error::js_error)?;
45 headers
46 .append(ACCEPT.as_str(), "application/grpc-web+proto")
47 .map_err(Error::js_error)?;
48 headers.append("x-grpc-web", "1").map_err(Error::js_error)?;
49
50 for (header_name, header_value) in header_map.iter() {
52 if header_name != CONTENT_TYPE {
54 headers
55 .set(header_name.as_str(), header_value.to_str()?)
56 .map_err(Error::js_error)?;
57 }
58 }
59
60 Ok(headers)
61}
62
63async fn prepare_body(request: Request<Body>) -> Result<Option<JsValue>, Error> {
64 let body = Some(request.collect().await?.to_bytes());
65 Ok(body.map(|bytes| Uint8Array::from(bytes.as_ref()).into()))
66}
67
68fn prepare_request(
69 url: &str,
70 headers: Headers,
71 body: Option<JsValue>,
72) -> Result<web_sys::Request, Error> {
73 let init = RequestInit::new();
74
75 init.set_method("POST");
76 init.set_headers(headers.as_ref());
77 if let Some(ref body) = body {
78 init.set_body(body);
79 }
80 init.set_credentials(RequestCredentials::SameOrigin);
81
82 web_sys::Request::new_with_str_and_init(url, &init).map_err(Error::js_error)
83}
84
85fn set_response_headers(
86 mut result: Builder,
87 response: &web_sys::Response,
88) -> Result<(Builder, Option<String>), Error> {
89 let headers = response.headers();
90
91 let header_iter = js_sys::try_iter(headers.as_ref()).map_err(Error::js_error)?;
92
93 let mut content_type = None;
94
95 if let Some(header_iter) = header_iter {
96 for header in header_iter {
97 let header = header.map_err(Error::js_error)?;
98 let pair: Array = header.into();
99
100 let header_name = pair.get(0).as_string();
101 let header_value = pair.get(1).as_string();
102
103 match (header_name, header_value) {
104 (Some(header_name), Some(header_value)) => {
105 if header_name == CONTENT_TYPE.as_str() {
106 content_type = Some(header_value.clone());
107 }
108
109 result = result.header(header_name, header_value);
110 }
111 _ => continue,
112 }
113 }
114 }
115
116 Ok((result, content_type))
117}