How iloader Handles Device Selection and Pairing with iOS Devices: A Technical Deep Dive

iloader implements device selection and pairing through a Tauri-based architecture where React frontend components orchestrate Rust backend commands that interface directly with Apple's MobileDevice APIs, automatically detecting stored pairings or guiding users through manual selection and pairing file placement.

nab138/iloader is a desktop application that simplifies iOS device management by wrapping low-level Apple framework interactions in a modern TypeScript/React interface. The application handles the complete lifecycle of device discovery, selection, and secure pairing through a coordinated command flow between the frontend UI and the Rust backend, supporting both USB and network connections.

Device Discovery and Automatic Pairing Detection

The device selection process begins immediately when the application mounts the main device interface component.

Scanning for Available iOS Devices

When the <Device> component initializes, it invokes the list_devices command to query the backend for connected iOS hardware. According to the source code in src/Device.tsx at line 98, this command returns an array of DeviceInfo objects containing essential metadata:

  • Device name and unique identifier (UUID)
  • Connection type (USB or network)
  • iOS version information
// From src/Device.tsx
const devices = await invoke<DeviceInfo[]>("list_devices");

Automatic Selection of Pre-Paired Devices

Before presenting the device list to the user, iloader checks for existing trust relationships. At lines 23-27 in src/Device.tsx, the application queries each discovered device using the has_stored_rppairing command. If any device already maintains a stored pairing file from a previous session, iloader automatically selects the first qualified device via selectDevice(devicesWithPairing[0]), bypassing manual selection for returning users.

Manual Device Selection and the Pairing Workflow

When automatic selection does not occur—or when users explicitly choose a different device—iloader initiates a structured pairing handshake.

Initiating Selection via the UI

Each rendered device card functions as a clickable button (lines 8-12 in src/Device.tsx). Clicking a device triggers the selectDevice callback, which performs three critical operations:

  1. State Transmission: Sends the selected device to the backend via the set_selected_device command (lines 66-75)
  2. Visual Feedback: Displays a modal indicating "pairing in progress" while the handshake occurs (lines 70-84)
  3. Result Handling: Processes success, cancellation, or error states with appropriate UI updates and toast notifications
// Inside src/Device.tsx
const selectDevice = useCallback((device: DeviceInfo | null) => {
  invoke("set_selected_device", { device })
    .then(() => setSelectedDevice(device))
    .catch(e => toast.error(err(t("device.failed_select"), e)));
}, [setSelectedDevice, err, t]);

Backend Pairing Handshake

While the modal displays, the Rust backend (implemented in src-tauri/src/lib.rs) executes the low-level pairing protocol using Apple's MobileDevice framework. The backend handles certificate exchanges, trust dialog prompts on the iOS device, and secure session establishment. The frontend remains responsive during this process, updating the modal status based on command resolution.

Managing Pairing Files and Advanced Operations

Beyond initial device connection, iloader provides granular control over pairing file distribution and management through dedicated interface pages.

Placing Pairing Files for Specific Applications

The Pairing page (src/pages/Pairing.tsx) lists applications installed on the iOS device that support external pairing file injection. At line 31, the component calls installed_pairing_apps to retrieve eligible targets, including each app's bundle ID and file system path.

When a user clicks Place, the application invokes place_pairing_cmd (lines 51-55) to transfer the pairing file onto the device:

// Inside src/pages/Pairing.tsx
const pair = useCallback(async (app: PairingAppInfo) => {
  const promise = invoke<void>("place_pairing_cmd", {
    bundleId: app.bundleId,
    path: app.path,
  });
  toast.promise(promise, {
    loading: t("pairing.placing_pairing_file"),
    success: t("pairing.pairing_file_placed_success"),
    error: e => err(t("pairing.failed_place_pairing"), e),
  });
}, [t, err]);

Bulk and Advanced Pairing Management

The interface supports bulk operations through a "Place All" button that iterates through all discovered pairing-capable apps, sequentially executing place_pairing_cmd for each. Additionally, the Settings page (src/pages/Settings.tsx) provides administrative controls:

  • Delete Stored Pairing: Clears the persistent pairing record via delete_stored_rppairing (lines 78-89) and resets the selected device state
  • Export Pairing File: Advanced users can extract pairing data using export_pairing_cmd (lines 43-48) for backup or transfer purposes
// From src/pages/Settings.tsx
await invoke("delete_stored_rppairing");
await invoke("set_selected_device");
setSelectedDevice(null);

All commands utilize toast.promise wrappers to provide immediate visual feedback on operation status, ensuring users understand when background processes succeed or fail.

Backend Architecture and MobileDevice Integration

The Rust backend serves as the abstraction layer between the TypeScript frontend and Apple's proprietary MobileDevice framework. Each frontend command maps to native Rust implementations in src-tauri/src/lib.rs that handle:

  • USB and network socket management with iOS devices
  • Certificate parsing and validation for pairing handshakes
  • File system operations for placing and extracting pairing records
  • Secure communication channel establishment

This architecture allows iloader to present a web-based user experience while performing operations that require native platform privileges and direct hardware access.

Summary

  • iloader uses a Tauri stack (React frontend, Rust backend) to bridge web technologies with native iOS device management.
  • Device discovery occurs via the list_devices command in src/Device.tsx, returning comprehensive device metadata.
  • Automatic pairing detection checks for stored pairing files using has_stored_rppairing, enabling zero-click reconnection for trusted devices.
  • Manual selection triggers the set_selected_device command, which initiates a backend handshake while displaying progress modals.
  • Pairing file management happens through src/pages/Pairing.tsx, supporting individual and bulk placement via place_pairing_cmd.
  • Advanced controls in Settings allow deletion (delete_stored_rppairing) and export (export_pairing_cmd) of pairing records.

Frequently Asked Questions

How does iloader automatically detect previously paired iOS devices?

iloader queries each discovered device using the has_stored_rppairing command (found at lines 23-27 in src/Device.tsx). This checks the backend's storage for existing pairing records (rppairing files) associated with each device's UUID. If the command returns true for any device, iloader immediately calls selectDevice() with the first matching device, establishing the connection without requiring user interaction.

What happens when I click a device card in the iloader interface?

Clicking a device card invokes the selectDevice callback, which sends the device object to the Rust backend via set_selected_device (lines 66-75 in src/Device.tsx). The UI simultaneously displays a pairing progress modal while the backend performs the MobileDevice handshake. Upon resolution, the modal closes and the UI updates to show the selected device state, or displays an error toast if the pairing fails or is cancelled.

Can I place pairing files for multiple apps simultaneously?

Yes. While the place_pairing_cmd command handles single app placement (lines 51-55 in src/pages/Pairing.tsx), iloader provides a bulk placement button that iterates through all apps returned by installed_pairing_apps. The interface executes the command sequentially for each discovered pairing-capable application, providing individual toast notifications for each operation's success or failure.

Where does iloader store pairing information, and how can I remove it?

Pairing information is stored on the host machine via the Rust backend's data directory, accessible through the delete_stored_rppairing command. In src/pages/Settings.tsx (lines 78-89), the Settings page provides a "Delete stored rppairing" button that invokes this command followed by set_selected_device with a null parameter, effectively clearing the persistent record and resetting the UI state to unpaired.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →