outline_mobileproxy 0.0.2 copy "outline_mobileproxy: ^0.0.2" to clipboard
outline_mobileproxy: ^0.0.2 copied to clipboard

Flutter plugin for the Outline SDK's Mobileproxy library: run a local HTTP proxy on Android/iOS backed by Shadowsocks, SOCKS5, or an auto-selecting Smart Dialer, with no VPN permissions required.

outline_mobileproxy #

pub package Build native binaries license

Not affiliated with or endorsed by the Outline Foundation. This is an independent, third-party wrapper around their open-source SDK.

A Flutter plugin for the Outline SDK's Mobileproxy library, for Android and iOS.

Mobileproxy runs a local HTTP CONNECT proxy on-device, backed by a Go StreamDialer that can tunnel through a Shadowsocks server, a SOCKS5 upstream, a chain of transports, or an automatically-selected "Smart Dialer" strategy for circumventing network interference. Point your app's HTTP client, gRPC channel, or WebView at the local proxy address this plugin returns, and its traffic is routed accordingly — no VPN permissions, no NEPacketTunnelProvider, no VpnService, required.

This package does not implement its own proxy protocol logic; it's a thin, idiomatic Flutter wrapper around the official golang.getoutline.org/sdk/x/mobileproxy Go Mobile bindings, compiled to a real Android AAR and iOS XCFramework (see How the native binaries are built).

Contents #

Features #

  • Local HTTP CONNECT proxy, no VPN entitlement or system-level tunnel needed.
  • Static transport configuration (ss://, socks5://, split:, chained transports, ...).
  • Smart Dialer support: auto-select a working DNS/TLS strategy from a YAML strategy list, tested against domains you provide.
  • Typed Dart API with specific exceptions (InvalidConfigException, ProxyStartException, ProxyStopException).
  • Prebuilt native binaries checked in — no Go toolchain needed to consume the plugin, only to rebuild it.

Installation #

dependencies:
  outline_mobileproxy: ^0.0.1
flutter pub get

Platform requirements #

Platform Minimum version
Android API 21 (Android 5.0)
iOS 13.0

No further native setup is required — the plugin bundles the compiled Mobileproxy library for both platforms.

Usage #

Static transport configuration #

import 'package:outline_mobileproxy/outline_mobileproxy.dart';

final outline = OutlineMobileproxy();

final proxy = await outline.start(
  transportConfig: 'ss://<base64-userinfo>@host:port',
);
print('Local proxy listening at ${proxy.address}'); // e.g. 127.0.0.1:54321

// ... configure your networking library, see below ...

await outline.stop();

The transportConfig string follows the Outline SDK's config format, for example:

  • ss://<base64-userinfo>@host:port — a Shadowsocks server (the standard Outline access key format).
  • socks5://user:pass@host:port — a SOCKS5 upstream.
  • split:3 — split outgoing TCP streams at byte 3, a simple censorship-circumvention strategy that needs no server.
  • split:3|ss://... — transports can be chained.

Smart Dialer (automatic strategy selection) #

The Smart Dialer probes a list of DNS/TLS strategies against domains you provide, and picks the first one that works — useful when you don't have (or don't want to run) a proxy server, and just need to get past DNS/SNI-based interference.

final proxy = await outline.startSmart(
  config: SmartDialerConfig(
    testDomains: ['www.google.com', 'i.ytimg.com'],
    strategiesConfig: strategiesYaml, // see example config below
  ),
);

An example strategy config can be found at x/examples/smart-proxy/config.yaml in the outline-sdk repository.

Routing your networking library through the proxy #

Once started, proxy.address is a plain host:port HTTP proxy you can wire into whatever networking stack your app already uses.

dart:io HttpClient:

import 'dart:io';

final httpClient = HttpClient();
httpClient.findProxy = (uri) => 'PROXY ${proxy.address}';
final response = await httpClient.getUrl(Uri.parse('https://example.com'));

Dio:

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

final dio = Dio();
(dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
  final client = HttpClient();
  client.findProxy = (uri) => 'PROXY ${proxy.address}';
  return client;
};

gRPC (package:grpc v3.2.4+):

final channel = ClientChannel(
  'grpc.example.com',
  port: 443,
  options: ChannelOptions(
    proxy: Proxy(host: proxy.host, port: proxy.port),
  ),
);

Android WebView (androidx.webkit, native code):

ProxyController.getInstance().setProxyOverride(
  ProxyConfig.Builder().addProxyRule(proxy.address()).build(),
  {},
  {},
)

iOS WKWebView (iOS 17+, native code):

let endpoint = NWEndpoint.hostPort(
  host: NWEndpoint.Host(proxyHost),
  port: NWEndpoint.Port(integerLiteral: UInt16(proxyPort))
)
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore.proxyConfigurations = [
  .init(httpCONNECTProxy: endpoint)
]

More platform-specific snippets (OkHttp, JVM system properties) are in the Mobileproxy README.

Error handling #

