use electrum_client::{Client, ConfigBuilder, ElectrumApi};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use tokio::time::sleep;
struct KeycloakTokenManager {
token: Arc<RwLock<Option<String>>>,
keycloak_url: String,
grant_type: String,
client_id: String,
client_secret: String,
}
impl KeycloakTokenManager {
fn new(
keycloak_url: String,
grant_type: String,
client_id: String,
client_secret: String,
) -> Self {
Self {
token: Arc::new(RwLock::new(None)),
keycloak_url,
client_id,
client_secret,
grant_type,
}
}
fn get_token(&self) -> Option<String> {
self.token.read().unwrap().clone()
}
async fn fetch_token(&self) -> Result<String, Box<dyn std::error::Error>> {
let url = format!("{}/protocol/openid-connect/token", self.keycloak_url);
let body = format!(
"grant_type={}&client_id={}&client_secret={}",
self.grant_type, self.client_id, self.client_secret
);
let response = bitreq::post(url)
.with_header("Content-Type", "application/x-www-form-urlencoded")
.with_body(body)
.send_async()
.await?;
let json: serde_json::Value = response.json()?;
let access_token = json["access_token"]
.as_str()
.ok_or("Missing access_token")?
.to_string();
Ok(format!("Bearer {}", access_token))
}
async fn refresh_loop(self: Arc<Self>) {
loop {
sleep(Duration::from_secs(240)).await;
match self.fetch_token().await {
Ok(new_token) => {
println!("Token refreshed successfully");
*self.token.write().unwrap() = Some(new_token);
}
Err(e) => {
eprintln!("Failed to refresh token: {}", e);
}
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let electrum_url = std::env::var("ELECTRUM_URL")
.expect("SHOULD have the `ELECTRUM_URL` environment variable!");
let keycloak_url = std::env::var("KEYCLOAK_URL")
.expect("SHOULD have the `KEYCLOAK_URL` environment variable!");
let grant_type = std::env::var("GRANT_TYPE").unwrap_or("client_credentials".to_string());
let client_id =
std::env::var("CLIENT_ID").expect("SHOULD have the `CLIENT_ID` environment variable!");
let client_secret = std::env::var("CLIENT_SECRET")
.expect("SHOULD have the `CLIENT_SECRET` environment variable!");
let token_manager = Arc::new(KeycloakTokenManager::new(
keycloak_url,
grant_type,
client_id,
client_secret,
));
let jwt_token = token_manager.fetch_token().await?;
println!("JWT_TOKEN='{}'", &jwt_token[..jwt_token.len().min(40)]);
*token_manager.token.write().unwrap() = Some(jwt_token);
let tm_clone = token_manager.clone();
tokio::spawn(async move {
tm_clone.refresh_loop().await;
});
let tm_for_provider = token_manager.clone();
let config = ConfigBuilder::new()
.authorization_provider(Some(Arc::new(move || tm_for_provider.get_token())))
.build();
let client = Client::from_config(&electrum_url, config)?;
loop {
match client.server_features() {
Ok(features) => println!("Connected: {:?}", features),
Err(e) => eprintln!("Error: {}", e),
}
tokio::time::sleep(Duration::from_secs(10)).await;
}
}