use std::path::Path; use cm_domain::WorkspaceId; use tokio::net::UnixStream; use uuid::Uuid; use crate::protocol::{read_frame, write_frame, Request, Response}; use crate::BrokerError; /// Client side of the broker protocol, used by the server process only. pub struct BrokerClient { stream: UnixStream, } impl BrokerClient { pub async fn connect(socket_path: &Path) -> Result { let stream = UnixStream::connect(socket_path) .await .map_err(|e| BrokerError::Io(e.to_string()))?; Ok(BrokerClient { stream }) } async fn round_trip(&mut self, request: Request) -> Result { write_frame(&mut self.stream, &request).await?; let response: Response = read_frame(&mut self.stream).await?; response.into_result() } pub async fn store_secret( &mut self, workspace_id: WorkspaceId, kind: &str, plaintext: &str, ) -> Result { match self .round_trip(Request::StoreSecret { workspace_id: workspace_id.as_uuid(), kind: kind.to_owned(), plaintext: plaintext.to_owned(), }) .await? { Response::SecretStored { secret_id } => Ok(secret_id), other => Err(BrokerError::Io(format!("unexpected response: {other:?}"))), } } pub async fn secret_kind(&mut self, secret_id: Uuid) -> Result { match self.round_trip(Request::SecretKind { secret_id }).await? { Response::SecretKind { kind } => Ok(kind), other => Err(BrokerError::Io(format!("unexpected response: {other:?}"))), } } /// Asks the broker to verify a Slack signature; the signing secret /// never crosses the socket. pub async fn verify_slack_signature( &mut self, secret_id: Uuid, timestamp: &str, body: &str, signature: &str, ) -> Result { match self .round_trip(Request::VerifySlackSignature { secret_id, timestamp: timestamp.to_owned(), body: body.to_owned(), signature: signature.to_owned(), }) .await? { Response::Verified { valid } => Ok(valid), other => Err(BrokerError::Io(format!("unexpected response: {other:?}"))), } } pub async fn invoke_http( &mut self, approval_id: Uuid, secret_id: Uuid, url: &str, body: serde_json::Value, ) -> Result { match self .round_trip(Request::InvokeHttp { approval_id, secret_id, url: url.to_owned(), body, }) .await? { Response::HttpDone { status } => Ok(status), other => Err(BrokerError::Io(format!("unexpected response: {other:?}"))), } } /// Read-only GET with the stored PAT injected as a bearer token. The /// credential never leaves the broker; the caller only sees the response /// status + parsed JSON body. Used by the repo-provider sync path. pub async fn fetch_authorized( &mut self, secret_id: Uuid, url: &str, ) -> Result<(u16, serde_json::Value), BrokerError> { match self .round_trip(Request::FetchAuthorized { secret_id, url: url.to_owned(), }) .await? { Response::HttpJson { status, body } => Ok((status, body)), other => Err(BrokerError::Io(format!("unexpected response: {other:?}"))), } } }