295 lines
9.4 KiB
Rust

mod backend;
mod config;
mod error;
mod ios_device;
mod mdm;
mod platform;
mod server;
mod sidecar;
use backend::api::{
MagicApiClient, MagicDeviceInfo, MdmStatusByUdidData, MdmStatusData, RedeemPairingResponse,
RegisterDeviceResponse, ReleaseResponse, UserProfile,
};
use config::{AppConfig, DesktopProtectionState, MagicSession};
use error::AppResult;
use std::process::Command;
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_os::init())
.invoke_handler(tauri::generate_handler![
platform::get_platform,
server::local_http::start_local_profile_server,
server::local_http::stop_local_profile_server,
sidecar::supervise_magic::run_supervise_magic,
ios_device::detect_iphone_state,
ios_device::get_supervise_status,
ios_device::get_installed_profiles,
ios_device::get_installed_apps,
ios_device::install_profile,
ios_device::download_and_patch_enrollment_profile,
mdm::mdm_ping,
mdm::mdm_push,
mdm::mdm_install_app,
mdm::mdm_set_supervised_mode,
mdm::mdm_take_management,
mdm::mdm_install_lock_profile,
redeem_pairing_code,
register_device,
get_stored_session,
get_magic_devices,
get_magic_status,
request_release,
cancel_release,
start_cooldown,
cancel_cooldown,
logout_magic,
download_profile,
activate_protection,
fetch_me,
get_mdm_status,
get_mdm_status_by_udid,
link_mdm_device,
get_desktop_protection_status,
set_desktop_protection_status,
get_hostname,
get_hardware_id,
get_device_id,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
fn hostname() -> AppResult<String> {
let output = Command::new("hostname")
.output()
.map_err(|e| error::AppError::new(format!("Failed to get hostname: {}", e)))?;
let mut hostname = String::from_utf8_lossy(&output.stdout).to_string();
hostname = hostname.trim().to_string();
if hostname.is_empty() {
hostname = "ReBreak Magic Device".to_string();
}
Ok(hostname)
}
fn get_or_init_hardware_id(config: &mut AppConfig) -> AppResult<String> {
if let Some(id) = config.hardware_id.clone() {
return Ok(id);
}
let id = platform::get_hardware_id()?;
config.set_hardware_id(id.clone())?;
Ok(id)
}
#[tauri::command]
fn get_hardware_id() -> AppResult<String> {
let mut config = AppConfig::load();
get_or_init_hardware_id(&mut config)
}
#[tauri::command]
fn get_device_id() -> AppResult<Option<String>> {
Ok(AppConfig::load().device_id)
}
fn require_session() -> AppResult<MagicSession> {
AppConfig::load_magic_session()?.ok_or_else(|| {
error::AppError::new("Keine Magic-Session gefunden. Bitte neu paaren.")
})
}
#[tauri::command]
async fn redeem_pairing_code(code: String, label: Option<String>) -> AppResult<RedeemPairingResponse> {
let trimmed = code.trim().to_string();
if !trimmed.chars().all(|c| c.is_ascii_digit()) || trimmed.len() != 6 {
return Err(error::AppError::new("Der Code besteht aus 6 Ziffern.".to_string()));
}
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
let response = client
.redeem_pairing_code(&trimmed, label.as_deref())
.await?;
let label = label.unwrap_or_else(|| hostname().unwrap_or_else(|_| "ReBreak Magic".to_string()));
let session = MagicSession::new(response.token.clone(), response.session_id.clone(), Some(label));
AppConfig::save_magic_session(&session)?;
Ok(response)
}
#[tauri::command]
async fn register_device(
model: Option<String>,
os_version: Option<String>,
) -> AppResult<RegisterDeviceResponse> {
let session = require_session()?;
let mut config = AppConfig::load();
let client = MagicApiClient::new(&config);
let hostname = hostname()?;
// Hardware-ID ist der stabile Identifikator; deviceId wird vom Backend zurückgeliefert.
let hardware_id = get_or_init_hardware_id(&mut config)?;
let response = client
.register_device(
&session.access_token,
config.device_id.as_deref(),
Some(&hardware_id),
&hostname,
model.as_deref(),
os_version.as_deref(),
)
.await?;
config.set_device_id(response.device_id.clone())?;
Ok(response)
}
#[tauri::command]
async fn get_stored_session() -> AppResult<Option<MagicSession>> {
AppConfig::load_magic_session()
}
#[tauri::command]
async fn get_magic_devices() -> AppResult<Vec<MagicDeviceInfo>> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
client.list_devices(&session.access_token).await
}
#[tauri::command]
async fn get_magic_status(dns_token: String) -> AppResult<bool> {
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
client.get_status(&dns_token).await
}
#[tauri::command]
async fn request_release(device_id: String) -> AppResult<ReleaseResponse> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
client.request_release(&session.access_token, &device_id).await
}
#[tauri::command]
async fn cancel_release(device_id: String) -> AppResult<()> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
client.cancel_release(&session.access_token, &device_id).await
}
#[tauri::command]
async fn start_cooldown(device_id: String, duration_minutes: u32) -> AppResult<serde_json::Value> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
let result = client.start_cooldown(&session.access_token, &device_id, duration_minutes).await?;
Ok(serde_json::json!({ "cooldownUntil": result.cooldown_until }))
}
#[tauri::command]
async fn cancel_cooldown(device_id: String) -> AppResult<()> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
client.cancel_cooldown(&session.access_token, &device_id).await
}
#[tauri::command]
fn logout_magic() -> AppResult<()> {
AppConfig::clear_magic_session()
}
#[tauri::command]
fn activate_protection(profile_path: String) -> AppResult<()> {
platform::activate_protection(&profile_path)
}
#[tauri::command]
async fn fetch_me() -> AppResult<UserProfile> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
client.fetch_me(&session.access_token).await
}
#[tauri::command]
async fn get_mdm_status(device_id: String) -> AppResult<MdmStatusData> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
client.get_mdm_status(&session.access_token, &device_id).await
}
#[tauri::command]
async fn link_mdm_device(device_id: String, mdm_id: String) -> AppResult<()> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
client.link_mdm_device(&session.access_token, &device_id, &mdm_id).await
}
#[tauri::command]
async fn get_mdm_status_by_udid(udid: String) -> AppResult<MdmStatusByUdidData> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
client.get_mdm_status_by_udid(&session.access_token, &udid).await
}
#[tauri::command]
async fn download_profile(profile_url: String) -> AppResult<String> {
let session = require_session()?;
let config = AppConfig::load();
let client = MagicApiClient::new(&config);
// Try to extract dns_token from profileUrl, fallback to direct token lookup
let dns_token = extract_dns_token(&profile_url)
.or_else(|| Some(session.access_token.clone()))
.ok_or_else(|| error::AppError::new("Kein DNS-Token verfügbar".to_string()))?;
let bytes = client.download_profile(&dns_token).await?;
let config_dir = AppConfig::config_dir()?;
std::fs::create_dir_all(&config_dir)?;
let profile_path = config_dir.join("rebreak-iphone-protect.mobileconfig");
std::fs::write(&profile_path, bytes)?;
Ok(profile_path.to_string_lossy().to_string())
}
fn extract_dns_token(profile_url: &str) -> Option<String> {
profile_url.split("token=").nth(1).map(|s| s.split('&').next().unwrap_or(s).to_string())
}
#[tauri::command]
fn get_desktop_protection_status() -> AppResult<Option<DesktopProtectionState>> {
AppConfig::load_desktop_protection()
}
#[tauri::command]
fn get_hostname() -> AppResult<String> {
hostname()
}
#[tauri::command]
fn set_desktop_protection_status(active: bool, platform: String) -> AppResult<()> {
if active {
let state = DesktopProtectionState {
active,
platform,
activated_at: chrono::Utc::now(),
};
AppConfig::save_desktop_protection(&state)
} else {
AppConfig::clear_desktop_protection()
}
}