Include recent Magic app work: Tauri native shell, iOS device detection via supervise-magic sidecar, MDM client, local HTTP server, new pages (detect, enroll, supervise, sideload, pair, preflight, configure, done), and updated device section/status UI.
143 lines
4.5 KiB
Rust
143 lines
4.5 KiB
Rust
use crate::config::AppConfig;
|
|
use crate::error::{AppError, AppResult};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
const HTTP_TIMEOUT_SECONDS: u64 = 30;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MdmPushStatus {
|
|
pub udid: String,
|
|
pub push_result: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MdmEnqueueResult {
|
|
pub command_uuid: String,
|
|
pub response_body: String,
|
|
}
|
|
|
|
pub struct MdmClient {
|
|
client: reqwest::Client,
|
|
server: String,
|
|
auth_header: String,
|
|
}
|
|
|
|
impl MdmClient {
|
|
pub fn new() -> AppResult<Self> {
|
|
let cfg = AppConfig::load_binder_config()?;
|
|
let creds = format!("{}:{}", cfg.mdm_user, cfg.mdm_api_key);
|
|
use base64::{engine::general_purpose::STANDARD, Engine};
|
|
let auth_header = format!("Basic {}", STANDARD.encode(creds));
|
|
|
|
Ok(Self {
|
|
client: reqwest::Client::builder()
|
|
.timeout(std::time::Duration::from_secs(HTTP_TIMEOUT_SECONDS))
|
|
.build()
|
|
.map_err(|e| AppError::new(format!("reqwest client build: {}", e)))?,
|
|
server: cfg.mdm_server,
|
|
auth_header,
|
|
})
|
|
}
|
|
|
|
fn url(&self, path: &str) -> AppResult<String> {
|
|
let base = self.server.trim_end_matches('/');
|
|
Ok(format!("{}{}", base, path))
|
|
}
|
|
|
|
fn request(&self, method: reqwest::Method, path: &str) -> AppResult<reqwest::RequestBuilder> {
|
|
let url = self.url(path)?;
|
|
Ok(self
|
|
.client
|
|
.request(method, &url)
|
|
.header("Authorization", &self.auth_header))
|
|
}
|
|
|
|
pub async fn ping(&self) -> AppResult<String> {
|
|
let resp = self
|
|
.request(reqwest::Method::GET, "/version")?
|
|
.send()
|
|
.await
|
|
.map_err(|e| AppError::new(format!("MDM ping failed: {}", e)))?;
|
|
|
|
let status = resp.status();
|
|
let body = resp
|
|
.text()
|
|
.await
|
|
.unwrap_or_else(|_| "Unknown error".to_string());
|
|
|
|
if !status.is_success() {
|
|
return Err(AppError::new(format!("MDM ping HTTP {}: {}", status, body)));
|
|
}
|
|
Ok(body)
|
|
}
|
|
|
|
pub async fn push(&self, udid: &str) -> AppResult<MdmPushStatus> {
|
|
let resp = self
|
|
.request(reqwest::Method::GET, &format!("/v1/push/{}", udid))?
|
|
.send()
|
|
.await
|
|
.map_err(|e| AppError::new(format!("MDM push failed: {}", e)))?;
|
|
|
|
let status = resp.status();
|
|
let body = resp
|
|
.text()
|
|
.await
|
|
.unwrap_or_else(|_| "Unknown error".to_string());
|
|
|
|
if !status.is_success() {
|
|
return Err(AppError::new(format!("MDM push HTTP {}: {}", status, body)));
|
|
}
|
|
|
|
// Parse NanoMDM response: { "status": { "<udid>": { "push_result": "..." } } }
|
|
let parsed: serde_json::Value = serde_json::from_str(&body)
|
|
.map_err(|e| AppError::new(format!("MDM push parse error: {} — body: {}", e, body)))?;
|
|
|
|
let push_result = parsed
|
|
.get("status")
|
|
.and_then(|s| s.get(udid))
|
|
.and_then(|d| d.get("push_result"))
|
|
.and_then(|v| v.as_str())
|
|
.ok_or_else(|| AppError::new(format!("MDM push response unerwartet: {}", body)))?
|
|
.to_string();
|
|
|
|
Ok(MdmPushStatus {
|
|
udid: udid.to_string(),
|
|
push_result,
|
|
})
|
|
}
|
|
|
|
pub async fn enqueue(&self, udid: &str, command: serde_json::Value) -> AppResult<MdmEnqueueResult> {
|
|
let command_uuid = uuid::Uuid::new_v4().to_string();
|
|
let envelope = serde_json::json!({
|
|
"CommandUUID": command_uuid,
|
|
"Command": command,
|
|
});
|
|
|
|
// NanoMDM expects plist, but also accepts JSON in newer versions.
|
|
// For maximum compatibility we send JSON here; if it fails, the caller
|
|
// will see the error.
|
|
let resp = self
|
|
.request(reqwest::Method::PUT, &format!("/v1/enqueue/{}?push=1", udid))?
|
|
.header("Content-Type", "application/json")
|
|
.json(&envelope)
|
|
.send()
|
|
.await
|
|
.map_err(|e| AppError::new(format!("MDM enqueue failed: {}", e)))?;
|
|
|
|
let status = resp.status();
|
|
let body = resp
|
|
.text()
|
|
.await
|
|
.unwrap_or_else(|_| "Unknown error".to_string());
|
|
|
|
if !status.is_success() {
|
|
return Err(AppError::new(format!("MDM enqueue HTTP {}: {}", status, body)));
|
|
}
|
|
|
|
Ok(MdmEnqueueResult {
|
|
command_uuid,
|
|
response_body: body,
|
|
})
|
|
}
|
|
}
|