Skip to main content
The Networking module is a production-grade HTTP layer with:
  • A single async HTTPClient API (send(_:) -> HTTPResponse)
  • Interceptors for auth, headers, and retry decisions
  • Configurable retry (exponential backoff with jitter, Retry-After aware)
  • Response caching (URLCache-backed, with optional synthetic TTL)
  • Error mapping to the app-wide AppError type
Full technical details live in your project at docs/modules/Networking.md.

What’s new in v2.2.0

  • Swift 6 strict-concurrency clean: HTTPClient, HTTPInterceptor, TokenProvider, HTTPRequest, and HTTPResponse are all Sendable; protocol-typed properties use explicit any.
  • URLSessionHTTPClient.applySyntheticTTL guards on a resolvable URL instead of force-unwrapping, so it stays safe under malformed request inputs. No public API change.

Purpose

Handles:
  • HTTP requests via a single HTTPClient.send(_:) API
  • Cross-cutting concerns (auth tokens, app/telemetry headers, retry) through interceptors
  • Consistent error handling by mapping transport and HTTP errors to AppError

Core Abstractions

HTTPClient Protocol

HTTPRequest

HTTPRequest is a concrete Sendable value type — there is no per-endpoint protocol to conform to. Build one with a path and method, then layer headers, query, body, and an optional cache policy via the with… helpers.
HTTPMethod covers .get, .post, .put, .patch, .delete, .head, and .options.

HTTPResponse

send(_:) returns an HTTPResponse, not a decoded model — you decode response.data yourself.

Interceptors

Interceptors conform to HTTPInterceptor and are applied in order. Each can mutate the outgoing URLRequest (adapt) and/or vote on whether a failed attempt should be retried (shouldRetry).
Three interceptors ship out of the box:
Bearer-token authenticationAuthInterceptor(tokenProvider:) adds an Authorization: Bearer {token} header:
  • Reads the current token from an injected TokenProvider
  • Only sets the header if one is not already present (respects manual overrides)
  • Does nothing when currentToken() returns nil
  • Returns .noRetry — token-refresh logic stays in the Auth module to avoid coupling
The app wires this with Storage.KeychainTokenProvider, so tokens come from the secure iOS Keychain.

RetryPolicy

RetryPolicy configures the backoff math used between attempts:
Delay is baseDelay * 2^(attempt-1), capped at maxDelay, then randomized by ±jitter.

Configuration

The boilerplate wires the client in CompositionRoot. Interceptors run in array order (headers → auth → retry):
When no proxy base URL is configured, the app falls back to an UnconfiguredHTTPClient that fails fast instead of silently hitting a placeholder host.

Customization Examples

Add a custom request

Because HTTPRequest is a value type, a “custom request” is just a factory that returns one:

Add a custom interceptor

Error Handling

Transport failures and non-2xx responses are mapped to Core.AppError before they reach you:

Key Files

Dependencies

  • CoreAppError, AppLogger

Used By

  • Auth — Supabase auth calls (SupabaseAuthAPI, SessionManager)
  • AIProxyLLMClient proxy requests to the Supabase Edge Function
  • Your services — any API client you build on top of HTTPClient

Best Practices

  • Build HTTPRequest values with the with… helpers
  • Decode response.data at the call site
  • Encode bodies with JSONEncoder and set Content-Type
  • Clear naming (verb + noun) for request factories
  • Catch AppError from send(_:)
  • Let RetryInterceptor handle idempotent/transient retries
  • Surface user-facing copy via AppError.localizedUserMessage
  • Order matters — headers and auth before retry
  • Keep each interceptor focused on one concern
  • Test interceptors in isolation
  • Return .noRetry unless the interceptor owns retry logic

Learn More

Full Documentation

Complete Networking guide

Auth Module

Uses HTTPClient for API calls

AI Module

Uses HTTPClient for the proxy

Architecture

See the networking layer

Test Coverage

The Networking package ships with its own NetworkingTests target (client, interceptor ordering, retry policy, caching, URL builder). It runs as one of the 12 package test targets in the suite of ~598 tests across 12 package test targets + the app test suites, executed in a single Boilerplate.xctestplan pass.