Skip to main content

HTTP Interceptors

Audience: End users, system administrators, technical stakeholders Last Updated: 2026-04-06 Version: 2.0.0


Table of Contents

  1. Overview
  2. How HTTP Interceptors Work
  3. The Six Interceptors
  4. Authentication Flow
  5. Error Handling & User Notifications
  6. Retry & Resilience
  7. Request Timing & Logging
  8. Session Expiry Detection
  9. Endpoint Exclusions
  10. Technical Specifications
  11. Security Considerations
  12. Troubleshooting
  13. Related Documentation

Overview

HTTP interceptors are middleware that process every network request the app makes before it leaves the device and every response that comes back. Llamafin uses a chain of six interceptors to automatically handle server URL management, authentication, error handling, retry logic, request timing, and session expiry detection.

Why It Matters

Without interceptors, every API call in the app would need to manually:

  • Prepend the server URL to the request path
  • Attach authentication tokens to headers
  • Handle errors and show user notifications
  • Retry failed requests on server errors
  • Track request timing for debugging
  • Detect expired sessions and log the user out

Interceptors centralize this logic so that every API call automatically benefits from these behaviors without any additional code.

Key Benefits

BenefitDescription
Automatic AuthenticationTokens are attached to every request without manual handling
Consistent Error HandlingAll errors are caught, logged, and shown to the user as toast messages
Self-Healing RetriesServer errors are automatically retried with exponential backoff
Session ProtectionExpired sessions are detected and the user is safely logged out
Performance MonitoringEvery request is timed and logged for debugging
URL ManagementServer URL changes are handled centrally without updating every API call

How HTTP Interceptors Work

The Chain Concept

Think of interceptors as a series of checkpoints that every request passes through on its way to the server, and every response passes through on its way back. Each checkpoint can inspect, modify, or block the request/response.

App Makes Request


┌─────────────────────────────────────────┐
│ Checkpoint 1: Server URL │
│ → Prepends server URL │
│ → Adds base authentication header │
└─────────────────┬───────────────────────┘


┌─────────────────────────────────────────┐
│ Checkpoint 2: Error Handler │
│ → Passes through (handles errors │
│ on the way back) │
└─────────────────┬───────────────────────┘


┌─────────────────────────────────────────┐
│ Checkpoint 3: Authentication │
│ → Waits for app to load your session │
│ → Attaches your auth token │
└─────────────────┬───────────────────────┘


┌─────────────────────────────────────────┐
│ Checkpoint 4: Retry Logic │
│ → Sets up automatic retry if the │
│ server returns an error │
└─────────────────┬───────────────────────┘


┌─────────────────────────────────────────┐
│ Checkpoint 5: Request Timer │
│ → Records when the request starts │
│ → Logs how long it took │
└─────────────────┬───────────────────────┘


┌─────────────────────────────────────────┐
│ Checkpoint 6: Session Monitor │
│ → Checks for "unauthorized" errors │
│ → Logs you out if your session expired│
└─────────────────┬───────────────────────┘


Sent to Server

Response Flow (Reverse)

When the server responds, the response travels back through the same checkpoints in reverse order (from Checkpoint 6 back to Checkpoint 1). This allows interceptors to:

  • Detect 401/403 errors before the app sees them
  • Retry failed requests before giving up
  • Log timing information
  • Show user-friendly error messages

The Six Interceptors

1. Server URL Interceptor

What It Does: Automatically prepends your Jellyfin server URL to every API request and attaches a base authentication header.

