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.

example/lib/main.dart

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

import 'package:flutter/material.dart';
import 'package:outline_mobileproxy/outline_mobileproxy.dart';

void main() {
  runApp(const ExampleApp());
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Outline Mobileproxy',
      theme: ThemeData(colorSchemeSeed: const Color(0xFF2E7D32), useMaterial3: true),
      home: const ProxyDemoPage(),
    );
  }
}

class ProxyDemoPage extends StatefulWidget {
  const ProxyDemoPage({super.key});

  @override
  State<ProxyDemoPage> createState() => _ProxyDemoPageState();
}

class _ProxyDemoPageState extends State<ProxyDemoPage> {
  final _outline = OutlineMobileproxy();
  final _configController = TextEditingController(text: 'split:3');

  String _platformVersion = 'Unknown';
  ProxyInfo? _proxy;
  bool _busy = false;
  String _log = '';

  @override
  void initState() {
    super.initState();
    _outline.getPlatformVersion().then((v) {
      if (mounted) setState(() => _platformVersion = v ?? 'unknown');
    });
  }

  @override
  void dispose() {
    _configController.dispose();
    super.dispose();
  }

  void _appendLog(String message) {
    final timestamp = TimeOfDay.now().format(context);
    setState(() => _log = '[$timestamp] $message\n$_log');
  }

  Future<void> _start() async {
    setState(() => _busy = true);
    try {
      final proxy = await _outline.start(transportConfig: _configController.text.trim());
      setState(() => _proxy = proxy);
      _appendLog('Started proxy at ${proxy.address}');
    } on OutlineMobileproxyException catch (e) {
      _appendLog('Failed to start: ${e.message}');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<void> _stop() async {
    setState(() => _busy = true);
    try {
      await _outline.stop();
      setState(() => _proxy = null);
      _appendLog('Stopped proxy');
    } on OutlineMobileproxyException catch (e) {
      _appendLog('Failed to stop: ${e.message}');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<void> _testConnection() async {
    final proxy = _proxy;
    if (proxy == null) return;

    setState(() => _busy = true);
    _appendLog('Fetching https://example.com through ${proxy.address} ...');
    try {
      final httpClient = HttpClient();
      httpClient.findProxy = (uri) => 'PROXY ${proxy.address}';
      httpClient.badCertificateCallback = (cert, host, port) => false;

      final request = await httpClient
          .getUrl(Uri.parse('https://example.com'))
          .timeout(const Duration(seconds: 15));
      final response = await request.close().timeout(const Duration(seconds: 15));
      await response.drain<void>();
      _appendLog('Success: HTTP ${response.statusCode}');
      httpClient.close();
    } catch (e) {
      _appendLog('Request failed: $e');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final isRunning = _proxy != null;

    return Scaffold(
      appBar: AppBar(title: const Text('Outline Mobileproxy Demo')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Text('Running on $_platformVersion', style: Theme.of(context).textTheme.bodySmall),
          const SizedBox(height: 16),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('Transport config', style: Theme.of(context).textTheme.titleMedium),
                  const SizedBox(height: 4),
                  Text(
                    'e.g. split:3, ss://<userinfo>@host:port, or socks5://host:port',
                    style: Theme.of(context).textTheme.bodySmall,
                  ),
                  const SizedBox(height: 8),
                  TextField(
                    controller: _configController,
                    enabled: !isRunning,
                    decoration: const InputDecoration(border: OutlineInputBorder()),
                  ),
                  const SizedBox(height: 16),
                  Row(
                    children: [
                      Expanded(
                        child: FilledButton(
                          onPressed: _busy || isRunning ? null : _start,
                          child: const Text('Start'),
                        ),
                      ),
                      const SizedBox(width: 12),
                      Expanded(
                        child: OutlinedButton(
                          onPressed: _busy || !isRunning ? null : _stop,
                          child: const Text('Stop'),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Card(
            color: isRunning
                ? Theme.of(context).colorScheme.primaryContainer
                : Theme.of(context).colorScheme.surfaceContainerHighest,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Row(
                children: [
                  Icon(isRunning ? Icons.check_circle : Icons.circle_outlined),
                  const SizedBox(width: 12),
                  Expanded(
                    child: Text(isRunning ? 'Running at ${_proxy!.address}' : 'Stopped'),
                  ),
                  if (isRunning)
                    TextButton(
                      onPressed: _busy ? null : _testConnection,
                      child: const Text('Test connection'),
                    ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Text('Log', style: Theme.of(context).textTheme.titleMedium),
          const SizedBox(height: 8),
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: Theme.of(context).colorScheme.surfaceContainerHighest,
              borderRadius: BorderRadius.circular(8),
            ),
            child: Text(_log.isEmpty ? 'No activity yet.' : _log),
          ),
        ],
      ),
    );
  }
}
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