interceptly 2.1.0 copy "interceptly: ^2.1.0" to clipboard
interceptly: ^2.1.0 copied to clipboard

Client-agnostic network and realtime inspector for Flutter with overlay UI, replay tools, WebSocket inspection, and HAR/cURL/Postman export.

Interceptly #

pub package pub points license

Interceptly is a high-performance, client-agnostic network & realtime inspector for Flutter. It provides real-time traffic visualization for HTTP and bidirectional WebSockets with zero UI jank and minimal memory footprint.

The core interceptly package contains the inspection UI overlay, background isolate processing pipeline, hybrid storage engine, and realtime stream buffer. It is completely independent of specific HTTP/WebSocket networking libraries.


Preview #

REST Inspector Realtime Inspector
REST Realtime

Features #

  • Client-Agnostic Core: Zero unnecessary dependencies bundled in core. Use only the adapters your app needs.
  • High Performance: Background isolates handle payload parsing and serialization to prevent UI frame drops.
  • Realtime WebSocket Support: Live inspection of incoming & outgoing frames (text, JSON, binary) with 60fps throttling and per-session FIFO ring buffering.
  • Hybrid Storage: Smart memory management that offloads large payloads to temporary files.
  • Postman-like Replay: Built-in request editor to modify headers/body and re-send HTTP requests.
  • Network Simulation: Global simulation profiles for Offline, Slow 3G, Fast 4G, or custom latency/bandwidth.
  • Advanced Formatters: Specialized views for GraphQL operations, Multipart FormData, cURL generation, and Binary data.
  • Master Search: Filter across URLs, headers, bodies, frames, and errors with instant highlight navigation.
  • Endpoint Filtering: Synchronously filter URLs with the shouldCapture predicate to bypass analytics or unwanted endpoints.
  • Export: Share or export traffic sessions as HAR, cURL commands, or Postman collections.

Architecture & Satellite Adapters #

To inspect network traffic, pair interceptly with the adapter(s) corresponding to your networking client:

Client Library Adapter Package Import
Dio interceptly_dio import 'package:interceptly_dio/interceptly_dio.dart';
http interceptly_http import 'package:interceptly_http/interceptly_http.dart';
Chopper interceptly_chopper import 'package:interceptly_chopper/interceptly_chopper.dart';
web_socket_channel interceptly_web_socket import 'package:interceptly_web_socket/interceptly_web_socket.dart';

Installation #

Add the core package to your pubspec.yaml:

flutter pub add interceptly

And add any network adapter(s) your application uses:

# Example: Adding Dio and WebSocket adapters
flutter pub add interceptly_dio
flutter pub add interceptly_web_socket

Quick Start #

1. Initialize Interceptly #

Initialize Interceptly in your app startup:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:interceptly/interceptly.dart';

final _navKey = GlobalKey<NavigatorState>();

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MaterialApp(navigatorKey: _navKey, home: const MyApp()));

  if (kDebugMode || kProfileMode) {
    Interceptly.init(
      navigatorKey: _navKey,
      settings: const InterceptlySettings(
        maxEntries: 1000,
        maxFramesPerSession: 1000,
      ),
      showFloatingButton: true, // Floating overlay bug button
    );
  }
}

2. Attach Adapters #

Dio (package:interceptly_dio)

import 'package:dio/dio.dart';
import 'package:interceptly_dio/interceptly_dio.dart';

final dio = Dio()..interceptors.add(InterceptlyDioInterceptor());

HTTP (package:interceptly_http)

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

final client = InterceptlyHttpClient(http.Client());
final response = await client.get(Uri.parse('https://api.example.com/data'));

Chopper (package:interceptly_chopper)

import 'package:chopper/chopper.dart';
import 'package:interceptly_chopper/interceptly_chopper.dart';

final chopper = ChopperClient(
  interceptors: [InterceptlyChopperInterceptor()],
);

WebSocket (package:interceptly_web_socket)

import 'package:interceptly_web_socket/interceptly_web_socket.dart';

final channel = InterceptlyWebSocketChannel.connect(
  Uri.parse('wss://echo.websocket.events'),
);

channel.stream.listen((message) {
  print('Received echo: $message');
});

channel.sink.add('Hello Interceptly!');

Realtime API for Custom Protocols #

For custom socket implementations, streaming clients, or proprietary transports, you can record realtime connections and frames directly using the Interceptly API:

