Compare commits

...
Author SHA1 Message Date
aecsocket ebc5e6a276 fix2 2026-06-19 19:51:33 +01:00
aecsocket b584ad01cc fix clippy 2026-06-19 19:40:17 +01:00
aecsocket dbef06b1e6 fix backoff 2026-06-19 17:29:32 +01:00
aecsocket 71ec71e599 Tiltify API querying backoff 2026-06-19 17:13:50 +01:00
+57 -24
View File
@@ -1,6 +1,6 @@
use std::time::{Duration, Instant};
use eyre::eyre;
use eyre::{Result, eyre};
use serde::Deserialize;
use serde_json::json;
use tokio::sync::Mutex;
@@ -13,7 +13,24 @@ use crate::{
#[derive(Debug)]
pub struct TiltifyClient {
http: HttpClient,
token: Mutex<Option<TiltifyAccessToken>>,
state: Mutex<TiltifyState>,
}
#[derive(Debug)]
struct TiltifyState {
token: Option<TiltifyAccessToken>,
rate_limited_until: Instant,
rate_limit_backoff: Duration,
}
impl TiltifyState {
fn set_fetch_backoff(&mut self) {
self.rate_limited_until = Instant::now() + self.rate_limit_backoff;
self.rate_limit_backoff = self
.rate_limit_backoff
.saturating_mul(2)
.min(TILTIFY_MAX_RATE_LIMIT_BACKOFF);
}
}
#[derive(Debug)]
@@ -28,38 +45,34 @@ struct TiltifyTokenResponse {
expires_in: u64,
}
const TILTIFY_INITIAL_RATE_LIMIT_BACKOFF: Duration = Duration::from_secs(60);
const TILTIFY_MAX_RATE_LIMIT_BACKOFF: Duration = Duration::from_secs(15 * 60);
impl TiltifyClient {
pub fn new(http: HttpClient) -> Self {
Self {
http,
token: Mutex::new(None),
state: Mutex::new(TiltifyState {
token: None,
rate_limited_until: Instant::now(),
rate_limit_backoff: TILTIFY_INITIAL_RATE_LIMIT_BACKOFF,
}),
}
}
pub async fn access_token(&self) -> eyre::Result<String> {
let mut token = self.token.lock().await;
pub async fn access_token(&self) -> Result<String> {
let mut state = self.state.lock().await;
if let Some(token) = token.as_ref()
if let Some(token) = state.token.as_ref()
&& token.expires_at > Instant::now()
{
return Ok(token.access_token.clone());
}
let response = self.fetch_access_token().await?;
let expires_at = Instant::now()
+ Duration::from_secs(response.expires_in)
.saturating_sub(Duration::from_secs(60));
let access_token = response.access_token;
if state.rate_limited_until > Instant::now() {
return Err(eyre!("waiting for rate limit to reset"));
}
*token = Some(TiltifyAccessToken {
access_token: access_token.clone(),
expires_at,
});
Ok(access_token)
}
async fn fetch_access_token(&self) -> eyre::Result<TiltifyTokenResponse> {
if ENV.TILTIFY_CLIENT_ID.is_empty()
|| ENV.TILTIFY_CLIENT_SECRET.is_empty()
{
@@ -79,13 +92,33 @@ impl TiltifyClient {
}))
.send()
.await
.wrap_err("fetching OAuth token")?
.error_for_status()
.wrap_err("fetching OAuth token")?
.inspect_err(|_| state.set_fetch_backoff())
.wrap_err("fetching OAuth token")?;
let response = match response.error_for_status() {
Ok(response) => response,
Err(error) => {
state.set_fetch_backoff();
return Err(error).wrap_err("fetching OAuth token");
}
};
let response = response
.json::<TiltifyTokenResponse>()
.await
.wrap_err("parsing OAuth token response")?;
Ok(response)
let expires_at = Instant::now()
+ Duration::from_secs(response.expires_in)
.saturating_sub(Duration::from_secs(60));
let access_token = response.access_token;
state.token = Some(TiltifyAccessToken {
access_token: access_token.clone(),
expires_at,
});
state.rate_limit_backoff = TILTIFY_INITIAL_RATE_LIMIT_BACKOFF;
Ok(access_token)
}
}