v2ray_box 1.0.6
v2ray_box: ^1.0.6 copied to clipboard
A Flutter plugin for VPN connectivity with dual-core support (Xray-core & sing-box). Supports VLESS, VMess, Trojan, Shadowsocks, Hysteria, TUIC, WireGuard and more. Switch between cores at runtime.
V2Ray Box #
A Flutter plugin for VPN functionality with dual-core support: Xray-core and sing-box. Switch between cores at runtime without reinstalling.
Platform Support #
| Platform | Status | Mode |
|---|---|---|
| Android | Supported | VPN / Proxy |
| iOS | Supported | VPN (via NetworkExtension) |
| macOS | Supported | Proxy (system proxy) |
Features #
- Dual Core Engine — Xray-core and sing-box with runtime switching
- Multiple V2Ray protocols (VMess, VLESS, Trojan, Shadowsocks, Hysteria, Hysteria2, TUIC, WireGuard, SSH)
- Multiple transports (WebSocket, gRPC, HTTP/H2, HTTPUpgrade, xHTTP, QUIC)
- TLS, Reality, uTLS fingerprint, Multiplex support
- VPN and Proxy modes
- Real-time traffic statistics (Xray stats API / sing-box Clash API)
- Connection status monitoring
- Config link parsing and validation
- Single and parallel batch ping testing with streaming results
- Per-app proxy (Android only)
- Customizable notification (Android)
- Persistent total traffic storage
Installation #
dependencies:
v2ray_box:
path: ../v2ray_box
Platform Setup #
Important: Core files are not bundled with this package. Build them with the scripts below in your app project.
Android Core Build (Script-Based) #
For Android apps using this package, build cores with these scripts:
- Copy
example/scripts/build_android_libxray.shand/orexample/scripts/build_android_libsingbox.shinto your app repo (for example:scripts/) - Run from app root (or pass
--project-root)
Example:
bash ./scripts/build_android_libxray.sh
bash ./scripts/build_android_libsingbox.sh
Output paths:
- Android Xray:
android/app/libs/libxray.aar - Android sing-box:
android/app/src/main/jniLibs/<abi>/libsingbox.so
For command usage, flags, and ready-to-run examples, see example/scripts/README.md.
Android — Required Manifest & Gradle Settings #
Add these Android settings (same as the example app).
- Required permissions in
android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
</manifest>
- Service declarations + cleartext inside
<application>:
<application
android:usesCleartextTraffic="true"
tools:targetApi="31"
...>
<service
android:name="com.example.v2ray_box.bg.VPNService"
android:exported="false"
android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="vpn" />
</service>
<service
android:name="com.example.v2ray_box.bg.ProxyService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="proxy" />
</service>
</application>
- Required Gradle settings in
android/app/build.gradleorbuild.gradle.kts:
android {
defaultConfig {
multiDexEnabled true
}
packaging {
jniLibs {
useLegacyPackaging = true
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
}
Kotlin DSL:
android {
defaultConfig {
multiDexEnabled = true
}
packaging {
jniLibs {
useLegacyPackaging = true
}
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
}
Notes:
android:usesCleartextTraffic="true"is required for local HTTP endpoints (e.g.127.0.0.1:9090).- Keep
android:permission="android.permission.BIND_VPN_SERVICE"onVPNService. QUERY_ALL_PACKAGESis only needed for Per-App Proxy.
iOS Notes #
- Default PacketTunnel path in this package is sing-box via
Libbox.xcframework. - iOS Xray requires custom PacketTunnel integration with
LibXray.xcframework. - Minimum version: iOS 15.0+.
macOS Notes #
- Place
xrayand/orsing-boxundermacos/Frameworks/. - Ensure executability (
chmod +x). - Minimum version: macOS 10.15+.
Usage #
Initialize #
import 'package:v2ray_box/v2ray_box.dart';
final v2rayBox = V2rayBox();
await v2rayBox.initialize(
notificationStopButtonText: 'Disconnect',
);
Switch Core Engine #
// Set core engine (disconnect VPN first if connected)
await v2rayBox.setCoreEngine('xray'); // Xray-core (default)
await v2rayBox.setCoreEngine('singbox'); // sing-box
// Get current core engine
final engine = await v2rayBox.getCoreEngine();
Connect / Disconnect #
await v2rayBox.connect('vless://uuid@server:port?...#Name', name: 'My Config');
await v2rayBox.disconnect();
Monitor Status #
v2rayBox.watchStatus().listen((status) {
// VpnStatus.stopped, starting, started, stopping
});
Monitor Traffic #
v2rayBox.watchStats().listen((stats) {
print('Up: ${stats.formattedUplink}, Down: ${stats.formattedDownlink}');
});
Core Info #
final info = await v2rayBox.getCoreInfo();
// Returns: { "core": "xray", "engine": "xray-core", "version": "26.2.6" }
// or: { "core": "singbox", "engine": "sing-box", "version": "1.12.22" }
Ping #
// Single (timeout is optional, default: 7000ms)
final latency = await v2rayBox.ping(configLink);
final latencyFastFail = await v2rayBox.ping(configLink, timeout: 4000);
// Parallel batch (timeout is optional, default: 7000ms per config)
final sub = v2rayBox.watchPingResults().listen((result) {
print('${result["link"]}: ${result["latency"]}ms');
});
final results = await v2rayBox.pingAll(links);
final resultsSlowNetwork = await v2rayBox.pingAll(links, timeout: 10000);
await sub.cancel();
VPN Mode #
await v2rayBox.setServiceMode(VpnMode.vpn); // route all traffic
await v2rayBox.setServiceMode(VpnMode.proxy); // local proxy only
Per-App Proxy (Android) #
await v2rayBox.setPerAppProxyMode(PerAppProxyMode.exclude);
await v2rayBox.setPerAppProxyList(['com.example.app'], PerAppProxyMode.exclude);
Total Traffic #
final traffic = await v2rayBox.getTotalTraffic();
print('Total: ${traffic.formattedTotal}');
await v2rayBox.resetTotalTraffic();
Supported Protocols #
| Protocol | Link Format |
|---|---|
| VLESS | vless://uuid@server:port?params#name |
| VMess | vmess://base64_json |
| Trojan | trojan://password@server:port?params#name |
| Shadowsocks | ss://base64(method:password)@server:port#name |
| Hysteria2 | hy2://auth@server:port?params#name |
| Hysteria | hy://server:port?params#name |
| TUIC | tuic://uuid:password@server:port?params#name |
| WireGuard | wg://private_key@server:port?params#name |
| SSH | ssh://user:password@server:port?params#name |
Transports #
type=ws | type=grpc | type=http | type=h2 | type=httpupgrade | type=xhttp | type=quic
TLS Options #
security=tls | security=reality | fp=chrome | alpn=h2,http/1.1 | pbk=...&sid=...
Multiplex #
mux=1 | mux-max-streams=4
Architecture #
Core Engines by Platform #
| Android | iOS | macOS | |
|---|---|---|---|
| Xray-core integration | AAR library (in-process) | xcframework (via PacketTunnel) | CLI binary (subprocess) |
| sing-box integration | CLI binary (subprocess) | Libbox.xcframework (via PacketTunnel) | CLI binary (subprocess) |
| VPN Mode | Android VpnService + TUN | NetworkExtension PacketTunnel | N/A (proxy mode only) |
| Proxy Mode | SOCKS/HTTP local proxy | N/A (VPN mode only) | System proxy via networksetup |
| Traffic Stats | Xray stats API / Clash API | Clash API / PacketTunnel IPC | Clash API (sing-box) |
| Config Format | Xray JSON / sing-box JSON | Xray JSON / sing-box JSON | Xray JSON / sing-box JSON |
How Android sing-box VPN Mode Works #
On Android, the plugin uses Xray-core as a TUN bridge for sing-box:
- Android
VpnServicecreates a TUN interface - Xray-core reads from TUN and forwards traffic to sing-box's local SOCKS proxy (
127.0.0.1:10808) - sing-box handles the actual proxy connection to the remote server
This avoids the need for sing-box to manage TUN directly (which requires root on Android when running as a CLI binary).
How iOS VPN Works #
On iOS, the VPN runs as a PacketTunnel Network Extension:
- The main app generates the config JSON (sing-box or Xray format) and passes it to the PacketTunnel extension
- The PacketTunnel extension receives the config + core engine type via VPN tunnel options
- The extension uses
Libbox.xcframeworkfor sing-box. For Xray, you need customLibXray.xcframeworkintegration in PacketTunnel. - All device traffic is routed through the tunnel interface
How macOS Proxy Works #
On macOS, both cores run as CLI binaries (subprocesses):
- The plugin starts the selected core binary (
sing-boxorxray) with the generated config - The core opens a local SOCKS/HTTP proxy port
- The plugin configures macOS system proxy settings via
networksetupto route traffic through the local proxy - When stopped, system proxy settings are restored to their previous state
API Reference #
Initialization #
| Method | Returns | Description |
|---|---|---|
initialize({notificationStopButtonText, notificationIconName}) |
Future<void> |
Initialize the VPN core. Must be called first. |
setConfigOptions(options) |
Future<bool> |
Set configuration options for the VPN service |
configOptions |
ConfigOptions |
Get current configuration options (getter) |
Connection #
| Method | Returns | Description |
|---|---|---|
connect(link, {name, notificationTitle}) |
Future<bool> |
Start VPN with a config link |
connectWithJson(configJson, {name}) |
Future<bool> |
Start VPN with raw JSON config |
disconnect() |
Future<bool> |
Stop VPN connection |
restart(link, {name}) |
Future<bool> |
Restart VPN connection |
Status & Streams #
| Method | Returns | Description |
|---|---|---|
watchStatus() |
Stream<VpnStatus> |
Watch VPN status changes |
watchStats() |
Stream<VpnStats> |
Watch real-time traffic statistics |
watchAlerts() |
Stream<Map<String, dynamic>> |
Watch VPN alerts |
watchLogs() |
Stream<Map<String, dynamic>> |
Watch live log stream |
getLogs() |
Future<List<String>> |
Get current log buffer |
Core Engine #
| Method | Returns | Description |
|---|---|---|
getCoreInfo() |
Future<Map<String, dynamic>> |
Get core engine info (name, version) |
setCoreEngine(engine) |
Future<bool> |
Set active core ('xray' or 'singbox') |
getCoreEngine() |
Future<String> |
Get active core engine name |
Ping & Testing #
| Method | Returns | Description |
|---|---|---|
ping(link, {timeout}) |
Future<int> |
Test latency of a single config (ms). timeout is optional, default 7000 ms. |
pingAll(links, {timeout}) |
Future<Map<String, int>> |
Test latency of multiple configs in parallel. timeout is optional, default 7000 ms per config. |
watchPingResults() |
Stream<Map<String, dynamic>> |
Watch individual ping results during pingAll |
setPingTestUrl(url) |
Future<bool> |
Set custom URL for ping testing |
getPingTestUrl() |
Future<String> |
Get current ping test URL |
VPN Mode & Permissions #
| Method | Returns | Description |
|---|---|---|
setServiceMode(mode) |
Future<bool> |
Set VPN mode (VpnMode.vpn or VpnMode.proxy) |
getServiceMode() |
Future<VpnMode> |
Get current VPN mode |
checkVpnPermission() |
Future<bool> |
Check if VPN permission is granted |
requestVpnPermission() |
Future<bool> |
Request VPN permission from user |
Per-App Proxy (Android) #
| Method | Returns | Description |
|---|---|---|
setPerAppProxyMode(mode) |
Future<bool> |
Set per-app proxy mode |
getPerAppProxyMode() |
Future<PerAppProxyMode> |
Get current per-app proxy mode |
setPerAppProxyList(packages, mode) |
Future<bool> |
Set list of packages for per-app proxy |
getPerAppProxyList(mode) |
Future<List<String>> |
Get list of packages for per-app proxy |
getInstalledApps() |
Future<List<AppInfo>> |
Get list of installed applications |
getAppIcon(packageName) |
Future<String?> |
Get app icon as base64 PNG |
Traffic #
| Method | Returns | Description |
|---|---|---|
getTotalTraffic() |
Future<TotalTraffic> |
Get persistent total traffic stats |
resetTotalTraffic() |
Future<bool> |
Reset total traffic to zero |
Config Utilities #
| Method | Returns | Description |
|---|---|---|
parseConfig(link, {debug}) |
Future<String> |
Validate config link (empty = valid) |
generateConfig(link) |
Future<String> |
Generate full JSON config from link |
checkConfigJson(configJson) |
Future<String> |
Validate raw JSON config |
getActiveConfig() |
Future<String> |
Get currently active JSON config |
formatConfig(configJson) |
Future<String> |
Prettify a JSON config |
parseConfigLink(link) |
VpnConfig |
Parse link into VpnConfig object |
isValidConfigLink(link) |
bool |
Check if link is a valid config |
Notification (Android) #
| Method | Returns | Description |
|---|---|---|
setNotificationStopButtonText(text) |
Future<bool> |
Set stop button text |
setNotificationTitle(title) |
Future<bool> |
Set custom notification title |
setNotificationIcon(iconName) |
Future<bool> |
Set notification icon (drawable name) |
Subscription #
| Method | Returns | Description |
|---|---|---|
parseSubscription(link) |
Future<Map<String, dynamic>> |
Parse subscription import link |
generateSubscriptionLink(name, url) |
Future<String> |
Generate subscription link |
Misc #
| Method | Returns | Description |
|---|---|---|
setDebugMode(enabled) |
Future<bool> |
Enable/disable verbose logging |
getDebugMode() |
Future<bool> |
Get current debug mode state |
formatBytes(bytes) |
Future<String> |
Format bytes to human-readable string |
proxyDisplayType(type) |
Future<String> |
Get display name for proxy type |
availablePort({startPort}) |
Future<int> |
Find an available network port |
selectOutbound(groupTag, outboundTag) |
Future<bool> |
Select outbound in a group |
setClashMode(mode) |
Future<bool> |
Set clash routing mode |
setLocale(locale) |
Future<bool> |
Set locale for the core library |
getPlatformVersion() |
Future<String?> |
Get platform version string |
Reducing App Size (Single Core Mode) #
If you want a smaller app, you can ship only one of the two cores on any platform:
Android:
- Xray-core only — Keep only
android/app/libs/libxray.aarand removelibsingbox.sofromjniLibs/. - sing-box only — Keep only
libsingbox.soinjniLibs/and removeandroid/app/libs/libxray.aar.
iOS:
- sing-box only — Only include
Libbox.xcframework. - Xray-core only — Use a custom PacketTunnel path with
LibXray.xcframework.
macOS:
- Only place the binary you need (
sing-boxorxray) inmacos/Frameworks/.
Important: When shipping a single core, make sure you do not expose the core switching option to users in your app's UI. If a user tries to switch to a core that isn't bundled, the VPN connection will fail. Set the default core engine to the one you've included and hide the engine selector from your settings page.
// Example: sing-box only app — set once at startup, no UI switch needed
await v2rayBox.setCoreEngine('singbox');