fedimint_api_client/api/
ws.rs1use 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 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 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}