try {
  await outline.start(transportConfig: key);
} on InvalidConfigException catch (e) {
  // The transport config (or Smart Dialer strategy config) is invalid, or no
  // working strategy was found.
} on ProxyStartException catch (e) {
  // The local proxy failed to bind/start (e.g. address already in use).
} on ProxyStopException catch (e) {
  // The proxy failed to stop cleanly.
}

Calling start/startSmart while a proxy is already running stops the previous one first. stop() is a no-op if nothing is running. Always call stop() when your app is done with the proxy (e.g. dispose(), AppLifecycleState.detached) to release the bound port.

API reference #

Method Description
start({transportConfig, localAddress}) Starts a proxy using a static transport config. Returns a ProxyInfo.
startSmart({config, localAddress}) Starts a proxy using the Smart Dialer. Returns a ProxyInfo.
stop({timeoutSeconds}) Stops the running proxy, if any.
isRunning() Whether a proxy is currently running.
currentProxy() The ProxyInfo of the running proxy, or null.
getPlatformVersion() The host OS name/version, mostly useful for diagnostics.

localAddress defaults to 127.0.0.1:0, letting the OS pick a free loopback port — read ProxyInfo.port to find out which one.

Example app #

The example/ app demonstrates both modes: enter a transport config, start/stop the proxy, and fire a test HTTP request through it. Run it with:

cd example
flutter run

The plugin's own integration tests exercise the real native proxy (start/stop/error-mapping) end-to-end on a device or simulator:

cd example
flutter test integration_test/plugin_integration_test.dart -d <device-id>

How the native binaries are built #

This plugin bundles prebuilt Mobileproxy binaries:

  • android/libs/mobileproxy-classes.jar + android/src/main/jniLibs/*/libgojni.so
  • ios/Frameworks/Mobileproxy.xcframework

They're built from the upstream Go source (golang.getoutline.org/sdk/x/mobileproxy) with Go Mobile, at a pinned, tagged revision rather than a moving branch — see tool/OUTLINE_SDK_REF for the exact ref and NATIVE_PROVENANCE.md for the resolved commit, toolchain versions, and flags used to produce what's currently checked in. The Android artifacts are the AAR produced by gomobile bind, unpacked into a plain jar + jniLibs, because the Android Gradle Plugin does not allow a library module to declare a local .aar file dependency (it can't be re-packaged into this plugin's own AAR).

Verifying the binaries. .github/workflows/build-native.yml rebuilds both artifacts from that same pinned ref on every push/PR that touches it, in the open, and:

  • diffs the generated iOS Objective-C header byte-for-byte against what's checked in — deterministic given the same source, so any mismatch fails the build — and compares the Android API surface with javap (public method signatures) rather than a raw jar diff, since the jar's bytes depend on the compiling JDK, not just the pinned Go source (a real cross-JDK CI run caught exactly this: same source, different JDK, different jar bytes, identical API);
  • builds and links the example app against the freshly built native libraries, and runs the plugin's integration tests against them on a real Android emulator and iOS Simulator, to functionally verify the compiled .so/Mach-O binaries (these embed a Go build ID even with -trimpath, so they aren't expected to be byte-identical across separate builds — functional verification is the honest bar here, not a raw binary diff);
  • uploads the freshly built artifacts so anyone can download and compare them independently, rather than trusting the checked-in copies by inspection alone.

To rebuild locally, e.g. to bump tool/OUTLINE_SDK_REF for an SDK update:

tool/build_native.sh all

Psiphon fallback support is intentionally not built in by default: the Psiphon library is GPL-licensed, and using it requires a config obtained directly from the Psiphon team (sponsor@psiphon.ca). If you need it, build a -tags=psiphon variant yourself following the Mobileproxy README and be mindful of the licensing implications for your app.

FAQ #

Does this need VPN permissions? No. Mobileproxy runs a local HTTP proxy, not a VPN. Nothing is added to AndroidManifest.xml or app entitlements; you explicitly opt individual networking clients into using the proxy.

Can I use my existing Outline / Shadowsocks access key? Yes — pass it directly as transportConfig, e.g. outline.start(transportConfig: 'ss://<key>@host:port').

Does it tunnel all app traffic automatically? No, only whatever you explicitly point at proxy.address (see Routing your networking library through the proxy). For system-wide tunneling you'd need a VPN service instead, which is out of scope for this package.

Why doesn't start() throw if I call it twice? By design — calling start/startSmart again stops the previous proxy and starts a new one, which matches the common "switch server" UX. If you need stricter semantics, check isRunning() first.

License #

Apache License 2.0 — see LICENSE. This plugin wraps the Outline SDK, also Apache-2.0 licensed, by the Outline Foundation / Jigsaw. Not officially affiliated with or endorsed by the Outline Foundation.

1
likes
140
points
11
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter plugin for the Outline SDK's Mobileproxy library: run a local HTTP proxy on Android/iOS backed by Shadowsocks, SOCKS5, or an auto-selecting Smart Dialer, with no VPN permissions required.

Repository (GitHub)
View/report issues

Topics

#proxy #networking #security #vpn

License

Apache-2.0 (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on outline_mobileproxy

Packages that implement outline_mobileproxy