2
0
This commit is contained in:
2026-03-22 17:41:55 +09:00
parent e27079d175
commit 45a7c1b9ae
4 changed files with 328 additions and 2 deletions

View File

@@ -194,6 +194,20 @@ impl XrpcClient {
url: &str,
full_url: &str,
body: Option<&B>,
) -> Result<T> {
self.dpop_request_with_retry_proxy(oauth, token, method, url, full_url, body, None).await
}
/// DPoP-authenticated request with optional proxy header
async fn dpop_request_with_retry_proxy<B: Serialize, T: DeserializeOwned>(
&self,
oauth: &OAuthSession,
token: &str,
method: &str,
url: &str,
full_url: &str,
body: Option<&B>,
proxy: Option<&str>,
) -> Result<T> {
let mut dpop_nonce: Option<String> = None;
@@ -205,7 +219,7 @@ impl XrpcClient {
dpop_nonce.as_deref(),
)?;
let builder = match method {
let mut builder = match method {
"GET" => self.inner.get(url),
_ => {
let b = self.inner.post(url);
@@ -217,6 +231,10 @@ impl XrpcClient {
}
};
if let Some(proxy_did) = proxy {
builder = builder.header("atproto-proxy", proxy_did);
}
let res = builder
.header("Authorization", format!("DPoP {}", token))
.header("DPoP", dpop_proof)
@@ -302,6 +320,64 @@ impl XrpcClient {
anyhow::bail!("XRPC DPoP call failed after nonce retry");
}
/// Authenticated GET with atproto-proxy header (for chat API)
pub async fn query_auth_proxy<T: DeserializeOwned>(
&self,
nsid: &str,
params: &[(&str, &str)],
token: &str,
proxy: &str,
) -> Result<T> {
let mut url = format!("{}/xrpc/{}", Self::ensure_scheme(&self.pds_host), nsid);
if !params.is_empty() {
url.push('?');
let qs: Vec<String> = params.iter().map(|(k, v)| format!("{}={}", k, v)).collect();
url.push_str(&qs.join("&"));
}
let full_url = url.clone();
if let Some(oauth) = self.try_load_oauth() {
self.dpop_request_with_retry_proxy(&oauth, token, "GET", &url, &full_url, None::<&()>, Some(proxy))
.await
} else {
let res = self.inner
.get(&url)
.header("Authorization", format!("Bearer {}", token))
.header("atproto-proxy", proxy)
.send()
.await
.context("XRPC proxy query failed")?;
self.handle_response(res).await
}
}
/// Authenticated POST with atproto-proxy header (for chat API)
pub async fn call_proxy<B: Serialize, T: DeserializeOwned>(
&self,
nsid: &str,
body: &B,
token: &str,
proxy: &str,
) -> Result<T> {
let url = format!("{}/xrpc/{}", Self::ensure_scheme(&self.pds_host), nsid);
let full_url = url.clone();
if let Some(oauth) = self.try_load_oauth() {
self.dpop_request_with_retry_proxy(&oauth, token, "POST", &url, &full_url, Some(body), Some(proxy))
.await
} else {
let res = self.inner
.post(&url)
.header("Authorization", format!("Bearer {}", token))
.header("atproto-proxy", proxy)
.json(body)
.send()
.await
.context("XRPC proxy call failed")?;
self.handle_response(res).await
}
}
/// Handle an XRPC response: check status, parse ATProto errors, deserialize body.
async fn handle_response<T: DeserializeOwned>(&self, res: reqwest::Response) -> Result<T> {
if !res.status().is_success() {