buildRetryingClient function

Client buildRetryingClient(
  1. Client inner, {
  2. int retries = 3,
  3. Duration delay(
    1. int retryCount
    )?,
})

Wraps inner so transient network failures do not surface as errors.

Uses RetryClient from package:http, which already implements the exponential backoff, rather than hand-rolling a loop.

Two rules decide what may be sent twice:

Only safe requests, meaning GET and HEAD. A replayed POST registers the user twice or creates a second document, so writes are never retried - one clear failure beats a duplicate record. This is decided from the outgoing request, before anything is sent, rather than from the response

  • a failure to connect has no response to inspect.

The token refresh is not an exception to that, though it reads like one: it is a POST only because it carries a token, and it creates nothing. It was allowed here for exactly that reason, on the argument that a single dropped packet should not end a session. That argument is wrong, because the API rotates refresh tokens.

Consider a refresh that reaches the server and is processed, rotating the token, whose response is then lost - a reset connection, or a 502 from a proxy sitting in front of an app that already did the work. The replay presents a token the server has now spent, so it answers 401, and _refreshAndSettle correctly reads a 401 on a refresh as "this session is over" and clears it. The user is signed out by a dropped packet, which is the very outcome the retry was added to prevent - and worse than not retrying, since without it the caller sees a transport error and the stored token survives to be tried again.

So: do not add the refresh endpoint back here. If refresh tokens ever stop rotating, that is the thing to change first.

Only transient causes. A connection that failed to open, and the gateway statuses a proxy returns while a server restarts. Not 429: the API's rate limit windows run from ten minutes to an hour, so retrying seconds later cannot succeed and only spends more of the budget - the caller is told to back off instead. Not 4xx: the server understood the request and rejected it, and it will reject it again.

delay is exposed so tests do not have to wait.

Implementation

http.Client buildRetryingClient(
  http.Client inner, {
  int retries = 3,
  Duration Function(int retryCount)? delay,
}) {
  return _SafeRequestRetryClient(
    inner: inner,
    retrying: RetryClient(
      inner,
      retries: retries,
      when: _isRetriableResponse,
      whenError: _isRetriableError,
      delay: delay ?? _backoff,
    ),
  );
}