// 1. Record connection opening
Interceptly.instance.recordRealtimeConnection(
  id: 'conn_123',
  url: 'wss://my-custom-socket.example.com',
  headers: {'User-Agent': 'FlutterApp'},
);

// 2. Record incoming or outgoing text frames
Interceptly.instance.recordRealtimeFrame(
  RealtimeFrame.text(
    id: RequestId.generate(),
    sessionId: 'conn_123',
    direction: FrameDirection.incoming,
    text: '{"event":"chat_message","body":"Hello!"}',
  ),
);

// 3. Record binary frames
Interceptly.instance.recordRealtimeFrame(
  RealtimeFrame.binary(
    id: RequestId.generate(),
    sessionId: 'conn_123',
    direction: FrameDirection.outgoing,
    bytes: uint8ListPayload,
  ),
);

// 4. Record connection closure
Interceptly.instance.closeRealtimeConnection(
  'conn_123',
  closeCode: 1000,
  closeReason: 'Normal closure',
);

Extending Interceptly: Traffic Collectors SPI #

Interceptly follows the Bridge / SPI (Service Provider Interface) pattern. If you use a custom networking library (such as gRPC, GraphQL, Ktor bridge, or Socket.IO), you can easily build your own adapter by implementing the contract interfaces without re-implementing timing, simulation, or storage logic:

Custom HTTP Adapter (HttpCaptureAdapter) #

Implement HttpCaptureAdapter and delegate lifecycle events to HttpTrafficCollector:

import 'package:interceptly/interceptly.dart';

class MyCustomHttpInterceptor implements HttpCaptureAdapter {
  MyCustomHttpInterceptor([
    HttpTrafficCollector? collector,
  ])  : collector = collector ?? DefaultHttpTrafficCollector();

  @override
  InspectorSession get session => collector.session;

  @override
  final HttpTrafficCollector collector;

  Future<void> onSend(MyCustomRequest req) async {
    // 1. Notify start of request (applies pre-request latency/offline simulation)
    final context = await collector.startRequest(
      method: req.method,
      uri: req.uri,
      headers: req.headers,
      bodyBytes: req.bodyBytes,
      contentType: req.contentType,
    );
    req.extra['interceptly_context'] = context;
  }

  Future<void> onReceive(MyCustomResponse res) async {
    final context = res.request.extra['interceptly_context'] as HttpRequestContext?;
    if (context == null) return;

    // 2. Notify completed response (applies download throttle and records capture)
    await collector.completeResponse(
      context: context,
      statusCode: res.statusCode,
      headers: res.headers,
      bodyBytes: res.bodyBytes,
      responseContentType: res.contentType,
    );
  }

  void onError(MyCustomRequest req, Object error) {
    final context = req.extra['interceptly_context'] as HttpRequestContext?;
    if (context == null) return;

    // 3. Notify request failure
    collector.failRequest(
      context: context,
      error: error,
      errorMessage: error.toString(),
    );
  }
}

Custom Realtime Adapter (RealtimeCaptureAdapter) #

For custom WebSockets, SSE streams, or MQTT/Socket.IO connections, implement RealtimeCaptureAdapter and delegate to RealtimeTrafficCollector:

import 'package:interceptly/interceptly.dart';

class MyCustomRealtimeClient implements RealtimeCaptureAdapter {
  MyCustomRealtimeClient({
    RealtimeTrafficCollector? collector,
  })  : collector = collector ?? DefaultRealtimeTrafficCollector();

  @override
  InspectorSession get session => collector.session;

  @override
  final RealtimeTrafficCollector collector;

  void onConnect(String sessionId, Uri endpoint) {
    collector.recordConnection(sessionId: sessionId, uri: endpoint);
  }

  void onMessageReceived(String sessionId, dynamic rawData) {
    collector.recordFrame(
      sessionId: sessionId,
      direction: FrameDirection.incoming,
      type: rawData is String ? FrameType.text : FrameType.binary,
      payload: rawData,
    );
  }

  void onMessageSent(String sessionId, dynamic rawData) {
    collector.recordFrame(
      sessionId: sessionId,
      direction: FrameDirection.outgoing,
      type: rawData is String ? FrameType.text : FrameType.binary,
      payload: rawData,
    );
  }

