This commit is contained in:
Fabian Freund
2025-08-29 13:27:23 +02:00
parent 917af47579
commit caef036422
23 changed files with 665 additions and 7 deletions
@@ -0,0 +1,34 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins-dependencies
/build/
/coverage/
.gradle/
@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "20f82749394e68bcfbbeee96bad384abaae09c13"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
- platform: android
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
@@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.
@@ -0,0 +1 @@
TODO: Add your license here.
@@ -0,0 +1,15 @@
# pluggable_transports_proxy
A new Flutter plugin project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/to/develop-plugins),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
@@ -0,0 +1,35 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lint/package.yaml
# Uncomment the following section to specify additional rules.
linter:
rules:
unawaited_futures: true
discarded_futures: true
collection_methods_unrelated_type: true
analyzer:
plugins:
- custom_lint
exclude:
- "**.g.dart"
- "**.swagger.dart"
- "**.freezed.dart"
- "**.chopper.dart"
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx
@@ -0,0 +1,70 @@
group = "eu.weblibre.pluggable_transports_proxy"
version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = "2.1.0"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.9.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
namespace = "eu.weblibre.pluggable_transports_proxy"
compileSdk = 36
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}
sourceSets {
main.java.srcDirs += "src/main/kotlin"
test.java.srcDirs += "src/test/kotlin"
}
defaultConfig {
minSdk = 24
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}
dependencies {
implementation 'com.netzarchitekten:IPtProxy:4.2.2'
}
@@ -0,0 +1 @@
rootProject.name = 'pluggable_transports_proxy'
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.weblibre.pluggable_transports_proxy">
</manifest>
@@ -0,0 +1,34 @@
package eu.weblibre.pluggable_transports_proxy
import IPtProxy.Controller
import eu.weblibre.pluggable_transports_proxy.pigeons.IPtProxyController
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
import java.io.File
/** PluggableTransportsProxyPlugin */
class PluggableTransportsProxyPlugin : FlutterPlugin {
private lateinit var channel: MethodChannel
companion object {
@Volatile
private var INSTANCE: Controller? = null
fun getController(ptDirPath: String): Controller {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: Controller(ptDirPath, true, false, "INFO", null).also { INSTANCE = it }
}
}
}
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val ptDir = File(flutterPluginBinding.applicationContext.cacheDir, "pt_state")
val ptc = getController(ptDir.path)
IPtProxyController.setUp(flutterPluginBinding.binaryMessenger, ProxyImpl(controller = ptc))
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// Controller instance remains alive for reuse
}
}
@@ -0,0 +1,33 @@
package eu.weblibre.pluggable_transports_proxy
import eu.weblibre.pluggable_transports_proxy.pigeons.IPtProxyController
import eu.weblibre.pluggable_transports_proxy.pigeons.ProxyType
class ProxyImpl(val controller: IPtProxy.Controller) : IPtProxyController {
override fun start(
proxyType: ProxyType,
proxy: String
): Long {
val type = when (proxyType) {
ProxyType.OBFS4 -> IPtProxy.IPtProxy.Obfs4
ProxyType.MEEK_LITE -> IPtProxy.IPtProxy.MeekLite
ProxyType.WEBTUNNEL -> IPtProxy.IPtProxy.Webtunnel
ProxyType.SNOWFLAKE -> IPtProxy.IPtProxy.Snowflake
};
controller.start(type, proxy)
return controller.port(type)
}
override fun stop(proxyType: ProxyType) {
val type = when (proxyType) {
ProxyType.OBFS4 -> IPtProxy.IPtProxy.Obfs4
ProxyType.MEEK_LITE -> IPtProxy.IPtProxy.MeekLite
ProxyType.WEBTUNNEL -> IPtProxy.IPtProxy.Webtunnel
ProxyType.SNOWFLAKE -> IPtProxy.IPtProxy.Snowflake
};
controller.stop(type)
}
}
@@ -0,0 +1,137 @@
// Autogenerated from Pigeon (v26.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package eu.weblibre.pluggable_transports_proxy.pigeons
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object ProxyPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
enum class ProxyType(val raw: Int) {
OBFS4(0),
MEEK_LITE(1),
WEBTUNNEL(2),
SNOWFLAKE(3);
companion object {
fun ofRaw(raw: Int): ProxyType? {
return values().firstOrNull { it.raw == raw }
}
}
}
private open class ProxyPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let {
ProxyType.ofRaw(it.toInt())
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is ProxyType -> {
stream.write(129)
writeValue(stream, value.raw)
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface IPtProxyController {
fun start(proxyType: ProxyType, proxy: String): Long
fun stop(proxyType: ProxyType)
companion object {
/** The codec used by IPtProxyController. */
val codec: MessageCodec<Any?> by lazy {
ProxyPigeonCodec()
}
/** Sets up an instance of `IPtProxyController` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: IPtProxyController?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.start$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val proxyTypeArg = args[0] as ProxyType
val proxyArg = args[1] as String
val wrapped: List<Any?> = try {
listOf(api.start(proxyTypeArg, proxyArg))
} catch (exception: Throwable) {
ProxyPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.stop$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val proxyTypeArg = args[0] as ProxyType
val wrapped: List<Any?> = try {
api.stop(proxyTypeArg)
listOf(null)
} catch (exception: Throwable) {
ProxyPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -0,0 +1 @@
export 'src/pigeons/proxy.g.dart' show IPtProxyController, ProxyType;
@@ -0,0 +1,116 @@
// Autogenerated from Pigeon (v26.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';
PlatformException _createConnectionError(String channelName) {
return PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel: "$channelName".',
);
}
enum ProxyType {
obfs4,
meekLite,
webtunnel,
snowflake,
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is ProxyType) {
buffer.putUint8(129);
writeValue(buffer, value.index);
} else {
super.writeValue(buffer, value);
}
}
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 129:
final int? value = readValue(buffer) as int?;
return value == null ? null : ProxyType.values[value];
default:
return super.readValueOfType(type, buffer);
}
}
}
class IPtProxyController {
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
IPtProxyController({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
final String pigeonVar_messageChannelSuffix;
Future<int> start(ProxyType proxyType, String proxy) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.start$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType, proxy]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as int?)!;
}
}
Future<void> stop(ProxyType proxyType) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.stop$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
}
@@ -0,0 +1,21 @@
import 'package:pigeon/pigeon.dart';
enum ProxyType { obfs4, meekLite, webtunnel, snowflake }
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/src/pigeons/proxy.g.dart',
dartOptions: DartOptions(),
kotlinOut:
'android/src/main/kotlin/eu/weblibre/pluggable_transports_proxy/pigeons/Proxy.g.kt',
kotlinOptions: KotlinOptions(
package: 'eu.weblibre.pluggable_transports_proxy.pigeons',
),
dartPackageName: 'pluggable_transports_proxy',
),
)
@HostApi()
abstract class IPtProxyController {
int start(ProxyType proxyType, String proxy);
void stop(ProxyType proxyType);
}
@@ -0,0 +1,71 @@
name: pluggable_transports_proxy
description: "A new Flutter plugin project."
version: 0.0.1
publish_to: 'none'
resolution: workspace
environment:
sdk: ^3.9.0
flutter: '>=3.3.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
lint: ^2.8.0
pigeon: ^26.0.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: eu.weblibre.pluggable_transports_proxy
pluginClass: PluggableTransportsProxyPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package
@@ -1,4 +1,4 @@
// Autogenerated from Pigeon (v26.0.0), do not edit directly.
// Autogenerated from Pigeon (v26.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
@@ -1,4 +1,4 @@
// Autogenerated from Pigeon (v26.0.0), do not edit directly.
// Autogenerated from Pigeon (v26.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers