From 61b91cf5442e97b213a70e6f699713041c73715d Mon Sep 17 00:00:00 2001 From: Orual Date: Mon, 10 Nov 2025 14:54:26 -0500 Subject: [PATCH] ported minimal changes from trait branch --- crates/jacquard-common/src/types/cid.rs | 9 +- crates/jacquard/src/client.rs | 248 ++++++++++++++++++++++++ 2 files changed, 251 insertions(+), 6 deletions(-) diff --git a/crates/jacquard-common/src/types/cid.rs b/crates/jacquard-common/src/types/cid.rs index 0456dfc14..77398e2ed 100644 --- a/crates/jacquard-common/src/types/cid.rs +++ b/crates/jacquard-common/src/types/cid.rs @@ -1,4 +1,4 @@ -use crate::{CowStr, IntoStatic}; +use crate::{CowStr, IntoStatic, cowstr::ToCowStr}; pub use cid::Cid as IpldCid; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor}; use smol_str::ToSmolStr; @@ -379,11 +379,8 @@ where where E: serde::de::Error, { - if let Ok(cid) = IpldCid::try_from(v.as_bytes()) { - Ok(CidLink(Cid::ipld(cid))) - } else { - Err(E::custom("invalid CID string")) - } + // TODO: currently overly permissive, should fix + Ok(CidLink::cow_str(v.to_cowstr()).into_static()) } fn visit_map(self, mut map: A) -> Result diff --git a/crates/jacquard/src/client.rs b/crates/jacquard/src/client.rs index 96ba40882..9dbd13322 100644 --- a/crates/jacquard/src/client.rs +++ b/crates/jacquard/src/client.rs @@ -47,6 +47,8 @@ use jacquard_common::types::recordkey::{RecordKey, Rkey}; use jacquard_common::types::string::AtUri; #[cfg(feature = "api")] use jacquard_common::types::uri::RecordUri; +#[cfg(not(target_arch = "wasm32"))] +use jacquard_common::xrpc::XrpcResponse; use jacquard_common::xrpc::{ CallOptions, Response, XrpcClient, XrpcError, XrpcExt, XrpcRequest, XrpcResp, }; @@ -58,6 +60,7 @@ use jacquard_common::{ use jacquard_identity::resolver::{ DidDocResponse, IdentityError, IdentityResolver, ResolverOptions, }; +use jacquard_identity::{JacquardResolver, slingshot_resolver_default}; use jacquard_oauth::authstore::ClientAuthStore; use jacquard_oauth::client::OAuthSession; use jacquard_oauth::dpop::DpopExt; @@ -66,7 +69,10 @@ use serde::Serialize; #[cfg(feature = "api")] use std::marker::Send; use std::option::Option; +use std::sync::Arc; pub use token::FileAuthStore; +use tokio::sync::RwLock; +use url::Url; /// Identifies the active authentication mode for an agent/session. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -144,6 +150,248 @@ impl Default for BasicClient { Self::unauthenticated() } } +pub struct UnauthenticatedSession { + resolver: Arc, + endpoint: Arc>>, + options: Arc>>, +} + +impl Default for UnauthenticatedSession { + fn default() -> Self { + Self::new_public() + } +} + +impl UnauthenticatedSession { + pub fn new_public() -> Self { + let resolver = Arc::new(JacquardResolver::default()); + let endpoint = Arc::new(RwLock::new(None)); + let options = Arc::new(RwLock::new(CallOptions::default())); + Self { + resolver, + endpoint, + options, + } + } + + pub fn new_slingshot() -> Self { + let resolver = Arc::new(slingshot_resolver_default()); + let endpoint = Arc::new(RwLock::new(None)); + let options = Arc::new(RwLock::new(CallOptions::default())); + Self { + resolver, + endpoint, + options, + } + } +} + +impl HttpClient for UnauthenticatedSession { + type Error = T::Error; + + #[cfg(not(target_arch = "wasm32"))] + fn send_http( + &self, + request: http::Request>, + ) -> impl Future>, T::Error>> + Send { + self.resolver.send_http(request) + } + + #[cfg(target_arch = "wasm32")] + fn send_http( + &self, + request: http::Request>, + ) -> impl Future>, T::Error>> { + self.resolver.send_http(request) + } +} + +impl XrpcClient for UnauthenticatedSession +where + T: Sync + Send, +{ + #[doc = " Get the base URI for the client."] + fn base_uri(&self) -> impl Future + Send { + async move { + self.endpoint.read().await.clone().unwrap_or( + Url::parse("https://public.bsky.app").expect("public appview should be valid url"), + ) + } + } + + #[doc = " Send an XRPC request and parse the response"] + #[cfg(not(target_arch = "wasm32"))] + fn send(&self, request: R) -> impl Future>> + Send + where + R: XrpcRequest + Send + Sync, + ::Response: Send + Sync, + Self: Sync, + { + async move { + let opts = self.options.read().await.clone(); + self.send_with_opts(request, opts).await + } + } + + #[doc = " Send an XRPC request and parse the response"] + #[cfg(not(target_arch = "wasm32"))] + fn send_with_opts( + &self, + request: R, + opts: CallOptions<'_>, + ) -> impl Future>> + Send + where + R: XrpcRequest + Send + Sync, + ::Response: Send + Sync, + Self: Sync, + { + async move { + let base_uri = self.base_uri().await; + self.resolver + .xrpc(base_uri.clone()) + .with_options(opts.clone()) + .send(&request) + .await + } + } + + #[doc = " Send an XRPC request and parse the response"] + #[cfg(target_arch = "wasm32")] + fn send(&self, request: R) -> impl Future>> + where + R: XrpcRequest + Send + Sync, + ::Response: Send + Sync, + { + async move { + let opts = self.options.read().await.clone(); + self.send_with_opts(request, opts).await + } + } + + #[doc = " Send an XRPC request and parse the response"] + #[cfg(target_arch = "wasm32")] + fn send_with_opts( + &self, + request: R, + opts: CallOptions<'_>, + ) -> impl Future>> + where + R: XrpcRequest + Send + Sync, + ::Response: Send + Sync, + { + async move { + let base_uri = self.base_uri().await; + self.resolver + .xrpc(base_uri.clone()) + .with_options(opts.clone()) + .send(&request) + .await + } + } + + #[doc = " Set the base URI for the client."] + fn set_base_uri(&self, url: Url) -> impl Future + Send { + async move { + let mut guard = self.endpoint.write().await; + *guard = Some(url); + } + } + + #[doc = " Get the call options for the client."] + fn opts(&self) -> impl Future> + Send { + async move { self.options.read().await.clone() } + } + + #[doc = " Set the call options for the client."] + fn set_opts(&self, opts: CallOptions<'_>) -> impl Future + Send { + async move { + *self.options.write().await = opts.into_static(); + } + } +} + +impl AgentSession for UnauthenticatedSession +where + T: Sync + Send, +{ + fn session_kind(&self) -> AgentKind { + AgentKind::AppPassword + } + + fn session_info( + &self, + ) -> impl Future, Option>)>> { + async { None } // no session + } + + fn endpoint(&self) -> impl Future { + async { self.base_uri().await } + } + + #[doc = " Override per-session call options."] + fn set_options<'a>(&'a self, opts: CallOptions<'a>) -> impl Future + Send { + async move { + *self.options.write().await = opts.into_static(); + } + } + + #[doc = " Refresh the session and return a fresh AuthorizationToken."] + fn refresh(&self) -> impl Future>> + Send { + async { + Err(ClientError::auth( + jacquard_common::error::AuthError::NotAuthenticated, + )) + } + } +} + +impl IdentityResolver for UnauthenticatedSession { + #[doc = " Access options for validation decisions in default methods"] + fn options(&self) -> &ResolverOptions { + self.resolver.options() + } + + #[doc = " Resolve handle"] + #[cfg(not(target_arch = "wasm32"))] + fn resolve_handle( + &self, + handle: &Handle<'_>, + ) -> impl Future, IdentityError>> + Send + where + Self: Sync, + { + self.resolver.resolve_handle(handle) + } + + #[doc = " Resolve DID document"] + #[cfg(not(target_arch = "wasm32"))] + fn resolve_did_doc( + &self, + did: &Did<'_>, + ) -> impl Future> + Send + where + Self: Sync, + { + self.resolver.resolve_did_doc(did) + } + #[doc = " Resolve handle"] + #[cfg(target_arch = "wasm32")] + fn resolve_handle( + &self, + handle: &Handle<'_>, + ) -> impl Future, IdentityError>> { + self.resolver.resolve_handle(handle) + } + + #[doc = " Resolve DID document"] + #[cfg(target_arch = "wasm32")] + fn resolve_did_doc( + &self, + did: &Did<'_>, + ) -> impl Future> { + self.resolver.resolve_did_doc(did) + } +} /// MemoryCredentialSession: credential session with in memory store and identity resolver pub type MemoryCredentialSession = CredentialSession< -- 2.51.2