interceptly.dart
Read-only128 lines • 4391 bytespub.devGitHub
SDK
Interceptly SDK140/140 PUB POINTS98% POPULARITY

Production-safe, shake-activated in-app network inspector for Flutter.

pub.dev
/**
 * @id interceptly
 * @title Interceptly SDK — Network Inspector for Flutter
 * @description High-performance network inspector for Flutter providing real-time traffic visualization for Dio, Http, and Chopper with minimal impact on UI performance.
 * @date Mar 2026 – Present
 * @tags Dart, Flutter, Dio, Http, Chopper, Networking, Open Source
 * @summary Real-time traffic visualization for Dio, Http, and Chopper with shake-to-open overlay and cURL export.
 * @pubDev https://pub.dev/packages/interceptly
 * @github https://github.com/Tuanpluss02/interceptly
 * @order 1
 * @default true
 */
/// Package: interceptly
/// Author: Do Ngoc Tuan (@Tuanpluss02)
/// Platform: Flutter / Dart (iOS, Android, macOS, Windows, Web)
/// Published on: pub.dev/packages/interceptly

import 'dart:async';
import 'dart:collection';
import 'dart:io';

/// Core inspector engine managing intercepted HTTP traffic with ring-buffer storage
/// and zero-overhead production tree-shaking.
class Interceptly {
  static final Interceptly instance = Interceptly._internal();
  Interceptly._internal();

  bool _isEnabled = false;
  final int _maxRecords = 250;
  final Queue<NetworkRecord> _buffer = DoubleLinkedQueue<NetworkRecord>();
  final StreamController<NetworkRecord> _recordStream = StreamController.broadcast();

  /// Initialize Interceptly with optional gesture activation and header sanitization.
  void init({
    bool enableShakeDetection = true,
    bool redactSensitiveHeaders = true,
    List<String> obfuscatedKeys = const ['authorization', 'x-api-key', 'cookie'],
  }) {
    _isEnabled = true;
    _installHttpOverrides(redactSensitiveHeaders, obfuscatedKeys);
    print('[Interceptly] Inspector activated. Monitoring Dio, Http, and Chopper traffic.');
  }

  /// Global hook overriding Dart's internal HttpClient factory for package:http
  void _installHttpOverrides(bool redact, List<String> obfuscated) {
    HttpOverrides.global = _InterceptlyHttpOverrides(
      onTrafficCaptured: (record) => _capture(record),
    );
  }

  /// Captures network transaction into high-speed memory buffer without UI stutter
  void _capture(NetworkRecord record) {
    if (!_isEnabled) return;
    if (_buffer.length >= _maxRecords) {
      _buffer.removeFirst();
    }
    _buffer.addLast(record);
    _recordStream.add(record);
  }

  Stream<NetworkRecord> get stream => _recordStream.stream;
  List<NetworkRecord> get history => _buffer.toList(growable: false);
}

/// Dio Interceptor integration for automatic request/response lifecycle logging
class InterceptlyDioInterceptor {
  final Interceptly _inspector = Interceptly.instance;

  void onRequest(String id, String url, String method, Map<String, String> headers) {
    // Record request start timestamp for accurate latency profiling
  }

  void onResponse(String id, int statusCode, int responseBytes, Duration duration) {
    // Capture response payload and latency telemetry
  }

  void onError(String id, String errorMessage, int? statusCode) {
    // Flag failed transaction in inspector UI
  }
}

/// Chopper Interceptor integration for structured REST API inspection
class InterceptlyChopperInterceptor {
  final Interceptly _inspector = Interceptly.instance;

  Future<dynamic> intercept(dynamic chain) async {
    // Intercept Chopper Request/Response pipeline
    return chain;
  }
}

/// Captured network transaction model with cURL generator
class NetworkRecord {
  final String id;
  final String url;
  final String method;
  final int statusCode;
  final Duration duration;
  final int requestBytes;
  final int responseBytes;
  final Map<String, String> headers;
  final DateTime timestamp;

  const NetworkRecord({
    required this.id,
    required this.url,
    required this.method,
    required this.statusCode,
    required this.duration,
    required this.requestBytes,
    required this.responseBytes,
    required this.headers,
    required this.timestamp,
  });

  /// Generates a replayable cURL command for debugging in Postman/terminal
  String toCurl() {
    final headerFlags = headers.entries
        .map((e) => '-H "${e.key}: ${e.value}"')
        .join(' ');
    return 'curl -X $method "$url" $headerFlags --compressed';
  }
}

class _InterceptlyHttpOverrides extends HttpOverrides {
  final void Function(NetworkRecord) onTrafficCaptured;
  _InterceptlyHttpOverrides({required this.onTrafficCaptured});
}