use std::sync::Arc;
use tokio::sync::RwLock;
use crate::error::WaveError;
const TOKEN_URL: &str = "https://api.waveapps.com/oauth2/token/";
pub type TokenRefreshCallback = Arc<dyn Fn(&str, &str) + Send + Sync>;
#[derive(Clone)]
pub struct OAuthConfig {
pub client_id: String,
pub client_secret: String,
pub access_token: String,
pub refresh_token: String,
pub redirect_uri: String,
pub on_token_refresh: Option<TokenRefreshCallback>,
}
#[derive(Clone)]
pub(crate) struct AuthState {
pub(crate) config: OAuthConfig,
pub(crate) tokens: Arc<RwLock<TokenPair>>,
}
pub(crate) struct TokenPair {
pub access_token: String,
pub refresh_token: String,
}
impl AuthState {
pub fn new(config: OAuthConfig) -> Self {
let tokens = TokenPair {
access_token: config.access_token.clone(),
refresh_token: config.refresh_token.clone(),
};
Self {
config,
tokens: Arc::new(RwLock::new(tokens)),
}
}
pub async fn access_token(&self) -> String {
self.tokens.read().await.access_token.clone()
}
pub async fn refresh(&self, http: &reqwest::Client) -> Result<(), WaveError> {
let refresh_token = self.tokens.read().await.refresh_token.clone();
let params = [
("client_id", self.config.client_id.as_str()),
("client_secret", self.config.client_secret.as_str()),
("refresh_token", refresh_token.as_str()),
("grant_type", "refresh_token"),
("redirect_uri", self.config.redirect_uri.as_str()),
];
let resp = http
.post(TOKEN_URL)
.form(¶ms)
.send()
.await
.map_err(|e| WaveError::TokenRefresh(e.to_string()))?;
if !resp.status().is_success() {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
return Err(WaveError::TokenRefresh(format!(
"HTTP {status} — {body}"
)));
}
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| WaveError::TokenRefresh(e.to_string()))?;
let new_access = body["access_token"]
.as_str()
.ok_or_else(|| WaveError::TokenRefresh("missing access_token in response".into()))?;
let new_refresh = body["refresh_token"]
.as_str()
.ok_or_else(|| WaveError::TokenRefresh("missing refresh_token in response".into()))?;
{
let mut tokens = self.tokens.write().await;
tokens.access_token = new_access.to_string();
tokens.refresh_token = new_refresh.to_string();
}
if let Some(cb) = &self.config.on_token_refresh {
cb(new_access, new_refresh);
}
Ok(())
}
}