use crate::envelope::{IpcEnvelope, Origin};
use crate::error::ProtocolError;
use crate::message::IpcMessage;
use crate::service::{ServiceOutcome, VaultOp, VaultOpResult};
use crate::token::load_ipc_token;
use crate::Result;
use std::path::PathBuf;
#[cfg(windows)]
use tracing::debug;
pub struct IpcClient {
socket_path: PathBuf,
auth_token: String,
client_token: Option<String>,
origin: Option<Origin>,
capability: Option<String>,
}
impl IpcClient {
pub fn new(socket_path: PathBuf) -> Result<Self> {
let auth_token = load_ipc_token()?;
Ok(Self::new_with_token(socket_path, auth_token))
}
pub fn new_with_token(socket_path: PathBuf, auth_token: String) -> Self {
Self {
socket_path,
auth_token,
client_token: None,
origin: None,
capability: None,
}
}
pub fn new_for_cli(socket_path: PathBuf, client_token: Option<String>) -> Result<Self> {
let auth_token = load_ipc_token()?;
Ok(Self {
socket_path,
auth_token,
client_token,
origin: Some(Origin::Cli),
capability: None,
})
}
pub fn with_context(mut self, client_token: Option<String>, origin: Option<Origin>) -> Self {
self.client_token = client_token;
self.origin = origin;
self
}
pub fn with_capability(mut self, capability: Option<String>) -> Self {
self.capability = capability;
self
}
pub fn new_for_native_host(socket_path: PathBuf) -> Result<Self> {
let auth_token = load_ipc_token()?;
Ok(Self {
socket_path,
auth_token,
client_token: None,
origin: Some(Origin::NativeHost),
capability: crate::token::load_native_host_capability(),
})
}
pub async fn send(&self, msg: IpcMessage) -> Result<IpcMessage> {
let envelope = IpcEnvelope {
token: self.auth_token.clone(),
client_token: self.client_token.clone(),
origin: self.origin,
capability: self.capability.clone(),
message: msg,
};
let msg_bytes = serde_json::to_vec(&envelope)
.map_err(|e| ProtocolError::Ipc(format!("Failed to serialize message: {}", e)))?;
#[cfg(unix)]
let transport_conn = {
crate::transport::unix::UnixSocketConnection::connect(self.socket_path.clone())
.await
.map_err(|e| ProtocolError::Ipc(format!("Failed to connect to daemon: {}", e)))?
};
#[cfg(windows)]
let transport_conn = {
let stored = self.socket_path.to_string_lossy().to_string();
let pipe_name = if stored.starts_with(r"\\.\pipe\") {
stored
} else {
crate::windows_frame::windows_named_pipe_path()
};
debug!("Connecting to named pipe: {}", pipe_name);
crate::transport::windows::connect_named_pipe(&pipe_name, 3000)
.await
.map_err(|e| {
ProtocolError::Ipc(format!("Failed to connect to named pipe: {}", e))
})?
};
let conn = crate::connection::TransportConnection::from(transport_conn);
let mut ipc = crate::connection::IpcConnection::connect_client(conn, &self.auth_token)
.await
.map_err(|e| ProtocolError::Ipc(format!("Session negotiation failed: {}", e)))?;
ipc.send_frame(&msg_bytes)
.await
.map_err(|e| ProtocolError::Ipc(format!("Failed to write message: {}", e)))?;
let buffer = ipc
.recv_frame()
.await
.map_err(|e| ProtocolError::Ipc(format!("Failed to read response: {}", e)))?;
serde_json::from_slice::<IpcMessage>(&buffer)
.map_err(|e| ProtocolError::Ipc(format!("Failed to parse response: {}", e)))
}
pub async fn call_service(&self, op: VaultOp) -> Result<VaultOpResult> {
let response = self.send(IpcMessage::ServiceCall { op }).await?;
match response {
IpcMessage::ServiceResult { outcome } => match outcome {
ServiceOutcome::Ok { result } => Ok(result),
ServiceOutcome::Err { error } => {
Err(ProtocolError::Service(error.code, error.message))
}
},
other => Err(ProtocolError::Ipc(format!(
"unexpected daemon response to service call: {}",
message_kind(&other)
))),
}
}
}
fn message_kind(msg: &IpcMessage) -> &'static str {
match msg {
IpcMessage::GetCredentialResponse { .. }
| IpcMessage::GetExternalSecretResponse { .. }
| IpcMessage::ListDomainCredentialsResponse { .. }
| IpcMessage::GetTotpCodeResponse { .. }
| IpcMessage::SaveCredentialResponse { .. }
| IpcMessage::SaveSecretResponse { .. }
| IpcMessage::DeleteSecretResponse { .. } => "browser-surface response",
IpcMessage::UnlockVaultResponse { .. } => "unlock response",
IpcMessage::VaultStatusResponse { .. } => "vault status",
IpcMessage::SyncNowResponse { .. } => "sync-now response",
IpcMessage::SyncStatusResponse { .. } => "sync status",
_ => "other",
}
}