diff --git a/mm2src/coins/utxo/bchd_grpc.rs b/mm2src/coins/utxo/bchd_grpc.rs index 269efcb52d..5be42205c1 100644 --- a/mm2src/coins/utxo/bchd_grpc.rs +++ b/mm2src/coins/utxo/bchd_grpc.rs @@ -416,3 +416,19 @@ mod bchd_grpc_tests { } } } + +#[cfg(target_arch = "wasm32")] +mod wasm_tests { + use super::*; + use wasm_bindgen_test::*; + + wasm_bindgen_test_configure!(run_in_browser); + + #[wasm_bindgen_test] + async fn test_check_slp_transaction_valid() { + let url = "https://bchd-testnet.greyh.at:18335"; + // https://testnet.simpleledger.info/tx/c5f46ccc5431687154335d5b6526f1b9cfa961c44b97956b7bec77f884f56c73 + let tx = hex::decode("010000000232809631da50999813c96996d587ceda2829db5e16247477a0eafcbb1ab9a10b020000006a473044022057c88d815fa563eda8ef7d0dd5c522f4501ffa6110df455b151b31609f149c22022048fecfc9b16e983fbfd05b0d2b7c011c3dbec542577fa00cd9bd192b81961f8e4121036879df230663db4cd083c8eeb0f293f46abc460ad3c299b0089b72e6d472202cffffffff32809631da50999813c96996d587ceda2829db5e16247477a0eafcbb1ab9a10b030000006a4730440220539e1204d2805c0474111a1f233ff82c0ab06e6e2bfc0cbe4975eacae64a0b1f02200ec83d32c2180f5567d0f760e85f1efc99d9341cfebd86c9a334310f6d4381494121036879df230663db4cd083c8eeb0f293f46abc460ad3c299b0089b72e6d472202cffffffff040000000000000000406a04534c500001010453454e4420bb309e48930671582bea508f9a1d9b491e49b69be3d6f372dc08da2ac6e90eb7080000000000000001080000000000002326e8030000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ace8030000000000001976a9148cfffc2409d063437d6aa8b75a009b9ba51b71fc88ac9f694801000000001976a9148cfffc2409d063437d6aa8b75a009b9ba51b71fc88ac8983d460").unwrap(); + check_slp_transaction(&[url], tx).await.unwrap(); + } +} diff --git a/mm2src/common/grpc_web.rs b/mm2src/common/grpc_web.rs index 9f7110878d..0f9f0abf1c 100644 --- a/mm2src/common/grpc_web.rs +++ b/mm2src/common/grpc_web.rs @@ -147,11 +147,22 @@ where } #[cfg(target_arch = "wasm32")] -pub async fn post_grpc_web(url: &str, _req: &Req) -> Result> +pub async fn post_grpc_web(url: &str, req: &Req) -> Result> where Req: prost::Message + Send + 'static, Res: prost::Message + Default + Send + 'static, { - let _request = FetchRequest::post(url); - unimplemented!() + let body = encode_body(req)?; + let request = FetchRequest::post(url) + .body_bytes(body) + .header("content-type", "application/grpc-web+proto") + .header("accept", "application/grpc-web+proto") + // https://github.com/grpc/grpc-web/issues/85#issue-217223001 + .header("x-grpc-web", "1"); + + let response = request.request_array().await?; + + let reply = decode_body(response.1.into())?; + + Ok(reply) } diff --git a/mm2src/common/transport/wasm_http.rs b/mm2src/common/transport/wasm_http.rs index 72c901876f..576e74ebd7 100644 --- a/mm2src/common/transport/wasm_http.rs +++ b/mm2src/common/transport/wasm_http.rs @@ -4,6 +4,7 @@ use crate::stringify_js_error; use crate::transport::{SlurpError, SlurpResult}; use futures::channel::oneshot; use http::{HeaderMap, StatusCode}; +use js_sys::Uint8Array; use std::collections::HashMap; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -67,6 +68,11 @@ impl FetchRequest { self } + pub fn body_bytes(mut self, body: Vec) -> FetchRequest { + self.body = Some(RequestBody::Bytes(body)); + self + } + /// Set the mode to [`RequestMode::Cors`]. /// The request is no-cors by default. pub fn cors(mut self) -> FetchRequest { @@ -88,6 +94,15 @@ impl FetchRequest { } } + pub async fn request_array(self) -> FetchResult> { + let (tx, rx) = oneshot::channel(); + Self::spawn_fetch_array(self, tx); + match rx.await { + Ok(res) => res, + Err(_e) => MmError::err(SlurpError::Internal("Spawned future has been canceled".to_owned())), + } + } + fn spawn_fetch_str(request: Self, tx: oneshot::Sender>) { let fut = async move { let result = Self::fetch_str(request).await; @@ -96,8 +111,15 @@ impl FetchRequest { spawn_local(fut); } - /// The private non-Send method that is called in a spawned future. - async fn fetch_str(request: Self) -> FetchResult { + fn spawn_fetch_array(request: Self, tx: oneshot::Sender>>) { + let fut = async move { + let result = Self::fetch_array(request).await; + tx.send(result).ok(); + }; + spawn_local(fut); + } + + async fn fetch(request: Self) -> FetchResult { let window = web_sys::window().expect("!window"); let uri = request.uri; @@ -133,6 +155,23 @@ impl FetchRequest { }, }; + let status_code = js_response.status(); + let status_code = match StatusCode::from_u16(status_code) { + Ok(code) => code, + Err(e) => { + let error = format!("Unexpected HTTP status code, found {}: {}", status_code, e); + return MmError::err(SlurpError::ErrorDeserializing { uri, error }); + }, + }; + + Ok((status_code, js_response)) + } + + /// The private non-Send method that is called in a spawned future. + async fn fetch_str(request: Self) -> FetchResult { + let uri = request.uri.clone(); + let (status_code, js_response) = Self::fetch(request).await?; + let resp_txt_fut = match js_response.text() { Ok(txt) => txt, Err(e) => { @@ -155,15 +194,35 @@ impl FetchRequest { }, }; - let status_code = js_response.status(); - let status_code = match StatusCode::from_u16(status_code) { - Ok(code) => code, + Ok((status_code, resp_str)) + } + + /// The private non-Send method that is called in a spawned future. + async fn fetch_array(request: Self) -> FetchResult> { + let uri = request.uri.clone(); + let (status_code, js_response) = Self::fetch(request).await?; + + let resp_array_fut = match js_response.array_buffer() { + Ok(blob) => blob, Err(e) => { - let error = format!("Unexpected HTTP status code, found {}: {}", status_code, e); + let error = format!( + "Expected blob, found {:?}: {}", + js_response, + crate::stringify_js_error(&e) + ); return MmError::err(SlurpError::ErrorDeserializing { uri, error }); }, }; - Ok((status_code, resp_str)) + let resp_array = JsFuture::from(resp_array_fut) + .await + .map_to_mm(|e| SlurpError::ErrorDeserializing { + uri: uri.clone(), + error: stringify_js_error(&e), + })?; + + let array = Uint8Array::new(&resp_array); + + Ok((status_code, array.to_vec())) } } @@ -183,12 +242,17 @@ impl FetchMethod { enum RequestBody { Utf8(String), + Bytes(Vec), } impl RequestBody { fn into_js_value(self) -> JsValue { match self { RequestBody::Utf8(string) => JsValue::from_str(&string), + RequestBody::Bytes(bytes) => { + let js_array = Uint8Array::from(bytes.as_slice()); + js_array.into() + }, } } } diff --git a/mm2src/lp_ordermatch/lp_bot.rs b/mm2src/lp_ordermatch/lp_bot.rs index e3500c3f3f..3f6453a3c9 100644 --- a/mm2src/lp_ordermatch/lp_bot.rs +++ b/mm2src/lp_ordermatch/lp_bot.rs @@ -83,6 +83,8 @@ pub enum Provider { Coingecko, #[serde(rename = "coinpaprika")] Coinpaprika, + #[serde(rename = "nomics")] + Nomics, #[serde(rename = "unknown")] Unknown, } diff --git a/mm2src/mm2_tests/lp_bot_tests.rs b/mm2src/mm2_tests/lp_bot_tests.rs index 62cee59504..fe685606cb 100644 --- a/mm2src/mm2_tests/lp_bot_tests.rs +++ b/mm2src/mm2_tests/lp_bot_tests.rs @@ -11,10 +11,7 @@ mod tests { #[test] #[cfg(not(target_arch = "wasm32"))] - fn test_process_price_request() { - let resp = block_on(process_price_request(KMD_PRICE_ENDPOINT)); - assert_ne!(resp.is_err(), true); - } + fn test_process_price_request() { let _resp = block_on(process_price_request(KMD_PRICE_ENDPOINT)).unwrap(); } #[test] #[cfg(not(target_arch = "wasm32"))]