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, report_device_protection_state, 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 { 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 { 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 { let mut config = AppConfig::load(); get_or_init_hardware_id(&mut config) } #[tauri::command] fn get_device_id() -> AppResult> { Ok(AppConfig::load().device_id) } fn require_session() -> AppResult { 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) -> AppResult { 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, os_version: Option, ) -> AppResult { 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> { AppConfig::load_magic_session() } #[tauri::command] async fn get_magic_devices() -> AppResult> { 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 { 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 { 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 { 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 { 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 { 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 report_device_protection_state( device_id: String, platform: String, protection_type: String, active: bool, reason: Option, ) -> AppResult<()> { let session = require_session()?; let config = AppConfig::load(); let client = MagicApiClient::new(&config); client .report_device_protection_state( &session.access_token, &device_id, &platform, &protection_type, active, reason.as_deref(), ) .await } #[tauri::command] async fn get_mdm_status_by_udid(udid: String) -> AppResult { 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 { 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 { profile_url.split("token=").nth(1).map(|s| s.split('&').next().unwrap_or(s).to_string()) } #[tauri::command] fn get_desktop_protection_status() -> AppResult> { AppConfig::load_desktop_protection() } #[tauri::command] fn get_hostname() -> AppResult { 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() } }