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 outcomeMeaningEffect on reachability
Ok(_)Server answered successfullyMark reachable (instant recovery)
Err(Authentication)Server answered with 401/403Mark reachable (server is up; request was rejected)
Err(NotFound)Server answered with 404Mark reachable (server is up)
Err(Server)Server answered with 5xx / bad bodyMark reachable (server is up)
Err(Network)Connection failure / timeout / DNSCandidate for offline (see debounce)
Err(Database)Local cache error onlyNo 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:

CommandDescription
connectivity_check_serverManual reachability check (also used by the frontend's advisory navigator.onLine hint)
connectivity_set_server_urlUpdate monitored server URL
connectivity_get_statusGet current connectivity status
connectivity_start_monitoringStart the offline recovery probe
connectivity_stop_monitoringStop the probe
connectivity_mark_reachableMark reachable — driven by OnlineRepository on every server success
connectivity_mark_unreachableMark 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.