  void onDisconnect(String sessionId, int? code, String? reason) {
    collector.recordClose(
      sessionId: sessionId,
      closeCode: code,
      closeReason: reason,
    );
  }
}

Configuration Reference #

All capture parameters and limits are configured via InterceptlySettings:

Parameter Default Description
bodyOffloadThreshold 50 KB Body size above which payloads move from RAM to temporary files
previewTruncationBytes 16 KB Inline byte budget for pending request rows awaiting a response
maxBodyBytes 5 MB Hard cap before payload truncation; <= 0 means unlimited
maxUiPreviewBytes 2 MB Max bytes decoded for UI detail display; <= 0 means unlimited
maxFramesPerSession 1000 Max realtime frames retained per WebSocket connection session
maxQueuedEvents 500 Pending capture events buffered before the background isolate is ready
maxEntries 5000 Maximum network requests/sessions retained in history
pendingTimeout 45 s Duration after which an unfinished request is marked as timed out
urlDecodeEnabled true Decode URL percent-encodings in inspector views
shouldCapture null Optional predicate bool Function(String url); returns false to skip capture

Note

shouldCapture Execution Limits: Evaluated synchronously on each request URL before dispatch and before queuing to background storage. The predicate must be lightweight and non-blocking. If it returns false, capture is completely bypassed, saving CPU and storage overhead.


Network Simulation #

Simulate slow or offline network conditions globally:

// Apply built-in profiles
Interceptly.instance.setNetworkSimulation(NetworkSimulationProfile.slow3G);

// Or create a custom profile
Interceptly.instance.setNetworkSimulation(
  const NetworkSimulationProfile(
    name: 'High Latency 2G',
    latencyMs: 800,
    downloadKbps: 250,
    uploadKbps: 100,
  ),
);

// Clear simulation
Interceptly.instance.clearNetworkSimulation();

Entry-Point Configuration & Triggers #

Interceptly.init Configuration Table #

Parameter Type Default Description
navigatorKey GlobalKey<NavigatorState> Required Global navigator key used to mount overlay and push inspector screen
showFloatingButton bool true Whether to display the draggable floating action button overlay
fabChild Widget? null Optional custom widget rendered inside the floating action button
customTrigger Stream<void>? null External event stream to trigger opening the inspector (e.g. shake detector, volume keys, gesture)
settings InterceptlySettings? Default settings Storage thresholds, body caps, filtering predicates, and timeout durations

Warning

Interceptly.init has no release guard: calling it in a release build ships the inspector to users. Always wrap the call in if (kDebugMode || kProfileMode).

Overlay & Navigation Triggers #

  • Route Transition Observer: Set navigatorObservers: [Interceptly.navigatorObserver] on your MaterialApp to ensure the floating overlay button always remains on top across route transitions.
  • Custom Trigger Stream: Supply customTrigger: shakeSensorStream to open the inspector on device shake or hardware buttons.
  • Custom Button Icon: Pass fabChild: Icon(Icons.bug_report, color: Colors.amber) to customize button appearance.
  • Programmatic Control:
    // Open inspector programmatically
    Interceptly.showInspector(context);
    
    // Temporarily hide or show the floating action button
    Interceptly.hideFloatingButtonForSession();
    Interceptly.showFloatingButtonForSession();
    final isHidden = Interceptly.isFloatingButtonHiddenForSession;
    
    // Clear all recorded history
    await Interceptly.instance.clear();
    
    // Remove overlay and cancel trigger subscriptions
    Interceptly.detach();
    

Storage Model #

  • Small Payloads: Kept in memory for instant responsiveness.
  • Large Payloads: Offloaded to temporary files via a background worker isolate.
  • Lazy Loading: Payloads are read from disk only when a record is viewed.
  • Multi-tiered Limits: Separate caps protect memory footprint during pending requests, stored history, and UI rendering.

Example Application #

An interactive Flutter showcase application demonstrating all adapters (Dio, HTTP, Chopper, WebSocket) is provided in the example directory:

cd packages/interceptly/example
flutter run

License #

MIT License - see LICENSE for details.

Published by stormx.dev

7
likes
0
points
349
downloads

Publisher

verified publisherstormx.dev

Weekly Downloads

Client-agnostic network and realtime inspector for Flutter with overlay UI, replay tools, WebSocket inspection, and HAR/cURL/Postman export.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, path_provider

More

Packages that depend on interceptly

Packages that implement interceptly