flutter_tor
Native Flutter plugin for embedded Tor client on iOS & macOS using Tor.xcframework (iCepa) with obfs4/snowflake bridge support via IPtProxy.
Features
- Embedded Tor client — no external processes or VPN profiles required.
- SOCKS5 proxy with automatic port assignment.
- Bridge support: built-in obfs4 and snowflake presets, or custom bridge lines.
- Reactive streams for connection status and native logs.
- Hot-restart safe — native singleton preserves Tor state across Dart restarts.
Platform support
| iOS | macOS | Android | Web | Windows | Linux |
|---|---|---|---|---|---|
| ✅ | ✅ | — | — | — | — |
Getting started
Requirements
- iOS 15.0+ / macOS 12.0+
- Flutter 3.3+
Installation
dependencies:
flutter_tor: ^0.2.0
macOS entitlements
Tor requires outbound network access. Add these to your macOS Runner entitlements (DebugProfile.entitlements and Release.entitlements):
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
Usage
Basic connection (no bridges)
import 'package:flutter_tor/flutter_tor.dart';
// Start Tor
await Tor.initialize();
// Listen for status changes
Tor.statusStream.listen((event) {
print('${event.status} ${event.progress}%');
});
// Get SOCKS5 proxy port once connected
final port = await Tor.getProxyPort();
// Stop Tor
await Tor.stop();
Using bridges
// Use built-in obfs4 bridges
await Tor.initialize(TorBridgePresets.obfs4Default);
// Use snowflake
await Tor.initialize(TorBridgePresets.snowflake);
// Use custom bridge lines
await Tor.initialize(TorBridgeConfig(
useBridges: true,
bridgeLines: ['obfs4 198.51.100.1:443 ...'],
));
Fetching fresh bridges
Built-in bridge lines can become stale. TorBridgePresets can fetch up-to-date bridges from the Tor Project's circumvention API:
// Fetch fresh obfs4 bridges (country-aware, falls back to built-in list)
final config = await TorBridgePresets.fetchObfs4();
await Tor.initialize(config);
// Fetch fresh snowflake bridges
final sf = await TorBridgePresets.fetchSnowflake();
await Tor.initialize(sf);
Both methods throw on network failure — use the static presets (obfs4Default, snowflake) as a fallback.
Making requests through Tor
Use the SOCKS5 proxy port with any HTTP client that supports SOCKS proxies (e.g., socks5_proxy):
import 'dart:io';
import 'package:socks5_proxy/socks_client.dart';
final port = await Tor.getProxyPort();
final client = HttpClient();
SocksTCPClient.assignToHttpClient(client, [
ProxySettings(InternetAddress.loopbackIPv4, port),
]);
final request = await client.getUrl(Uri.parse('https://check.torproject.org/api/ip'));
final response = await request.close();
Controlling native logs
Native logs (NSLog and Tor stdout/stderr) can be disabled via a single flag. Set it before initialize() to also suppress Tor's early startup messages:
// Disable all native logs
Tor.logsEnabled = false;
await Tor.initialize();
// Re-enable at any time
Tor.logsEnabled = true;
Recovering state after hot restart
Tor runs in a native singleton that outlives Dart restarts. Sync on startup:
final status = await Tor.getStatus();
if (status.status == TorStatus.connected) {
final port = await Tor.getProxyPort();
// ready to use
}
API overview
| Class | Description |
|---|---|
Tor |
Main entry point — initialize(), stop(), getProxyPort(), getStatus(), statusStream, logStream, logsEnabled. TorIos is a deprecated alias |
TorBridgeConfig |
Bridge configuration with useBridges, bridgeLines, useObfs4, useSnowflake |
TorBridgePresets |
Ready-to-use presets: obfs4Default, snowflake, noBridges |
TorStatus |
Enum: disconnected, connecting, connected, error |
TorStatusEvent |
Status snapshot with status, progress, errorMessage, bridges |
TorLogEvent |
Native log entry with level, tag, message, timestamp |
License
MIT License. See LICENSE for details.
Libraries
- flutter_tor
- Embedded Tor client for Flutter (iOS & macOS).