# How iloader's Tauri Backend Communicates with Its React Frontend: IPC Architecture Explained

> Discover how iloader's Tauri backend and React frontend communicate using IPC. Learn about Rust commands, frontend events, and bidirectional data flow for efficient app development.

- Repository: [Nicholas Sharp/iloader](https://github.com/nab138/iloader)
- Tags: architecture
- Published: 2026-09-13

---

**iloader uses Tauri's standard IPC model where the React frontend invokes Rust commands via `invoke()` and listens to backend events via `window.listen()`, while the Rust backend emits events using `window.emit()` for bidirectional communication, supplemented by `tauri-plugin-store` for persistent state management.**

iloader is an open-source iOS sideloading application built with Tauri and React. Understanding how its Rust backend communicates with the React frontend is essential for developers contributing to the codebase or building similar desktop applications. The architecture relies on three core mechanisms: **command invocation** for request-response patterns, **event emission** for server-push updates, and the **store plugin** for persistent configuration data.

## The Three Communication Mechanisms

### Frontend-to-Backend RPC with Invoke

The primary communication path from React to Rust uses Tauri's **invoke** API. The frontend calls `invoke("command_name", payload)` from `@tauri-apps/api/core`, which serializes the payload, sends it across the IPC boundary, and awaits the Rust command's return value.

In [`src/pages/Settings.tsx`](https://github.com/nab138/iloader/blob/main/src/pages/Settings.tsx), the application calls `force_disable_keyring` to override system keyring behavior:

```typescript
import { invoke } from "@tauri-apps/api/core";

await invoke("force_disable_keyring", { force: true });
await invoke("reset_anisette_state");

```

These invocations target specific commands registered in the backend. The `force_disable_keyring` command is implemented in [`src-tauri/src/secure_storage.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/secure_storage.rs) at lines 14-15, while the frontend invocation appears at lines 100-103 in the same Settings file.

### Backend-to-Frontend Event Emission

When the backend needs to push data to the UI without an explicit request—such as during two-factor authentication prompts or logging—it uses **window.emit()** for window-specific events or **app_handle.emit()** for global broadcasts.

In [`src-tauri/src/account.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/account.rs) (lines 71-74), the login flow emits a `"2fa-required"` event when Apple requests a verification code:

```rust
window.emit("2fa-required", params)
      .context("Failed to emit 2fa-required event")?;

```

Similarly, the logging layer in [`src-tauri/src/logging.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/logging.rs) (lines 105-106) forwards log records to the frontend using a global emit:

```rust
self.app_handle.emit("log-record", &record);

```

### Persistent State with tauri-plugin-store

For configuration data that must survive application restarts, iloader uses **tauri-plugin-store**. This plugin exposes a key-value store that both frontend and backend can access, though the React side typically reads values via `invoke` while the Rust side writes them directly.

In [`src/pages/Settings.tsx`](https://github.com/nab138/iloader/blob/main/src/pages/Settings.tsx) (lines 51-66), the frontend retrieves saved accounts and settings from the store:

```typescript
const store = await handle.store("data.json");
const saved = await store.get<{ email: string; password: string }[]>("saved-accounts");

```

## Command Registration in the Rust Backend

Before the frontend can invoke commands, they must be registered in [`src-tauri/src/lib.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/lib.rs) (lines 6-27) using the `tauri::generate_handler!` macro:

```rust
tauri::generate_handler![
    login_new,
    force_disable_keyring,
    reset_anisette_state,
    // ... additional commands
]

```

Each command is a Rust function decorated with `#[tauri::command]`. For example, `login_new` in [`src-tauri/src/account.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/account.rs) (lines 27-40) handles Apple ID authentication and stores the resulting session in a shared `SideloaderMutex` state container.

## Handling Asynchronous Events in React

To receive events from the backend, React components use `window.listen()` from `@tauri-apps/api/window`. This establishes a subscription that persists until the component unmounts or the listener is explicitly unregistered.

For the 2FA flow initiated in [`account.rs`](https://github.com/nab138/iloader/blob/main/account.rs), the frontend registers a listener:

```typescript
import { getCurrent } from "@tauri-apps/api/window";

const window = getCurrent();
const unlisten = await window.listen<string>("2fa-required", (event) => {
  // Display modal and collect verification code
  const code = await show2FADialog(event.payload);
  // Send code back via another invoke call
  await invoke("submit_2fa_code", { code });
});

```

The [`operation.rs`](https://github.com/nab138/iloader/blob/main/operation.rs) file demonstrates additional event emission patterns for long-running operations, emitting progress events that the UI can display to users during certificate generation or app installation.

## Key Files in the Communication Layer

- **[`src-tauri/src/lib.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/lib.rs)** – Command registration and Tauri builder configuration
- **[`src-tauri/src/account.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/account.rs)** – Authentication logic and `"2fa-required"` event emission (lines 71-74)
- **[`src-tauri/src/secure_storage.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/secure_storage.rs)** – Keyring management and `force_disable_keyring` implementation
- **[`src-tauri/src/logging.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/logging.rs)** – Global `"log-record"` event emission (lines 105-106)
- **[`src-tauri/src/operation.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/operation.rs)** – Progress events for long-running tasks
- **[`src/pages/Settings.tsx`](https://github.com/nab138/iloader/blob/main/src/pages/Settings.tsx)** – Frontend invocation examples (lines 100-103) and store access (lines 51-66)
- **[`src/pages/Pairing.tsx`](https://github.com/nab138/iloader/blob/main/src/pages/Pairing.tsx)** – Additional command invocations for device pairing

## Summary

- **iloader** uses three distinct IPC patterns: `invoke` for request-response, `emit`/`listen` for server-push updates, and `tauri-plugin-store` for persistent state.
- **Command registration** occurs centrally in [`lib.rs`](https://github.com/nab138/iloader/blob/main/lib.rs) using `generate_handler!`, with implementations spread across modules like [`account.rs`](https://github.com/nab138/iloader/blob/main/account.rs) and [`secure_storage.rs`](https://github.com/nab138/iloader/blob/main/secure_storage.rs).
- **Bidirectional events** handle asynchronous requirements like 2FA prompts, where the backend emits `"2fa-required"` and the frontend listens via `window.listen()`.
- **Global logging** uses `app_handle.emit("log-record", ...)` to stream logs from Rust to the React UI in real-time.
- **State persistence** leverages the store plugin for configuration data, accessible to both frontend and backend through JSON file storage.

## Frequently Asked Questions

### What IPC mechanism does iloader use for frontend-to-backend communication?

iloader uses Tauri's **invoke** system for all frontend-to-backend communication. The React code imports `invoke` from `@tauri-apps/api/core` and calls it with the command name and payload object. These commands are Rust functions marked with `#[tauri::command]` and registered in [`src-tauri/src/lib.rs`](https://github.com/nab138/iloader/blob/main/src-tauri/src/lib.rs) via the `generate_handler!` macro.

### How does iloader handle two-factor authentication between Rust and React?

When the Apple ID login requires 2FA, the Rust backend in [`account.rs`](https://github.com/nab138/iloader/blob/main/account.rs) (lines 71-74) emits a **window-specific event** using `window.emit("2fa-required", params)`. The React frontend listens for this event using `window.listen()` from `@tauri-apps/api/window`, displays a prompt to the user, and then sends the verification code back to the backend via another `invoke` call.

### Where does iloader store user settings and account data?

User settings persist in a JSON file managed by **tauri-plugin-store**. The backend can write directly to this store, while the frontend accesses it through `handle.store("data.json")` as seen in [`Settings.tsx`](https://github.com/nab138/iloader/blob/main/Settings.tsx) (lines 51-66). This includes saved email addresses, anisette server configurations, and the keyring override state managed in [`secure_storage.rs`](https://github.com/nab138/iloader/blob/main/secure_storage.rs).

### Can the iloader backend emit events to all windows simultaneously?

Yes. While window-specific events use `window.emit()` for targeted communication (like 2FA prompts), global broadcasts use `app_handle.emit()` as demonstrated in [`logging.rs`](https://github.com/nab138/iloader/blob/main/logging.rs) (lines 105-106). The `app_handle` approach emits to all windows, which is ideal for application-wide logging or background operation status updates.