use std::sync::Arc;
use affinidi_tdk::common::TDKSharedState;
use affinidi_tdk::didcomm::Message;
use affinidi_tdk::messaging::ATM;
use affinidi_tdk::messaging::config::ATMConfig;
use affinidi_tdk::messaging::profiles::ATMProfile;
use affinidi_tdk::secrets_resolver::SecretsResolver;
use tracing::{debug, info, warn};
use crate::protocols::PROBLEM_REPORT_TYPE;
pub struct DIDCommSession {
atm: Arc<ATM>,
profile: Arc<ATMProfile>,
pub(crate) client_did: String,
pub(crate) vta_did: String,
}
impl DIDCommSession {
pub async fn connect(
client_did: &str,
private_key_multibase: &str,
vta_did: &str,
mediator_did: &str,
) -> Result<Self, Box<dyn std::error::Error>> {
let seed = crate::did_key::decode_private_key_multibase(private_key_multibase)?;
let secrets = crate::did_key::secrets_from_did_key(client_did, &seed)?;
let tdk = TDKSharedState::default().await;
tdk.secrets_resolver.insert(secrets.signing).await;
tdk.secrets_resolver.insert(secrets.key_agreement).await;
let atm_config = ATMConfig::builder().build()?;
let atm = ATM::new(atm_config, Arc::new(tdk)).await?;
let profile = ATMProfile::new(
&atm,
None,
client_did.to_string(),
Some(mediator_did.to_string()),
)
.await?;
let profile = Arc::new(profile);
let atm = Arc::new(atm);
{
use affinidi_tdk::messaging::messages::Folder;
match atm.list_messages(&profile, Folder::Inbox).await {
Ok(messages) if !messages.is_empty() => {
let ids: Vec<String> = messages.iter().map(|m| m.msg_id.clone()).collect();
info!(count = ids.len(), "flushing stale queued messages from inbox");
let delete_req = affinidi_tdk::messaging::messages::DeleteMessageRequest {
message_ids: ids,
};
match atm.delete_messages_direct(&profile, &delete_req).await {
Ok(resp) => {
debug!(
deleted = resp.success.len(),
errors = resp.errors.len(),
"inbox flushed"
);
}
Err(e) => warn!("failed to flush stale messages (non-fatal): {e}"),
}
}
Ok(_) => {} Err(e) => warn!("could not list inbox (non-fatal): {e}"),
}
}
atm.profile_enable_websocket(&profile).await?;
debug!("DIDComm session connected via mediator {mediator_did} (WebSocket mode)");
Ok(Self {
atm,
profile,
client_did: client_did.to_string(),
vta_did: vta_did.to_string(),
})
}
pub async fn send_and_wait<T: serde::de::DeserializeOwned>(
&self,
msg_type: &str,
body: serde_json::Value,
expected_result_type: &str,
timeout_secs: u64,
) -> Result<T, Box<dyn std::error::Error>> {
let msg_id = uuid::Uuid::new_v4().to_string();
let msg = Message::build(msg_id.clone(), msg_type.to_string(), body)
.from(self.client_did.clone())
.to(self.vta_did.clone())
.finalize();
let (packed, _) = self
.atm
.pack_encrypted(
&msg,
&self.vta_did,
Some(&self.client_did),
Some(&self.client_did),
)
.await
.map_err(|e| format!("failed to pack message: {e}"))?;
debug!(msg_type, msg_id, "sending via DIDComm");
self.atm
.send_message(&self.profile, &packed, &msg_id, false, false)
.await
.map_err(|e| format!("failed to send message: {e}"))?;
let timeout = std::time::Duration::from_secs(timeout_secs);
let wait_duration = std::time::Duration::from_secs(5);
let deadline = tokio::time::Instant::now() + timeout;
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
return Err("timeout waiting for DIDComm response".into());
}
let wait = wait_duration.min(remaining);
let next = self
.atm
.message_pickup()
.live_stream_next(&self.profile, Some(wait), true)
.await
.map_err(|e| format!("message pickup error: {e}"))?;
let (response_msg, _meta) = match next {
Some(pair) => pair,
None => continue, };
let response_thid = response_msg.thid.as_deref().unwrap_or("");
if response_thid != msg_id {
debug!(
response_thid,
expected = msg_id,
response_type = %response_msg.typ,
"received message with non-matching thread ID — skipping"
);
continue;
}
debug!(response_type = %response_msg.typ, "received DIDComm response");
if response_msg.typ == PROBLEM_REPORT_TYPE
|| response_msg.typ.contains("problem-report")
{
let code = response_msg
.body
.get("code")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let comment = response_msg
.body
.get("comment")
.and_then(|v| v.as_str())
.unwrap_or("");
return Err(format!("{code}: {comment}").into());
}
if response_msg.typ != expected_result_type {
return Err(format!(
"unexpected response type: expected {expected_result_type}, got {}",
response_msg.typ
)
.into());
}
return serde_json::from_value(response_msg.body)
.map_err(|e| format!("failed to deserialize DIDComm response: {e}").into());
}
}
pub async fn shutdown(&self) {
self.atm.graceful_shutdown().await;
}
}