|
|
@@ -1,3 +1,4 @@
|
|
|
+//! HTTP Mint client with pluggable transport
|
|
|
use std::collections::HashSet;
|
|
|
use std::sync::{Arc, RwLock as StdRwLock};
|
|
|
use std::time::{Duration, Instant};
|
|
|
@@ -6,17 +7,15 @@ use async_trait::async_trait;
|
|
|
use cdk_common::{nut19, MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
|
|
|
#[cfg(feature = "auth")]
|
|
|
use cdk_common::{Method, ProtectedEndpoint, RoutePath};
|
|
|
-use reqwest::{Client, IntoUrl};
|
|
|
use serde::de::DeserializeOwned;
|
|
|
use serde::Serialize;
|
|
|
#[cfg(feature = "auth")]
|
|
|
use tokio::sync::RwLock;
|
|
|
use tracing::instrument;
|
|
|
-#[cfg(not(target_arch = "wasm32"))]
|
|
|
use url::Url;
|
|
|
|
|
|
+use super::transport::Transport;
|
|
|
use super::{Error, MintConnector};
|
|
|
-use crate::error::ErrorResponse;
|
|
|
use crate::mint_url::MintUrl;
|
|
|
#[cfg(feature = "auth")]
|
|
|
use crate::nuts::nut22::MintAuthRequest;
|
|
|
@@ -29,119 +28,30 @@ use crate::nuts::{
|
|
|
#[cfg(feature = "auth")]
|
|
|
use crate::wallet::auth::{AuthMintConnector, AuthWallet};
|
|
|
|
|
|
-#[derive(Debug, Clone)]
|
|
|
-struct HttpClientCore {
|
|
|
- inner: Client,
|
|
|
-}
|
|
|
-
|
|
|
-impl HttpClientCore {
|
|
|
- fn new() -> Self {
|
|
|
- #[cfg(not(target_arch = "wasm32"))]
|
|
|
- if rustls::crypto::CryptoProvider::get_default().is_none() {
|
|
|
- let _ = rustls::crypto::ring::default_provider().install_default();
|
|
|
- }
|
|
|
-
|
|
|
- Self {
|
|
|
- inner: Client::new(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn client(&self) -> &Client {
|
|
|
- &self.inner
|
|
|
- }
|
|
|
-
|
|
|
- async fn http_get<U: IntoUrl + Send, R: DeserializeOwned>(
|
|
|
- &self,
|
|
|
- url: U,
|
|
|
- auth: Option<AuthToken>,
|
|
|
- ) -> Result<R, Error> {
|
|
|
- let mut request = self.client().get(url);
|
|
|
-
|
|
|
- if let Some(auth) = auth {
|
|
|
- request = request.header(auth.header_key(), auth.to_string());
|
|
|
- }
|
|
|
-
|
|
|
- let response = request
|
|
|
- .send()
|
|
|
- .await
|
|
|
- .map_err(|e| {
|
|
|
- Error::HttpError(
|
|
|
- e.status().map(|status_code| status_code.as_u16()),
|
|
|
- e.to_string(),
|
|
|
- )
|
|
|
- })?
|
|
|
- .text()
|
|
|
- .await
|
|
|
- .map_err(|e| {
|
|
|
- Error::HttpError(
|
|
|
- e.status().map(|status_code| status_code.as_u16()),
|
|
|
- e.to_string(),
|
|
|
- )
|
|
|
- })?;
|
|
|
-
|
|
|
- serde_json::from_str::<R>(&response).map_err(|err| {
|
|
|
- tracing::warn!("Http Response error: {}", err);
|
|
|
- match ErrorResponse::from_json(&response) {
|
|
|
- Ok(ok) => <ErrorResponse as Into<Error>>::into(ok),
|
|
|
- Err(err) => err.into(),
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- async fn http_post<U: IntoUrl + Send, P: Serialize + ?Sized, R: DeserializeOwned>(
|
|
|
- &self,
|
|
|
- url: U,
|
|
|
- auth_token: Option<AuthToken>,
|
|
|
- payload: &P,
|
|
|
- ) -> Result<R, Error> {
|
|
|
- let mut request = self.client().post(url).json(&payload);
|
|
|
-
|
|
|
- if let Some(auth) = auth_token {
|
|
|
- request = request.header(auth.header_key(), auth.to_string());
|
|
|
- }
|
|
|
-
|
|
|
- let response = request.send().await.map_err(|e| {
|
|
|
- Error::HttpError(
|
|
|
- e.status().map(|status_code| status_code.as_u16()),
|
|
|
- e.to_string(),
|
|
|
- )
|
|
|
- })?;
|
|
|
-
|
|
|
- let response = response.text().await.map_err(|e| {
|
|
|
- Error::HttpError(
|
|
|
- e.status().map(|status_code| status_code.as_u16()),
|
|
|
- e.to_string(),
|
|
|
- )
|
|
|
- })?;
|
|
|
-
|
|
|
- serde_json::from_str::<R>(&response).map_err(|err| {
|
|
|
- tracing::warn!("Http Response error: {}", err);
|
|
|
- match ErrorResponse::from_json(&response) {
|
|
|
- Ok(ok) => <ErrorResponse as Into<Error>>::into(ok),
|
|
|
- Err(err) => err.into(),
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
type Cache = (u64, HashSet<(nut19::Method, nut19::Path)>);
|
|
|
|
|
|
/// Http Client
|
|
|
#[derive(Debug, Clone)]
|
|
|
-pub struct HttpClient {
|
|
|
- core: HttpClientCore,
|
|
|
+pub struct HttpClient<T>
|
|
|
+where
|
|
|
+ T: Transport + Send + Sync + 'static,
|
|
|
+{
|
|
|
+ transport: Arc<T>,
|
|
|
mint_url: MintUrl,
|
|
|
cache_support: Arc<StdRwLock<Cache>>,
|
|
|
#[cfg(feature = "auth")]
|
|
|
auth_wallet: Arc<RwLock<Option<AuthWallet>>>,
|
|
|
}
|
|
|
|
|
|
-impl HttpClient {
|
|
|
+impl<T> HttpClient<T>
|
|
|
+where
|
|
|
+ T: Transport + Send + Sync + 'static,
|
|
|
+{
|
|
|
/// Create new [`HttpClient`]
|
|
|
#[cfg(feature = "auth")]
|
|
|
pub fn new(mint_url: MintUrl, auth_wallet: Option<AuthWallet>) -> Self {
|
|
|
Self {
|
|
|
- core: HttpClientCore::new(),
|
|
|
+ transport: T::default().into(),
|
|
|
mint_url,
|
|
|
auth_wallet: Arc::new(RwLock::new(auth_wallet)),
|
|
|
cache_support: Default::default(),
|
|
|
@@ -152,7 +62,7 @@ impl HttpClient {
|
|
|
/// Create new [`HttpClient`]
|
|
|
pub fn new(mint_url: MintUrl) -> Self {
|
|
|
Self {
|
|
|
- core: HttpClientCore::new(),
|
|
|
+ transport: T::default().into(),
|
|
|
cache_support: Default::default(),
|
|
|
mint_url,
|
|
|
}
|
|
|
@@ -176,7 +86,6 @@ impl HttpClient {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- #[cfg(not(target_arch = "wasm32"))]
|
|
|
/// Create new [`HttpClient`] with a proxy for specific TLDs.
|
|
|
/// Specifying `None` for `host_matcher` will use the proxy for all
|
|
|
/// requests.
|
|
|
@@ -186,32 +95,11 @@ impl HttpClient {
|
|
|
host_matcher: Option<&str>,
|
|
|
accept_invalid_certs: bool,
|
|
|
) -> Result<Self, Error> {
|
|
|
- let regex = host_matcher
|
|
|
- .map(regex::Regex::new)
|
|
|
- .transpose()
|
|
|
- .map_err(|e| Error::Custom(e.to_string()))?;
|
|
|
- let client = reqwest::Client::builder()
|
|
|
- .proxy(reqwest::Proxy::custom(move |url| {
|
|
|
- if let Some(matcher) = regex.as_ref() {
|
|
|
- if let Some(host) = url.host_str() {
|
|
|
- if matcher.is_match(host) {
|
|
|
- return Some(proxy.clone());
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- None
|
|
|
- }))
|
|
|
- .danger_accept_invalid_certs(accept_invalid_certs) // Allow self-signed certs
|
|
|
- .build()
|
|
|
- .map_err(|e| {
|
|
|
- Error::HttpError(
|
|
|
- e.status().map(|status_code| status_code.as_u16()),
|
|
|
- e.to_string(),
|
|
|
- )
|
|
|
- })?;
|
|
|
+ let mut transport = T::default();
|
|
|
+ transport.with_proxy(proxy, host_matcher, accept_invalid_certs)?;
|
|
|
|
|
|
Ok(Self {
|
|
|
- core: HttpClientCore { inner: client },
|
|
|
+ transport: transport.into(),
|
|
|
mint_url,
|
|
|
#[cfg(feature = "auth")]
|
|
|
auth_wallet: Arc::new(RwLock::new(None)),
|
|
|
@@ -231,7 +119,7 @@ impl HttpClient {
|
|
|
payload: &P,
|
|
|
) -> Result<R, Error>
|
|
|
where
|
|
|
- P: Serialize + ?Sized,
|
|
|
+ P: Serialize + ?Sized + Send + Sync,
|
|
|
R: DeserializeOwned,
|
|
|
{
|
|
|
let started = Instant::now();
|
|
|
@@ -259,8 +147,12 @@ impl HttpClient {
|
|
|
})?;
|
|
|
|
|
|
let result = match method {
|
|
|
- nut19::Method::Get => self.core.http_get(url, auth_token.clone()).await,
|
|
|
- nut19::Method::Post => self.core.http_post(url, auth_token.clone(), payload).await,
|
|
|
+ nut19::Method::Get => self.transport.http_get(url, auth_token.clone()).await,
|
|
|
+ nut19::Method::Post => {
|
|
|
+ self.transport
|
|
|
+ .http_post(url, auth_token.clone(), payload)
|
|
|
+ .await
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
if result.is_ok() {
|
|
|
@@ -291,15 +183,18 @@ impl HttpClient {
|
|
|
|
|
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
|
|
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
|
|
-impl MintConnector for HttpClient {
|
|
|
+impl<T> MintConnector for HttpClient<T>
|
|
|
+where
|
|
|
+ T: Transport + Send + Sync + 'static,
|
|
|
+{
|
|
|
/// Get Active Mint Keys [NUT-01]
|
|
|
#[instrument(skip(self), fields(mint_url = %self.mint_url))]
|
|
|
async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
|
|
|
let url = self.mint_url.join_paths(&["v1", "keys"])?;
|
|
|
|
|
|
Ok(self
|
|
|
- .core
|
|
|
- .http_get::<_, KeysResponse>(url, None)
|
|
|
+ .transport
|
|
|
+ .http_get::<KeysResponse>(url, None)
|
|
|
.await?
|
|
|
.keysets)
|
|
|
}
|
|
|
@@ -311,7 +206,7 @@ impl MintConnector for HttpClient {
|
|
|
.mint_url
|
|
|
.join_paths(&["v1", "keys", &keyset_id.to_string()])?;
|
|
|
|
|
|
- let keys_response = self.core.http_get::<_, KeysResponse>(url, None).await?;
|
|
|
+ let keys_response = self.transport.http_get::<KeysResponse>(url, None).await?;
|
|
|
|
|
|
Ok(keys_response.keysets.first().unwrap().clone())
|
|
|
}
|
|
|
@@ -320,7 +215,7 @@ impl MintConnector for HttpClient {
|
|
|
#[instrument(skip(self), fields(mint_url = %self.mint_url))]
|
|
|
async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error> {
|
|
|
let url = self.mint_url.join_paths(&["v1", "keysets"])?;
|
|
|
- self.core.http_get(url, None).await
|
|
|
+ self.transport.http_get(url, None).await
|
|
|
}
|
|
|
|
|
|
/// Mint Quote [NUT-04]
|
|
|
@@ -341,7 +236,7 @@ impl MintConnector for HttpClient {
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
|
|
|
- self.core.http_post(url, auth_token, &request).await
|
|
|
+ self.transport.http_post(url, auth_token, &request).await
|
|
|
}
|
|
|
|
|
|
/// Mint Quote status
|
|
|
@@ -361,7 +256,7 @@ impl MintConnector for HttpClient {
|
|
|
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
- self.core.http_get(url, auth_token).await
|
|
|
+ self.transport.http_get(url, auth_token).await
|
|
|
}
|
|
|
|
|
|
/// Mint Tokens [NUT-04]
|
|
|
@@ -399,7 +294,7 @@ impl MintConnector for HttpClient {
|
|
|
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
- self.core.http_post(url, auth_token, &request).await
|
|
|
+ self.transport.http_post(url, auth_token, &request).await
|
|
|
}
|
|
|
|
|
|
/// Melt Quote Status
|
|
|
@@ -419,7 +314,7 @@ impl MintConnector for HttpClient {
|
|
|
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
- self.core.http_get(url, auth_token).await
|
|
|
+ self.transport.http_get(url, auth_token).await
|
|
|
}
|
|
|
|
|
|
/// Melt [NUT-05]
|
|
|
@@ -467,7 +362,7 @@ impl MintConnector for HttpClient {
|
|
|
/// Helper to get mint info
|
|
|
async fn get_mint_info(&self) -> Result<MintInfo, Error> {
|
|
|
let url = self.mint_url.join_paths(&["v1", "info"])?;
|
|
|
- let info: MintInfo = self.core.http_get(url, None).await?;
|
|
|
+ let info: MintInfo = self.transport.http_get(url, None).await?;
|
|
|
|
|
|
if let Ok(mut cache_support) = self.cache_support.write() {
|
|
|
*cache_support = (
|
|
|
@@ -509,7 +404,7 @@ impl MintConnector for HttpClient {
|
|
|
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
- self.core.http_post(url, auth_token, &request).await
|
|
|
+ self.transport.http_post(url, auth_token, &request).await
|
|
|
}
|
|
|
|
|
|
/// Restore request [NUT-13]
|
|
|
@@ -523,7 +418,7 @@ impl MintConnector for HttpClient {
|
|
|
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
- self.core.http_post(url, auth_token, &request).await
|
|
|
+ self.transport.http_post(url, auth_token, &request).await
|
|
|
}
|
|
|
|
|
|
/// Mint Quote Bolt12 [NUT-23]
|
|
|
@@ -544,7 +439,7 @@ impl MintConnector for HttpClient {
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
|
|
|
- self.core.http_post(url, auth_token, &request).await
|
|
|
+ self.transport.http_post(url, auth_token, &request).await
|
|
|
}
|
|
|
|
|
|
/// Mint Quote Bolt12 status
|
|
|
@@ -564,7 +459,7 @@ impl MintConnector for HttpClient {
|
|
|
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
- self.core.http_get(url, auth_token).await
|
|
|
+ self.transport.http_get(url, auth_token).await
|
|
|
}
|
|
|
|
|
|
/// Melt Quote Bolt12 [NUT-23]
|
|
|
@@ -583,7 +478,7 @@ impl MintConnector for HttpClient {
|
|
|
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
- self.core.http_post(url, auth_token, &request).await
|
|
|
+ self.transport.http_post(url, auth_token, &request).await
|
|
|
}
|
|
|
|
|
|
/// Melt Quote Bolt12 Status [NUT-23]
|
|
|
@@ -603,7 +498,7 @@ impl MintConnector for HttpClient {
|
|
|
|
|
|
#[cfg(not(feature = "auth"))]
|
|
|
let auth_token = None;
|
|
|
- self.core.http_get(url, auth_token).await
|
|
|
+ self.transport.http_get(url, auth_token).await
|
|
|
}
|
|
|
|
|
|
/// Melt Bolt12 [NUT-23]
|
|
|
@@ -632,18 +527,24 @@ impl MintConnector for HttpClient {
|
|
|
/// Http Client
|
|
|
#[derive(Debug, Clone)]
|
|
|
#[cfg(feature = "auth")]
|
|
|
-pub struct AuthHttpClient {
|
|
|
- core: HttpClientCore,
|
|
|
+pub struct AuthHttpClient<T>
|
|
|
+where
|
|
|
+ T: Transport + Send + Sync + 'static,
|
|
|
+{
|
|
|
+ transport: Arc<T>,
|
|
|
mint_url: MintUrl,
|
|
|
cat: Arc<RwLock<AuthToken>>,
|
|
|
}
|
|
|
|
|
|
#[cfg(feature = "auth")]
|
|
|
-impl AuthHttpClient {
|
|
|
+impl<T> AuthHttpClient<T>
|
|
|
+where
|
|
|
+ T: Transport + Send + Sync + 'static,
|
|
|
+{
|
|
|
/// Create new [`AuthHttpClient`]
|
|
|
pub fn new(mint_url: MintUrl, cat: Option<AuthToken>) -> Self {
|
|
|
Self {
|
|
|
- core: HttpClientCore::new(),
|
|
|
+ transport: T::default().into(),
|
|
|
mint_url,
|
|
|
cat: Arc::new(RwLock::new(
|
|
|
cat.unwrap_or(AuthToken::ClearAuth("".to_string())),
|
|
|
@@ -655,7 +556,10 @@ impl AuthHttpClient {
|
|
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
|
|
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
|
|
#[cfg(feature = "auth")]
|
|
|
-impl AuthMintConnector for AuthHttpClient {
|
|
|
+impl<T> AuthMintConnector for AuthHttpClient<T>
|
|
|
+where
|
|
|
+ T: Transport + Send + Sync + 'static,
|
|
|
+{
|
|
|
async fn get_auth_token(&self) -> Result<AuthToken, Error> {
|
|
|
Ok(self.cat.read().await.clone())
|
|
|
}
|
|
|
@@ -668,7 +572,7 @@ impl AuthMintConnector for AuthHttpClient {
|
|
|
/// Get Mint Info [NUT-06]
|
|
|
async fn get_mint_info(&self) -> Result<MintInfo, Error> {
|
|
|
let url = self.mint_url.join_paths(&["v1", "info"])?;
|
|
|
- let mint_info: MintInfo = self.core.http_get::<_, MintInfo>(url, None).await?;
|
|
|
+ let mint_info: MintInfo = self.transport.http_get::<MintInfo>(url, None).await?;
|
|
|
|
|
|
Ok(mint_info)
|
|
|
}
|
|
|
@@ -680,7 +584,7 @@ impl AuthMintConnector for AuthHttpClient {
|
|
|
self.mint_url
|
|
|
.join_paths(&["v1", "auth", "blind", "keys", &keyset_id.to_string()])?;
|
|
|
|
|
|
- let mut keys_response = self.core.http_get::<_, KeysResponse>(url, None).await?;
|
|
|
+ let mut keys_response = self.transport.http_get::<KeysResponse>(url, None).await?;
|
|
|
|
|
|
let keyset = keys_response
|
|
|
.keysets
|
|
|
@@ -698,14 +602,14 @@ impl AuthMintConnector for AuthHttpClient {
|
|
|
.mint_url
|
|
|
.join_paths(&["v1", "auth", "blind", "keysets"])?;
|
|
|
|
|
|
- self.core.http_get(url, None).await
|
|
|
+ self.transport.http_get(url, None).await
|
|
|
}
|
|
|
|
|
|
/// Mint Tokens [NUT-22]
|
|
|
#[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
|
|
|
async fn post_mint_blind_auth(&self, request: MintAuthRequest) -> Result<MintResponse, Error> {
|
|
|
let url = self.mint_url.join_paths(&["v1", "auth", "blind", "mint"])?;
|
|
|
- self.core
|
|
|
+ self.transport
|
|
|
.http_post(url, Some(self.cat.read().await.clone()), &request)
|
|
|
.await
|
|
|
}
|