Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gRPC-web support in WASM (BCHD API for SLP). #1127

Merged
merged 9 commits into from Nov 11, 2021
16 changes: 16 additions & 0 deletions mm2src/coins/utxo/bchd_grpc.rs
Expand Up @@ -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();
}
}
17 changes: 14 additions & 3 deletions mm2src/common/grpc_web.rs
Expand Up @@ -147,11 +147,22 @@ where
}

#[cfg(target_arch = "wasm32")]
pub async fn post_grpc_web<Req, Res>(url: &str, _req: &Req) -> Result<Res, MmError<PostGrpcWebErr>>
pub async fn post_grpc_web<Req, Res>(url: &str, req: &Req) -> Result<Res, MmError<PostGrpcWebErr>>
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)
}
78 changes: 71 additions & 7 deletions mm2src/common/transport/wasm_http.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -67,6 +68,11 @@ impl FetchRequest {
self
}

pub fn body_bytes(mut self, body: Vec<u8>) -> 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 {
Expand All @@ -88,6 +94,15 @@ impl FetchRequest {
}
}

pub async fn request_array(self) -> FetchResult<Vec<u8>> {
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<FetchResult<String>>) {
let fut = async move {
let result = Self::fetch_str(request).await;
Expand All @@ -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<String> {
fn spawn_fetch_array(request: Self, tx: oneshot::Sender<FetchResult<Vec<u8>>>) {
let fut = async move {
let result = Self::fetch_array(request).await;
tx.send(result).ok();
};
spawn_local(fut);
}

async fn fetch(request: Self) -> FetchResult<JsResponse> {
let window = web_sys::window().expect("!window");
let uri = request.uri;

Expand Down Expand Up @@ -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<String> {
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) => {
Expand All @@ -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<Vec<u8>> {
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()))
}
}

Expand All @@ -183,12 +242,17 @@ impl FetchMethod {

enum RequestBody {
Utf8(String),
Bytes(Vec<u8>),
}

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()
},
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions mm2src/lp_ordermatch/lp_bot.rs
Expand Up @@ -83,6 +83,8 @@ pub enum Provider {
Coingecko,
#[serde(rename = "coinpaprika")]
Coinpaprika,
#[serde(rename = "nomics")]
Nomics,
#[serde(rename = "unknown")]
Unknown,
}
Expand Down
5 changes: 1 addition & 4 deletions mm2src/mm2_tests/lp_bot_tests.rs
Expand Up @@ -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"))]
Expand Down