sendProviderRequest function

Future<StreamedResponse> sendProviderRequest(
  1. Client httpClient,
  2. Request request,
  3. CancelToken? cancelToken
)

Sends request, racing cancelToken (abort wins), and validates the response status.

Redirects are decided HERE, never by the underlying client (followRedirects is forced off): a same-origin hop is re-issued verbatim (method/body/headers — credentials intact), while a cross-origin 3xx FAILS as ProviderHttpError carrying the Location. An endpoint that bounces an API call to another host (expired SSO portal, moved URL) never gets the request re-sent, so Authorization: Bearer … can never leak to a redirect target — the SEC-01 trust boundary lives in this shared layer, not per relay.

Throws AbortedError when the token fires before the headers arrive and ProviderHttpError on a non-200 status (with the body consumed for the error message). The adapter's try/catch turns both into error events.

Implementation

Future<http.StreamedResponse> sendProviderRequest(
  http.Client httpClient,
  http.Request request,
  CancelToken? cancelToken,
) async {
  request.followRedirects = false;
  var current = request;
  for (var redirects = 0; ; redirects++) {
    final response = await sendWatchedProviderRequest(
      httpClient,
      current,
      cancelToken,
    );
    final location = response.headers['location'];
    final target = _redirectTarget(current.url, response.statusCode, location);
    if (target == null) return _validateStreamResponse(current, response);
    if (redirects >= _maxProviderRedirects ||
        !_sameOrigin(current.url, target)) {
      final body = await response.stream.bytesToString();
      throw ProviderHttpError(
        response.statusCode,
        body,
        requestUrl: current.url,
        redirectLocation: location,
      );
    }
    // Consume the (tiny) redirect body so the connection can be reused.
    await response.stream.drain<void>();
    current = _reissue(current, target, response.statusCode);
  }
}