# Connectivity & Network Architecture

## HTTP Client with Retry Logic

Location: `src-tauri/src/jellyfin/http_client.rs`

The HTTP client provides automatic retry with exponential backoff for network resilience:

    #![allow(unused)]
    fn main() {
    pub struct HttpClient {
        client: reqwest::Client,
        config: HttpConfig,
    }

    pub struct HttpConfig {
        pub timeout: Duration,        // Default: 30s (large library queries can be slow)
        pub max_retries: u32,         // Default: 3
    }
    }

> Note: ordinary requests use the 30s timeout above. The connectivity recovery probe (`ping`) uses a shorter, dedicated 5s timeout so an unreachable server is detected quickly while offline.

Retry Strategy:

- Retry delays: 1s, 2s, 4s (exponential backoff)
- Retries on: Network errors, 5xx server errors
- No retry on: 4xx client errors, 401/403 authentication errors

Error Classification:

    #![allow(unused)]
    fn main() {
    pub enum ErrorKind {
        Network,        // Connection failures, timeouts, DNS errors
        Authentication, // 401/403 responses
        Server,         // 5xx server errors
        Client,         // Other 4xx errors
    }
    }

## Connectivity Monitor

Location: `src-tauri/src/connectivity/mod.rs`

The connectivity monitor is the single source of truth for server reachability. Its primary signal is the outcome of real repository traffic — every server request the user actually makes. A standalone `/System/Info/Public` probe is kept only as an offline recovery detector.

### Source of truth: repository traffic

`OnlineRepository` reports the result of each server request to the monitor, classified via `RepoError`:

Repository outcome | Meaning | Effect on reachability
--- | --- | ---
`Ok(_)` | Server answered successfully | Mark reachable (instant recovery)
`Err(Authentication)` | Server answered with 401/403 | Mark reachable (server is up; request was rejected)
`Err(NotFound)` | Server answered with 404 | Mark reachable (server is up)
`Err(Server)` | Server answered with 5xx / bad body | Mark reachable (server is up)
`Err(Network)` | Connection failure / timeout / DNS | Candidate for offline (see debounce)
`Err(Database)` | Local cache error only | No effect (not a server signal)

This classification fixes the previous bug where a successful `/System/Info/Public` ping reported "online" even while the user's authenticated data calls were failing — and vice versa.

### Time-window debounce (offline) + instant recovery (online)

To stop the banner from flapping on a single dropped request, the transition to offline is debounced over a time window:

- On the first `Network` failure, the monitor records `first_failure_at`.
- It flips `is_server_reachable = false` only once `Network` failures have persisted continuously for `OFFLINE_CONFIRM_WINDOW` (5s) with no intervening success.
- Any success (or server-answered error) clears `first_failure_at` and immediately marks reachable.

Recovery is therefore instant and asymmetric: one good response brings the app back online, but a brief blip never trips the banner.

### Offline-only recovery probe

    flowchart TB
        Repo["OnlineRepository"] -->|"success / RepoError"| Monitor["ConnectivityMonitor"]
        Monitor --> State{"is_server_reachable?"}
        State -->|"Online"| NoProbe["No background polling<br/>(real traffic is the signal)"]
        State -->|"Offline"| Probe["5s /System/Info/Public probe<br/>(recovery detector)"]
        Probe -->|"reachable again"| Monitor
        Monitor -->|"on change"| Emit["Emit connectivity:changed<br/>+ connectivity:reconnected"]
        Emit --> Frontend["Frontend Store → banner"]

While online, there is no background polling — real requests keep the state fresh. While offline, the fast 5s probe runs so an idle app still detects the server returning even when no user traffic is flowing.

Features:

- Traffic-driven: Reachability follows the requests the user actually makes.
- Time-window debounce: Offline declared only after `OFFLINE_CONFIRM_WINDOW` (5s) of sustained network failure; recovery is instant.
- Offline-only probe: 5s `/System/Info/Public` probe runs only while offline.
- Event Emission: Emits `connectivity:changed` and `connectivity:reconnected` events.
- Thread-Safe: Uses `Arc<RwLock<>>` for shared state.

Tauri Commands:

Command | Description
--- | ---
`connectivity_check_server` | Manual reachability check (also used by the frontend's advisory `navigator.onLine` hint)
`connectivity_set_server_url` | Update monitored server URL
`connectivity_get_status` | Get current connectivity status
`connectivity_start_monitoring` | Start the offline recovery probe
`connectivity_stop_monitoring` | Stop the probe
`connectivity_mark_reachable` | Mark reachable — driven by `OnlineRepository` on every server success
`connectivity_mark_unreachable` | Mark unreachable — driven by `OnlineRepository` on `RepoError::Network` (subject to debounce)

Frontend Integration:

    // The store is a pure reflection of backend events — it no longer decides
    // reachability itself. navigator.onLine is advisory: it triggers an immediate
    // recheck rather than forcing the offline state.
    listen<{ isReachable: boolean }>("connectivity:changed", (event) => {
      updateConnectivityState(event.payload.isReachable);
    });

## Network Resilience Architecture

The connectivity system provides resilience through multiple layers:

1. HTTP Client Layer: Automatic retry with exponential backoff
2. Connectivity Monitoring: Reachability derived from real repository traffic, with an offline-only recovery probe
3. Frontend Integration: Offline mode detection and UI updates (a pure reflection of backend events)
4. Sync Queue: Offline mutations queued for later (see 06-downloads-and-offline.md)

Design Principles:

- Single source of truth: Reachability follows the outcome of real requests, classified via `RepoError`; the frontend store and the probe never compete to decide it.
- Fail Fast: Don't retry 4xx errors (client errors, authentication).
- Fail Slow: Retry network and 5xx errors with increasing delays.
- Debounced offline, instant online: Declare offline only after a sustained failure window; recover on the first success.
- Probe only when needed: Background polling runs only while offline, as a recovery detector.
- Event-Driven: Frontend reacts to connectivity changes via events.