use super::{Sandboxes, SpacesdClient, run, runtime};
use crate::{CuaError, Result};
use cua_daemon::{Runtime, RuntimeConfig, client::DaemonAddress, client::DaemonClient};
use std::{path::PathBuf, sync::Arc};
#[derive(Debug, Clone, Default, PartialEq, Eq, uniffi::Record)]
pub struct CuaConfig {
#[uniffi(default = None)]
pub state_dir: Option<String>,
#[uniffi(default = None)]
pub env_probe_timeout_ms: Option<u32>,
#[uniffi(default = None)]
pub spaces_home: Option<String>,
#[uniffi(default = None)]
pub teleport_home: Option<String>,
#[uniffi(default = false)]
pub account_from_session: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
pub enum CuaMode {
Embedded,
Daemon,
}
#[derive(Debug, Clone, PartialEq, Eq, uniffi::Record)]
pub struct CuaInfo {
pub sdk_version: String,
pub mode: CuaMode,
pub daemon_version: Option<String>,
pub daemon_pid: Option<u32>,
pub socket_path: Option<String>,
pub loopback_url: Option<String>,
pub features: Vec<String>,
}
#[derive(Clone)]
pub(crate) enum Backend {
Embedded(Runtime),
Daemon(DaemonClient),
}
pub(crate) fn features() -> Vec<String> {
let mut f = vec!["env".to_string(), "sandboxes".into(), "daemon".into()];
if cfg!(feature = "local") {
f.push("local".into());
}
if cfg!(feature = "spaces") {
f.push("spaces".into());
}
if cfg!(feature = "host") {
f.push("host".into());
}
if cfg!(feature = "media") {
f.push("media".into());
}
f
}
#[derive(uniffi::Object)]
pub struct Cua {
pub(crate) backend: Backend,
}
impl Cua {
pub fn from_runtime(runtime: Runtime) -> Arc<Self> {
Arc::new(Self {
backend: Backend::Embedded(runtime),
})
}
pub fn embedded_runtime(&self) -> Option<&Runtime> {
match &self.backend {
Backend::Embedded(r) => Some(r),
Backend::Daemon(_) => None,
}
}
}
#[cfg(all(feature = "spaces", feature = "host"))]
fn session_relay_account(home: &std::path::Path) -> cua_spaces::RelayAccount {
let url = cua_host::relay_url_for(None, home);
let session = Arc::new(cua_auth::Session::from_env());
let tokens: Arc<dyn cua_host::AccountTokens> =
Arc::new(super::host::SessionTokens(session.clone()));
let account = cua_spaces::RelayAccount::new(url.clone(), tokens.clone());
match cua_host::DeviceAuth::new(
&url,
tokens,
Arc::new(session.store().clone()),
cua_host::device_name(),
) {
Ok(device) => account.with_device(Arc::new(
device.with_pending_file(cua_host::device_pending_file(home)),
)),
Err(_) => account,
}
}
#[uniffi::export]
impl Cua {
#[uniffi::constructor]
pub fn embedded(config: CuaConfig) -> Result<Arc<Self>> {
let rc = RuntimeConfig {
state_dir: config.state_dir.clone().map(PathBuf::from),
local: None,
vmm: Some(Arc::new(cua_daemon::local::VmmLocal::default())),
env_probe_timeout: config.env_probe_timeout_ms.map(super::millis),
spaces_home: config.spaces_home.clone().map(PathBuf::from),
teleport_home: config.teleport_home.clone().map(PathBuf::from),
providers: None,
..Default::default()
};
let rt = {
let _guard = runtime().enter();
Runtime::new(rc)?
};
#[cfg(all(feature = "spaces", feature = "host"))]
if config.account_from_session && std::env::var_os("CUA_DAEMON_NO_RELAY").is_none() {
let home = config
.spaces_home
.clone()
.map(PathBuf::from)
.unwrap_or_else(cua_daemon::cua_home);
rt.spaces().set_relay(Some(session_relay_account(&home)));
}
Ok(Arc::new(Self {
backend: Backend::Embedded(rt),
}))
}
#[uniffi::constructor]
pub fn auto(config: CuaConfig) -> Result<Arc<Self>> {
match cua_daemon::client::wait_for_starting(std::time::Duration::from_secs(30))? {
Some(addr) => {
let client = {
let _guard = runtime().enter();
DaemonClient::new(addr)?
};
if cua_daemon::identity::bundled_cua().is_some() {
check_identity(&client)?;
}
Ok(Arc::new(Self {
backend: Backend::Daemon(client),
}))
}
None => Self::embedded(config),
}
}
#[uniffi::constructor]
pub fn connect(address: Option<String>, token: Option<String>) -> Result<Arc<Self>> {
let addr = DaemonAddress::resolve(address.as_deref(), token)?;
let client = {
let _guard = runtime().enter();
DaemonClient::new(addr)?
};
Ok(Arc::new(Self {
backend: Backend::Daemon(client),
}))
}
pub fn mode(&self) -> CuaMode {
match self.backend {
Backend::Embedded(_) => CuaMode::Embedded,
Backend::Daemon(_) => CuaMode::Daemon,
}
}
pub async fn info(&self) -> Result<CuaInfo> {
let backend = self.backend.clone();
run(async move {
let mut info = CuaInfo {
sdk_version: crate::cua_sdk_version(),
mode: CuaMode::Embedded,
daemon_version: None,
daemon_pid: None,
socket_path: None,
loopback_url: None,
features: features(),
};
if let Backend::Daemon(d) = backend {
let i = d.info().await?;
let opt = |s: String| (!s.is_empty()).then_some(s);
info.mode = CuaMode::Daemon;
info.daemon_version = Some(i.version);
info.daemon_pid = Some(i.pid);
info.socket_path = opt(i.socket_path);
info.loopback_url = opt(i.loopback_url);
}
Ok(info)
})
.await
}
pub fn sandboxes(&self) -> Arc<Sandboxes> {
Arc::new(Sandboxes {
backend: self.backend.clone(),
})
}
pub async fn spacesd(&self, url: String, token: Option<String>) -> Result<Arc<SpacesdClient>> {
let backend = self.backend.clone();
run(async move {
let client = match backend {
Backend::Embedded(rt) => rt.env_url(&url, token).await?,
Backend::Daemon(_) => {
let mut o = cua_spacesd_client::ConnectOptions::parse(&url)?;
o.token = token;
cua_spacesd_client::SpacesdClient::connect(o).await?
}
};
Ok(Arc::new(SpacesdClient::new(client, vec![])))
})
.await
}
pub async fn shutdown_daemon(&self) -> Result<()> {
let backend = self.backend.clone();
run(async move {
match backend {
Backend::Daemon(d) => Ok(d.shutdown().await?),
Backend::Embedded(_) => Err(CuaError::Unsupported(
"shutdown_daemon needs a daemon connection".into(),
)),
}
})
.await
}
}
#[cfg(feature = "local")]
#[uniffi::export]
impl Cua {
pub fn local(&self) -> Arc<super::Local> {
Arc::new(super::Local {
backend: self.backend.clone(),
})
}
}
#[cfg(feature = "spaces")]
#[uniffi::export]
impl Cua {
pub fn spaces(&self) -> Arc<super::Spaces> {
Arc::new(super::Spaces::new(&self.backend))
}
}
fn check_identity(client: &DaemonClient) -> Result<()> {
let client = client.clone();
let info = std::thread::spawn(move || {
runtime().block_on(async move {
tokio::time::timeout(std::time::Duration::from_secs(5), client.info()).await
})
})
.join()
.map_err(|_| CuaError::Internal("the daemon identity check panicked".into()))?;
match info {
Ok(Ok(info)) => Ok(cua_daemon::identity::check(&info)?),
_ => Ok(()),
}
}