How It Works:

  1. Reads your configured server URL from app settings
  2. Prepends it to the request path (e.g., /Items/123 becomes http://your-server:8096/Items/123)
  3. Attaches an X-Emby-Authorization header with device and app information

Bypasses: Skips requests for:

  • Static assets (/assets/*)
  • Absolute URLs (already complete URLs starting with http)
  • Requests explicitly marked to bypass (e.g., server discovery pings)

Header Format:

X-Emby-Authorization: MediaBrowser Client="llamafin", Device="iPhone", DeviceId="abc-123", Version="2.0.0"

This header identifies your app, device, and version to the Jellyfin server. The authentication token is added by the next interceptor.


2. HTTP Error Interceptor

What It Does: Catches HTTP errors and displays user-friendly toast messages while logging details for debugging.

Error Types Handled:

Error TypeWhat Triggers ItWhat You See
Network ErrorNo internet, DNS failure, server unreachableToast: "Unknown network error occurred"
Client Error (4xx)Bad request, not found, unauthorized (excluding 401/403)Toast: "Client error (status code)"
Server Error (5xx)Server crash, internal error, service unavailableToast: "Server error (status code)"
Lyrics Not Found (404)No lyrics available for a songNothing (silently handled, no error shown)

Special Case -- Lyrics 404: When the app requests lyrics for a song and none exist, the server returns a 404. This is expected behaviour (not every song has lyrics), so the interceptor converts this into a successful empty response rather than showing an error.


3. Authentication Interceptor

What It Does: Waits for your session to be loaded, then attaches your authentication token to every request.

How It Works:

  1. Public Access: Requests to public endpoints (server info, login page, public user list) pass through immediately -- no authentication needed.

  2. Session Wait: For authenticated requests, the interceptor pauses until your session has been fully rehydrated from storage. This prevents race conditions when the app starts up.

  3. Token Attachment: Once your session is loaded, it appends your access token to the authentication header:

    X-Emby-Authorization: ..., Token="your-access-token"
  4. Expired Session Detection: If your session is marked as expired before the request even fires, the interceptor stops the request and logs you out immediately.

Why Session Wait Matters: When you open the app, it needs time to read your saved session from storage. Without this wait, API calls could fire before your token is loaded, resulting in unauthorized errors. The interceptor ensures every request has your token attached.


4. Retry Interceptor

What It Does: Automatically retries failed requests when the server returns a 5xx error or a network failure occurs.

Retry Logic:

ConditionRetry?Delay Before Retry
Server error (5xx)Yes, up to 3 attempts100ms, then 200ms, then 400ms
Network failure (no response)Yes, up to 3 attempts100ms, then 200ms, then 400ms
Client error (4xx)NoN/A
Not found (404)NoN/A

Exponential Backoff: Each retry waits twice as long as the previous one:

  • 1st retry: 100ms delay
  • 2nd retry: 200ms delay
  • 3rd retry: 400ms delay

Total maximum retry delay: 700ms (less than one second).

Why Not Retry Client Errors: A 4xx error (like "not found" or "bad request") means the request itself is invalid. Retrying won't fix it -- the same error will occur again. Only transient server issues (5xx) and network drops are worth retrying.


5. Request Timestamp Interceptor

What It Does: Measures how long each HTTP request takes and logs the timing for debugging.

What It Logs:

EventLog LevelInformation Recorded
SuccessDebugHTTP method, URL, status code, start time, end time, duration (ms)
FailureWarningSame as success, plus error status text

Example Log Entry:

Request Succeeded: GET /Items/123?fields=Path
Status: 200
Start: 2026-04-06T10:30:15.123Z
End: 2026-04-06T10:30:15.345Z
Duration: 222ms

Note on Retry Timing: If a request is retried, the logged duration includes all retry delays. A request that retries twice will report the total time including the 100ms + 200ms backoff delays, not just the final successful request time.


6. Unauthorised Interceptor

What It Does: Detects when the server rejects your authentication (401 or 403 errors) and safely logs you out.

How It Works:

  1. 401/403 Detection: Catches any response with status 401 (Unauthorized) or 403 (Forbidden).

  2. Session Verification: Before taking action, it checks if you actually have an active session. This prevents accidental logouts for unauthenticated requests made before you've logged in.

  3. Session Cleanup: If you have a valid token and the server rejects it:

    • Logs a warning with the failing URL
    • Dispatches a "session expired" action
    • Shows a toast: "Session expired"
    • Redirects you to the login page
  4. Re-throws the Error: The error is passed back to the original requester so it can handle the failure appropriately.

Why Session Verification Matters: Without this check, the interceptor would log you out for any 401 error, including requests made by the app before you've ever logged in. The verification ensures logout is only triggered for requests made by an authenticated user whose token has been rejected.


Authentication Flow

How Authentication Travels with Requests

┌─────────────────────────────────────────────────────────────┐
│ X-Emby-Authorization Header │
├─────────────────────────────────────────────────────────────┤
│ │
│ Stage 1 (Server URL Interceptor): │
│ MediaBrowser Client="llamafin", Device="iPhone", │
│ DeviceId="abc-123", Version="2.0.0" │
│ │
│ Stage 2 (Auth Interceptor): │
│ MediaBrowser Client="llamafin", Device="iPhone", │
│ DeviceId="abc-123", Version="2.0.0", │
│ Token="your-access-token-here" │
│ │
└─────────────────────────────────────────────────────────────┘

This two-stage construction ensures:

  • The server always receives device identification (even for unauthenticated requests)
  • The token is only added when a valid session exists
  • The format is compatible with Jellyfin/Emby server authentication

Error Handling & User Notifications

What You See When Things Go Wrong

ScenarioUser NotificationWhat Happens Behind the Scenes
Server crashes mid-requestToast: "Server error (500)" after 3 retriesRetries 3 times with backoff, then shows error
You lose internetToast: "Unknown network error" after 3 retriesRetries 3 times, then shows generic network error
Your session expiresToast: "Session expired" → redirected to login401 detected, session cleared, redirect to login
Song not found (404)Toast: "Client error (404)"Logged as warning, error re-thrown to caller
No lyrics availableNothing (transparently handled)404 converted to successful empty response
Bad request (400)Toast: "Client error (400)"Logged as warning, error re-thrown to caller

Error Toast Messages

All error toasts use translation keys so they appear in your selected language:

Translation KeyWhen Displayed
errors.HTTP_ERROR_UNKNOWNNetwork error, DNS failure, server unreachable
errors.HTTP_ERROR_CLIENT4xx error (400, 404, etc.) with status code parameter
errors.HTTP_ERROR_SERVER5xx error (500, 502, etc.) with status code parameter
app.TOAST.SESSION_EXPIRED401/403 detected with active session

Retry & Resilience

Automatic Retry Behaviour

When the server experiences a transient error (5xx) or your network drops momentarily, the retry interceptor automatically attempts to recover:

Retry Timeline Example:

Time 0ms:    Request sent → Server returns 500 error
Time 100ms: 1st retry → Server returns 500 error
Time 300ms: 2nd retry (100ms + 200ms backoff) → Server returns 500 error
Time 700ms: 3rd retry (300ms + 400ms backoff) → Server returns 200 OK ✓

If all 3 retries fail, the error is passed to the HTTP Error Interceptor for user notification.

What Is NOT Retried

Error TypeWhy Not Retried
400 Bad RequestThe request is malformed; retrying won't fix it
401 UnauthorizedYour token is invalid; needs re-authentication, not retry
403 ForbiddenYou lack permissions; retrying won't grant access
404 Not FoundThe resource doesn't exist; retrying won't create it

Request Timing & Logging

How Timing Works

Every request is timed from the moment it leaves the app (after passing through all interceptors) to the moment the response is received.

What's Logged:

  • HTTP method (GET, POST, etc.)
  • Full URL with query parameters
  • Response status code
  • Start and end timestamps (ISO format)
  • Total duration in milliseconds

Log Levels:

  • Debug: Successful requests (for troubleshooting)
  • Warning: Failed requests (for error investigation)

Accessing Timing Data

Request timing logs appear in the app's debug logging system, accessible through:

  • Settings → Advanced → Debugging → App Logs
  • Diagnostic export from the loading shell during app boot

Session Expiry Detection

How Session Expiry Is Detected

The app uses a two-layer approach to detect expired sessions:

LayerInterceptorDetection Method
PreemptiveAuth InterceptorChecks is_expired flag before sending request
ReactiveUnauthorised InterceptorCatches 401/403 response from server

Preemptive Detection (Auth Interceptor)

Before a request fires, the Auth Interceptor checks if your session's is_expired flag is set. If so:

  • The request is blocked (never sent to the server)
  • A "session expired" toast is shown
  • You're redirected to the login page

Reactive Detection (Unauthorised Interceptor)

If the preemptive check passes but the server still rejects your token:

  • The 401/403 response is caught
  • The interceptor verifies you have an active session
  • If you do, the session is cleared and you're logged out
  • The error is re-thrown so the original request handler can respond

Why Two Layers?

The preemptive layer catches sessions that are known to be expired locally (e.g., from a keep-alive check). The reactive layer catches cases where the server invalidates your token without the app knowing (e.g., password change on another device, server-side session revocation).


Endpoint Exclusions

Requests That Skip Interceptors

Not all requests should go through the full interceptor chain. Certain requests are explicitly excluded:

Excluded FromPaths/ConditionsReason
Auth Interceptor/System/Info/Public, /Users/AuthenticateByName, /Users/PublicPublic endpoints that must work before login
Server URL Interceptor/assets/*, ./assets/*, URLs starting with httpStatic assets and already-complete URLs
Server URL InterceptorRequests with BYPASS_SERVER_URL_INTERCEPTION flagServer discovery pings and system-level calls
Unauthorised InterceptorRequests with BYPASS_LLAMAFIN_CONNECT_INTERCEPTION flagP2P peer communication (shouldn't log you out for peer errors)
HTTP Error Interceptor404 responses on URLs ending with /Lyrics"No lyrics found" is expected, not an error

Bypass Flags

The app uses two special flags (HttpContextToken) to skip interceptors for specific requests:

FlagUsed For
BYPASS_SERVER_URL_INTERCEPTIONServer discovery pings that shouldn't have the server URL prepended
BYPASS_LLAMAFIN_CONNECT_INTERCEPTIONP2P communication with other Llamafin devices on your network

Technical Specifications

Interceptor Chain

PropertyValue
Total Interceptors6
Registration PatternHTTP_INTERCEPTORS multi-provider
Request Processing OrderRegistration order (1st registered = 1st processed)
Response Processing OrderReverse registration order (6th registered = 1st processed)

Retry Configuration

PropertyValue
Max Retry Attempts3
Retry Base Delay100ms
Backoff Multiplier2x per attempt
Retry Delays100ms, 200ms, 400ms
Total Maximum Retry Delay700ms
Retry TriggerHTTP status >= 500 or status === 0 (network failure)
Excluded from RetryAll 4xx errors (400, 401, 403, 404, etc.)

Authentication Header

ComponentValue SourceFallback
Client nameSettings → Advanced → Player Namellamafin
Device modelDevice info (native plugin)WebBrowser
Device IDDevice UUID (native plugin)unknown-device-id
VersionApp version (build config)1.0.0
TokenAuth state (from login)(not appended if no session)

Public Endpoints (No Auth Required)

EndpointPurpose
/System/Info/PublicServer information (version, name, etc.)
/Users/AuthenticateByNameLogin endpoint
/Users/PublicPublic user list

Error Toast Messages

Translation KeyParametersDisplayed For
errors.HTTP_ERROR_UNKNOWNNoneNetwork errors, client-side failures
errors.HTTP_ERROR_CLIENTstatus (4xx code)400, 404, etc. (excluding 401/403)
errors.HTTP_ERROR_SERVERstatus (5xx code)500, 502, 503, etc.
app.TOAST.SESSION_EXPIREDNone401/403 with active session

Security Considerations

Authentication Security

AspectImplementation
Token StorageTokens stored in persistent storage (IndexedDB), rehydrated on startup
Token TransmissionSent via X-Emby-Authorization header (not in URL or body)
Token ExpiryNo automatic token refresh; expired sessions require re-authentication
Session Keep-AliveServer-side session is kept alive by periodic API calls (handled by auth feature, not interceptors)

Known Limitations

LimitationRisk LevelDescription
No token refreshMediumExpired tokens require full re-authentication (no silent refresh)
No request cancellation on logoutLowIn-flight requests continue after logout, may trigger additional session expiry toasts
Hardcoded public pathsLowPublic endpoint paths are hardcoded in the interceptor rather than centralized configuration
Substring match for public pathsLowAuth bypass uses includes() instead of startsWith(), which could theoretically match unintended URLs

Troubleshooting

Common Issues

IssueLikely CauseSolution
All API calls fail immediately on app startupServer URL not configuredGo to Settings → Source and configure your Jellyfin server URL
"Session expired" toast appears repeatedlyToken has expired or been revokedLog out and log back in with your credentials
"Server error (500)" toast appearsJellyfin server is crashing or overloadedCheck your Jellyfin server status, restart if necessary
"Unknown network error" toastNo internet connection or server unreachableCheck your network connection, verify server URL is accessible
Requests are slowServer is far away or overloadedCheck request timing in Settings → Advanced → Debugging → App Logs

Diagnostic Steps

If you're experiencing API errors:

  1. Check Network Status: Ensure your device has an active internet connection
  2. Verify Server URL: Confirm the server URL in Settings → Source is correct and accessible from your device
  3. Check Server Status: Ensure your Jellyfin server is running and accessible
  4. Review App Logs: Go to Settings → Advanced → Debugging → App Logs to see detailed request timing and error information
  5. Re-authenticate: Log out and log back in to establish a fresh session with a new token

When to Seek Help

Contact support if:

  • You consistently see "Server error" toasts despite a healthy Jellyfin server
  • Session expiry occurs immediately after login (may indicate server-side session issues)
  • Network errors persist on stable connections (may be a DNS or proxy issue)


Last Updated: 2026-04-06 Version: 2.0.0