# 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: ```rust 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:** ```rust 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 ```mermaid flowchart TB Repo["OnlineRepository"] -->|"success / RepoError"| Monitor["ConnectivityMonitor"] Monitor --> State{"is_server_reachable?"} State -->|"Online"| NoProbe["No background polling
(real traffic is the signal)"] State -->|"Offline"| Probe["5s /System/Info/Public probe
(recovery detector)"] Probe -->|"reachable again"| Monitor Monitor -->|"on change"| Emit["Emit connectivity:changed
+ 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>` 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:** ```typescript // 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](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.