intercepted_http 0.2.0 copy "intercepted_http: ^0.2.0" to clipboard
intercepted_http: ^0.2.0 copied to clipboard

A composable HTTP interceptor layer for package:http. Add auth headers, token refresh, retry logic, and logging without replacing your HTTP client.

example/lib/main.dart

import 'dart:convert';
import 'dart:developer';

import 'package:http/http.dart' as http;
import 'package:intercepted_http/intercepted_http.dart';

// ── 1. Auth interceptor — adds Bearer token, refreshes on 401 ────────────────

class AuthInterceptor extends HttpInterceptor {
  AuthInterceptor({required this.tokenProvider, required this.onRefresh});

  final Future<String> Function() tokenProvider;
  final Future<String> Function() onRefresh;
  bool _refreshed = false;

  @override
  Future<void> onRequest(http.Request request) async {
    final token = await tokenProvider();
    request.headers['Authorization'] = 'Bearer $token';
  }

  @override
  Future<void> onError(http.Response response, http.Request request) async {
    if (response.statusCode == HttpStatusCode.unauthorized) {
      final newToken = await onRefresh();
      request.headers['Authorization'] = 'Bearer $newToken';
      _refreshed = true;
    }
  }

  @override
  Future<Duration?> shouldRetry(
    Object error,
    StackTrace stackTrace,
    http.Request request, {
    http.Response? response,
  }) async {
    if (response?.statusCode == HttpStatusCode.unauthorized && _refreshed) {
      _refreshed = false;
      return Duration.zero;
    }
    return null;
  }
}

// ── 2. Logging interceptor ────────────────────────────────────────────────────

class LoggingInterceptor extends HttpInterceptor {
  @override
  Future<void> onRequest(http.Request request) async {
    log('→ ${request.method} ${request.url}');
  }

  @override
  Future<http.Response> onResponse(
    http.Response response,
    http.Request request,
  ) async {
    log('← ${response.statusCode} ${request.url}');
    return response;
  }

  @override
  Future<void> onError(http.Response response, http.Request request) async {
    log('✗ ${response.statusCode} ${request.url} — ${response.body}');
  }
}

// ── 3. Retry on network errors with exponential backoff ──────────────────────

class NetworkRetryInterceptor extends HttpInterceptor {
  int _attempt = 0;

  @override
  Future<Duration?> shouldRetry(
    Object error,
    StackTrace stackTrace,
    http.Request request, {
    http.Response? response,
  }) async {
    // Retry on timeouts and connection errors, never on HTTP errors
    if (response != null) return null;
    final delay = Duration(milliseconds: 200 * (1 << _attempt));
    _attempt++;
    return delay;
  }
}

// ── Usage ─────────────────────────────────────────────────────────────────────

Future<void> main() async {
  String token = 'initial-token';

  final client = InterceptedHttp(
    interceptors: [
      LoggingInterceptor(),
      AuthInterceptor(
        tokenProvider: () async => token,
        onRefresh: () async {
          token = 'refreshed-token';
          return token;
        },
      ),
      NetworkRetryInterceptor(),
    ],
  );

  try {
    // Works exactly like http.Client
    final response = await client.get(
      Uri.parse('https://jsonplaceholder.typicode.com/todos/1'),
    );

    final data = jsonDecode(response.body);
    log('Got: $data');
  } on HttpClientException catch (e) {
    log('HTTP error ${e.statusCode}: ${e.message}');
  } finally {
    client.close();
  }
}
1
likes
160
points
145
downloads

Documentation

API reference

Publisher

verified publishermatheusvinicius.dev.br

Weekly Downloads

A composable HTTP interceptor layer for package:http. Add auth headers, token refresh, retry logic, and logging without replacing your HTTP client.

Homepage
Repository (GitHub)
View/report issues

Topics

#http #networking #interceptor #middleware

License

MIT (license)

Dependencies

http

More

Packages that depend on intercepted_http