fedimint_api_client/api/
ws.rs

1use std::sync::Arc;
2
3#[allow(unused)]
4use anyhow::anyhow;
5use async_trait::async_trait;
6use fedimint_core::module::{ApiMethod, ApiRequestErased};
7#[cfg(not(target_family = "wasm"))]
8use fedimint_core::rustls::install_crypto_provider;
9use fedimint_core::util::SafeUrl;
10use fedimint_logging::LOG_NET_WS;
11use jsonrpsee_core::client::ClientT;
12pub use jsonrpsee_core::client::Error as JsonRpcClientError;
13use jsonrpsee_types::ErrorCode;
14#[cfg(target_family = "wasm")]
15use jsonrpsee_wasm_client::{Client as WsClient, WasmClientBuilder as WsClientBuilder};
16#[allow(unused)]
17#[cfg(not(target_family = "wasm"))]
18use jsonrpsee_ws_client::{WsClient, WsClientBuilder};
19use serde_json::Value;
20use tracing::trace;
21pub type JsonRpcResult<T> = Result<T, JsonRpcClientError>;
22
23use super::Connector;
24use crate::api::{DynGuaridianConnection, IGuardianConnection, PeerError, PeerResult};
25
26#[derive(Debug, Clone)]
27pub struct WebsocketConnector {}
28
29impl WebsocketConnector {
30    pub fn new() -> Self {
31        Self {}
32    }
33
34    async fn make_new_connection(
35        &self,
36        url: &SafeUrl,
37        api_secret: Option<&str>,
38    ) -> PeerResult<Arc<WsClient>> {
39        trace!(target: LOG_NET_WS, %url, "Creating new websocket connection");
40
41        #[cfg(not(target_family = "wasm"))]
42        let mut client = {
43            use jsonrpsee_ws_client::{CustomCertStore, WsClientBuilder};
44            use tokio_rustls::rustls::RootCertStore;
45
46            install_crypto_provider().await;
47            let webpki_roots = webpki_roots::TLS_SERVER_ROOTS.iter().cloned();
48            let mut root_certs = RootCertStore::empty();
49            root_certs.extend(webpki_roots);
50
51            let tls_cfg = CustomCertStore::builder()
52                .with_root_certificates(root_certs)
53                .with_no_client_auth();
54
55            WsClientBuilder::default()
56                .max_concurrent_requests(u16::MAX as usize)
57                .with_custom_cert_store(tls_cfg)
58        };
59
60        #[cfg(target_family = "wasm")]
61        let client = WsClientBuilder::default().max_concurrent_requests(u16::MAX as usize);
62
63        if let Some(api_secret) = api_secret {
64            #[cfg(not(target_family = "wasm"))]
65            {
66                // on native platforms, jsonrpsee-client ignores `user:pass@...` in the Url,
67                // but we can set up the headers manually
68
69                use base64::Engine as _;
70                use jsonrpsee_ws_client::{HeaderMap, HeaderValue};
71                let mut headers = HeaderMap::new();
72
73                let auth = base64::engine::general_purpose::STANDARD
74                    .encode(format!("fedimint:{api_secret}"));
75
76                headers.insert(
77                    "Authorization",
78                    HeaderValue::from_str(&format!("Basic {auth}")).expect("Can't fail"),
79                );
80
81                client = client.set_headers(headers);
82            }
83            #[cfg(target_family = "wasm")]
84            {
85                // on wasm, url will be handled by the browser, which should take care of
86                // `user:pass@...`
87                let mut url = url.clone();
88                url.set_username("fedimint")
89                    .map_err(|_| PeerError::InvalidEndpoint(anyhow!("invalid username")))?;
90                url.set_password(Some(&api_secret))
91                    .map_err(|_| PeerError::InvalidEndpoint(anyhow!("invalid secret")))?;
92
93                let client = client
94                    .build(url.as_str())
95                    .await
96                    .map_err(|err| PeerError::InternalClientError(err.into()))?;
97
98                return Ok(Arc::new(client));
99            }
100        }
101
102        let client = client
103            .build(url.as_str())
104            .await
105            .map_err(|err| PeerError::InternalClientError(err.into()))?;
106
107        Ok(Arc::new(client))
108    }
109}
110
111impl Default for WebsocketConnector {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117#[async_trait::async_trait]
118impl Connector for WebsocketConnector {
119    async fn connect_guardian(
120        &self,
121        url: &SafeUrl,
122        api_secret: Option<&str>,
123    ) -> PeerResult<DynGuaridianConnection> {
124        let client = self.make_new_connection(url, api_secret).await?;
125        Ok(client.into_dyn())
126    }
127}
128
129#[async_trait]
130impl IGuardianConnection for WsClient {
131    async fn request(&self, method: ApiMethod, request: ApiRequestErased) -> PeerResult<Value> {
132        let method = match method {
133            ApiMethod::Core(method) => method,
134            ApiMethod::Module(module_id, method) => format!("module_{module_id}_{method}"),
135        };
136
137        Ok(ClientT::request(self, &method, [request.to_json()])
138            .await
139            .map_err(jsonrpc_error_to_peer_error)?)
140    }
141
142    async fn await_disconnection(&self) {
143        self.on_disconnect().await;
144    }
145
146    fn is_connected(&self) -> bool {
147        WsClient::is_connected(self)
148    }
149}
150
151#[async_trait]
152impl IGuardianConnection for Arc<WsClient> {
153    async fn request(&self, method: ApiMethod, request: ApiRequestErased) -> PeerResult<Value> {
154        let method = match method {
155            ApiMethod::Core(method) => method,
156            ApiMethod::Module(module_id, method) => format!("module_{module_id}_{method}"),
157        };
158
159        Ok(
160            ClientT::request(self.as_ref(), &method, [request.to_json()])
161                .await
162                .map_err(jsonrpc_error_to_peer_error)?,
163        )
164    }
165
166    async fn await_disconnection(&self) {
167        self.on_disconnect().await;
168    }
169    fn is_connected(&self) -> bool {
170        WsClient::is_connected(self)
171    }
172}
173
174fn jsonrpc_error_to_peer_error(jsonrpc_error: JsonRpcClientError) -> PeerError {
175    match jsonrpc_error {
176        JsonRpcClientError::Call(error_object) => {
177            let error = anyhow!(error_object.message().to_owned());
178            match ErrorCode::from(error_object.code()) {
179                ErrorCode::ParseError | ErrorCode::OversizedRequest | ErrorCode::InvalidRequest => {
180                    PeerError::InvalidRequest(error)
181                }
182                ErrorCode::MethodNotFound => PeerError::InvalidRpcId(error),
183                ErrorCode::InvalidParams => PeerError::InvalidRequest(error),
184                ErrorCode::InternalError | ErrorCode::ServerIsBusy | ErrorCode::ServerError(_) => {
185                    PeerError::ServerError(error)
186                }
187            }
188        }
189        JsonRpcClientError::Transport(error) => PeerError::Transport(anyhow!(error)),
190        JsonRpcClientError::RestartNeeded(arc) => PeerError::Transport(anyhow!(arc)),
191        JsonRpcClientError::ParseError(error) => PeerError::InvalidResponse(anyhow!(error)),
192        JsonRpcClientError::InvalidSubscriptionId => {
193            PeerError::Transport(anyhow!("Invalid subscription id"))
194        }
195        JsonRpcClientError::InvalidRequestId(invalid_request_id) => {
196            PeerError::InvalidRequest(anyhow!(invalid_request_id))
197        }
198        JsonRpcClientError::RequestTimeout => PeerError::Transport(anyhow!("Request timeout")),
199        JsonRpcClientError::Custom(e) => PeerError::Transport(anyhow!(e)),
200        JsonRpcClientError::HttpNotImplemented => {
201            PeerError::ServerError(anyhow!("Http not implemented"))
202        }
203        JsonRpcClientError::EmptyBatchRequest(empty_batch_request) => {
204            PeerError::InvalidRequest(anyhow!(empty_batch_request))
205        }
206        JsonRpcClientError::RegisterMethod(register_method_error) => {
207            PeerError::InvalidResponse(anyhow!(register_method_error))
208        }
209    }
210}