From 2fbb96f5c1b8a7255c188d9b92a42b03bb608730 Mon Sep 17 00:00:00 2001 From: chen08209 Date: Tue, 16 Dec 2025 11:23:09 +0800 Subject: [PATCH] Add sqlite store Optimize android quick action Optimize backup and restore Optimize more details --- .gitignore | 11 +- .metadata | 29 +- .run/main.dart.run.xml | 7 + Makefile | 10 - android/app/build.gradle.kts | 9 +- android/app/google-services.json | 19 + .../src/main/kotlin/com/follow/clash/Ext.kt | 31 +- .../kotlin/com/follow/clash/MainActivity.kt | 4 - .../main/kotlin/com/follow/clash/Service.kt | 74 +- .../src/main/kotlin/com/follow/clash/State.kt | 148 +- .../kotlin/com/follow/clash/models/State.kt | 15 +- .../com/follow/clash/plugins/AppPlugin.kt | 4 +- .../com/follow/clash/plugins/ServicePlugin.kt | 37 +- android/build.gradle.kts | 12 +- android/core/src/main/cpp/core.cpp | 28 +- .../main/java/com/follow/clash/core/Core.kt | 22 + .../clash/service/IRemoteInterface.aidl | 2 + .../follow/clash/service/IVoidInterface.aidl | 6 + .../com/follow/clash/service/RemoteService.kt | 30 + .../com/follow/clash/service/VpnService.kt | 2 +- .../follow/clash/service/models/VpnOptions.kt | 4 +- android/settings.gradle.kts | 15 +- arb/intl_en.arb | 34 +- arb/intl_ja.arb | 34 +- arb/intl_ru.arb | 26 +- arb/intl_zh_CN.arb | 34 +- build.yaml | 1 + core/Clash.Meta | 2 +- core/common.go | 28 +- core/constant.go | 5 + core/go.mod | 38 +- core/go.sum | 93 +- core/hub.go | 70 +- core/lib.go | 23 + lib/application.dart | 27 +- lib/common/archive.dart | 12 +- lib/common/common.dart | 6 + lib/common/compute.dart | 118 +- lib/common/constant.dart | 26 +- lib/common/context.dart | 2 +- lib/common/dav_client.dart | 27 +- lib/common/file.dart | 38 + lib/common/{state.dart => hive.dart} | 0 lib/common/http.dart | 6 +- lib/common/indexing.dart | 260 ++ lib/common/iterable.dart | 22 +- lib/common/migration.dart | 53 + lib/common/mixin.dart | 44 +- lib/common/navigator.dart | 6 +- lib/common/num.dart | 8 +- lib/common/path.dart | 45 +- lib/common/picker.dart | 28 +- lib/common/preferences.dart | 67 +- lib/common/print.dart | 10 +- lib/common/request.dart | 59 +- lib/common/snowflake.dart | 58 + lib/common/store.dart | 33 + lib/common/string.dart | 28 +- lib/common/system.dart | 3 +- lib/common/task.dart | 612 +++ lib/common/tray.dart | 45 +- lib/common/utils.dart | 26 +- lib/common/window.dart | 14 +- lib/controller.dart | 1835 +++++---- lib/core/controller.dart | 70 +- lib/core/interface.dart | 30 +- lib/core/lib.dart | 14 +- lib/core/service.dart | 35 +- lib/database/database.dart | 78 + lib/database/generated/database.g.dart | 2941 ++++++++++++++ lib/database/links.dart | 52 + lib/database/profiles.dart | 168 + lib/database/rules.dart | 283 ++ lib/database/scripts.dart | 63 + lib/enum/enum.dart | 13 +- lib/features/overwrite/rule.dart | 4 +- lib/l10n/intl/messages_en.dart | 70 +- lib/l10n/intl/messages_ja.dart | 48 +- lib/l10n/intl/messages_ru.dart | 80 +- lib/l10n/intl/messages_zh_CN.dart | 38 +- lib/l10n/l10n.dart | 251 +- lib/main.dart | 70 +- lib/manager/android_manager.dart | 12 +- lib/manager/app_manager.dart | 198 +- lib/manager/core_manager.dart | 29 +- lib/manager/hotkey_manager.dart | 77 +- lib/manager/status_manager.dart | 123 +- lib/manager/theme_manager.dart | 24 +- lib/manager/tile_manager.dart | 26 +- lib/manager/tray_manager.dart | 4 +- lib/manager/vpn_manager.dart | 3 +- lib/manager/window_manager.dart | 24 +- lib/models/app.dart | 5 - lib/models/clash_config.dart | 7 +- lib/models/common.dart | 82 +- lib/models/config.dart | 88 +- lib/models/core.dart | 18 +- lib/models/generated/app.freezed.dart | 98 +- .../generated/clash_config.freezed.dart | 49 +- lib/models/generated/clash_config.g.dart | 8 +- lib/models/generated/common.freezed.dart | 668 ++-- lib/models/generated/common.g.dart | 26 +- lib/models/generated/config.freezed.dart | 701 +--- lib/models/generated/config.g.dart | 105 +- lib/models/generated/core.freezed.dart | 383 +- lib/models/generated/core.g.dart | 16 +- lib/models/generated/profile.freezed.dart | 904 +---- lib/models/generated/profile.g.dart | 87 +- ...lector.freezed.dart => state.freezed.dart} | 3505 +++++++++++++++-- lib/models/generated/state.g.dart | 34 + lib/models/generated/widget.freezed.dart | 1661 -------- lib/models/models.dart | 3 +- lib/models/profile.dart | 216 +- lib/models/{selector.dart => state.dart} | 178 +- lib/models/widget.dart | 53 - lib/pages/editor.dart | 4 +- lib/pages/error.dart | 121 + lib/pages/home.dart | 93 +- lib/pages/pages.dart | 3 +- lib/pages/scan.dart | 4 +- lib/plugins/app.dart | 24 +- lib/plugins/service.dart | 30 +- lib/providers/app.dart | 482 +-- lib/providers/config.dart | 279 +- lib/providers/database.dart | 250 ++ lib/providers/generated/app.g.dart | 487 ++- lib/providers/generated/config.g.dart | 283 +- lib/providers/generated/database.g.dart | 448 +++ lib/providers/generated/state.g.dart | 634 ++- lib/providers/state.dart | 228 +- lib/state.dart | 593 +-- lib/views/about.dart | 10 +- lib/views/access.dart | 33 +- lib/views/application_setting.dart | 24 +- ..._recovery.dart => backup_and_restore.dart} | 207 +- lib/views/config/advanced.dart | 6 +- lib/views/config/dns.dart | 44 +- lib/views/config/general.dart | 64 +- lib/views/config/network.dart | 12 +- .../config/{added_rules.dart => rules.dart} | 64 +- lib/views/config/scripts.dart | 46 +- lib/views/connection/requests.dart | 5 +- lib/views/dashboard/dashboard.dart | 7 +- lib/views/dashboard/widgets/intranet_ip.dart | 4 +- .../dashboard/widgets/network_detection.dart | 203 +- .../dashboard/widgets/network_speed.dart | 77 +- .../dashboard/widgets/outbound_mode.dart | 71 +- .../dashboard/widgets/quick_options.dart | 6 +- lib/views/dashboard/widgets/start_button.dart | 17 +- lib/views/developer.dart | 26 +- lib/views/hotkey.dart | 128 +- lib/views/logs.dart | 20 +- lib/views/profiles/add.dart | 33 +- lib/views/profiles/edit.dart | 142 +- lib/views/profiles/override.dart | 875 ---- lib/views/profiles/overwrite.dart | 205 +- lib/views/profiles/profiles.dart | 310 +- lib/views/proxies/card.dart | 2 +- lib/views/proxies/common.dart | 22 +- lib/views/proxies/list.dart | 10 +- lib/views/proxies/providers.dart | 85 +- lib/views/proxies/proxies.dart | 4 +- lib/views/proxies/setting.dart | 50 +- lib/views/proxies/tab.dart | 32 +- lib/views/resources.dart | 29 +- lib/views/theme.dart | 20 +- lib/views/tools.dart | 21 +- lib/views/views.dart | 22 +- lib/widgets/builder.dart | 63 +- lib/widgets/button.dart | 56 + lib/widgets/card.dart | 2 +- lib/widgets/grid.dart | 88 +- lib/widgets/inherited.dart | 41 + lib/widgets/input.dart | 39 +- lib/widgets/list.dart | 38 +- lib/widgets/pop_scope.dart | 6 +- lib/widgets/scaffold.dart | 81 +- lib/widgets/scroll.dart | 131 - lib/widgets/sheet.dart | 8 +- lib/widgets/widgets.dart | 2 + linux/CMakeLists.txt | 40 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugin_registrant.h | 0 linux/flutter/generated_plugins.cmake | 1 + linux/runner/CMakeLists.txt | 26 + linux/{ => runner}/main.cc | 0 linux/{ => runner}/my_application.cc | 17 +- linux/{ => runner}/my_application.h | 0 macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Podfile | 5 +- macos/Podfile.lock | 37 +- macos/Runner.xcodeproj/project.pbxproj | 196 +- .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 macos/Runner/Base.lproj/MainMenu.xib | 10 +- macos/Runner/Configs/Debug.xcconfig | 0 macos/Runner/Configs/Release.xcconfig | 0 macos/Runner/Configs/Warnings.xcconfig | 0 macos/RunnerTests/RunnerTests.swift | 2 +- pubspec.lock | 76 +- pubspec.yaml | 12 +- setup.dart | 25 +- windows/.gitignore | 7 - windows/CMakeLists.txt | 16 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugin_registrant.h | 0 windows/flutter/generated_plugins.cmake | 1 + windows/runner/CMakeLists.txt | 3 +- windows/runner/Runner.rc | 2 +- windows/runner/runner.exe.manifest | 6 - windows/runner/utils.cpp | 4 +- windows/runner/win32_window.cpp | 290 +- windows/runner/win32_window.h | 23 +- 214 files changed, 15659 insertions(+), 10751 deletions(-) create mode 100644 .run/main.dart.run.xml delete mode 100644 Makefile create mode 100644 android/service/src/main/aidl/com/follow/clash/service/IVoidInterface.aidl create mode 100644 lib/common/file.dart rename lib/common/{state.dart => hive.dart} (100%) create mode 100644 lib/common/indexing.dart create mode 100644 lib/common/migration.dart create mode 100644 lib/common/snowflake.dart create mode 100644 lib/common/store.dart create mode 100644 lib/common/task.dart create mode 100644 lib/database/database.dart create mode 100644 lib/database/generated/database.g.dart create mode 100644 lib/database/links.dart create mode 100644 lib/database/profiles.dart create mode 100644 lib/database/rules.dart create mode 100644 lib/database/scripts.dart rename lib/models/generated/{selector.freezed.dart => state.freezed.dart} (63%) create mode 100644 lib/models/generated/state.g.dart delete mode 100644 lib/models/generated/widget.freezed.dart rename lib/models/{selector.dart => state.dart} (63%) delete mode 100644 lib/models/widget.dart create mode 100644 lib/pages/error.dart create mode 100644 lib/providers/database.dart create mode 100644 lib/providers/generated/database.g.dart rename lib/views/{backup_and_recovery.dart => backup_and_restore.dart} (66%) rename lib/views/config/{added_rules.dart => rules.dart} (68%) delete mode 100644 lib/views/profiles/override.dart create mode 100644 lib/widgets/button.dart create mode 100644 lib/widgets/inherited.dart mode change 100755 => 100644 linux/flutter/generated_plugin_registrant.cc mode change 100755 => 100644 linux/flutter/generated_plugin_registrant.h mode change 100755 => 100644 linux/flutter/generated_plugins.cmake create mode 100644 linux/runner/CMakeLists.txt rename linux/{ => runner}/main.cc (100%) rename linux/{ => runner}/my_application.cc (86%) rename linux/{ => runner}/my_application.h (100%) mode change 100755 => 100644 macos/Flutter/GeneratedPluginRegistrant.swift mode change 100755 => 100644 macos/Podfile mode change 100755 => 100644 macos/Podfile.lock mode change 100755 => 100644 macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist mode change 100755 => 100644 macos/Runner.xcworkspace/contents.xcworkspacedata mode change 100755 => 100644 macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist mode change 100755 => 100644 macos/Runner/Base.lproj/MainMenu.xib mode change 100755 => 100644 macos/Runner/Configs/Debug.xcconfig mode change 100755 => 100644 macos/Runner/Configs/Release.xcconfig mode change 100755 => 100644 macos/Runner/Configs/Warnings.xcconfig mode change 100755 => 100644 windows/flutter/generated_plugin_registrant.cc mode change 100755 => 100644 windows/flutter/generated_plugin_registrant.h mode change 100755 => 100644 windows/flutter/generated_plugins.cmake diff --git a/.gitignore b/.gitignore index b568df4..d8a14f5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ migrate_working_dir/ # 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/ +.vscode/ # Flutter/Dart/Pub related **/doc/api/ @@ -41,6 +41,11 @@ app.*.symbols # Obfuscation related app.*.map.json +#AI generated +CLAUDE.md +/.claude + + # Android Studio will place build artifacts here /android/app/debug /android/app/profile @@ -53,7 +58,6 @@ app.*.map.json /android/core/**/cmake-build-*/ /android/core/**/jniLibs/ - #FlClash /libclash/ /android/app/src/main/jniLibs/ @@ -61,3 +65,6 @@ app.*.map.json /macos/**/Package.resolved devtools_options.yaml +# FVM Version Cache +.fvm/ +.fvmrc \ No newline at end of file diff --git a/.metadata b/.metadata index 30d90a7..2e80cfe 100644 --- a/.metadata +++ b/.metadata @@ -1,11 +1,11 @@ # 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. +# This file should be version controlled and should not be manually edited. version: - revision: 796c8ef79279f9c774545b3771238c3098dbefab - channel: stable + revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" + channel: "stable" project_type: app @@ -13,26 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: android - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: ios - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: linux - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: macos - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: web - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: windows - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 # User provided section diff --git a/.run/main.dart.run.xml b/.run/main.dart.run.xml new file mode 100644 index 0000000..7a9c9da --- /dev/null +++ b/.run/main.dart.run.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 627966f..0000000 --- a/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -android_arm64: - dart ./setup.dart android --arch arm64 -macos_arm64: - dart ./setup.dart macos --arch arm64 -android_app: - dart ./setup.dart android -android_arm64_core: - dart ./setup.dart android --arch arm64 --out core -macos_arm64_core: - dart ./setup.dart macos --arch arm64 --out core \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 39e11a5..4cef3e8 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -64,16 +64,17 @@ android { buildTypes { debug { isMinifyEnabled = false - applicationIdSuffix = ".debug" + applicationIdSuffix = ".dev" } release { isMinifyEnabled = true isShrinkResources = true - signingConfig = if (isRelease) { - signingConfigs.getByName("release") + if (isRelease) { + signingConfig = signingConfigs.getByName("release") } else { - signingConfigs.getByName("debug") + signingConfig = signingConfigs.getByName("debug") + applicationIdSuffix = ".dev" } proguardFiles( diff --git a/android/app/google-services.json b/android/app/google-services.json index 63581b3..ec7efbe 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -41,6 +41,25 @@ "other_platform_oauth_client": [] } } + }, + { + "client_info": { + "mobilesdk_app_id": "1:000000000000:android:0000000000000000", + "android_client_info": { + "package_name": "com.follow.clash.dev" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "0" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } } ] } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/follow/clash/Ext.kt b/android/app/src/main/kotlin/com/follow/clash/Ext.kt index b6d6bcd..7cabf5b 100644 --- a/android/app/src/main/kotlin/com/follow/clash/Ext.kt +++ b/android/app/src/main/kotlin/com/follow/clash/Ext.kt @@ -1,13 +1,18 @@ package com.follow.clash +import android.app.Application +import android.content.Context.MODE_PRIVATE import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.os.Build import android.os.Handler import android.os.Looper +import android.widget.Toast import androidx.core.graphics.drawable.toBitmap import com.follow.clash.common.GlobalState +import com.follow.clash.models.SharedState +import com.google.gson.Gson import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodChannel @@ -21,6 +26,30 @@ import kotlin.coroutines.resume private const val ICON_TTL_DAYS = 1L +val Application.sharedState: SharedState + get() { + try { + val sp = getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE) + val res = sp.getString("flutter.sharedState", "") + return Gson().fromJson(res, SharedState::class.java) + } catch (_: Exception) { + return SharedState() + } + } + + +private var lastToast: Toast? = null + +fun Application.showToast(text: String?) { + Handler(Looper.getMainLooper()).post { + lastToast?.cancel() + lastToast = Toast.makeText(this, text, Toast.LENGTH_LONG).apply { + show() + } + } + +} + suspend fun PackageManager.getPackageIconPath(packageName: String): String = withContext(Dispatchers.IO) { val cacheDir = GlobalState.application.cacheDir @@ -118,4 +147,4 @@ fun MethodChannel.invokeMethodOnMainThread( } }) } -} \ No newline at end of file +} diff --git a/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt b/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt index 8c841ae..c5760d2 100644 --- a/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt +++ b/android/app/src/main/kotlin/com/follow/clash/MainActivity.kt @@ -1,7 +1,6 @@ package com.follow.clash import android.os.Bundle -import androidx.lifecycle.lifecycleScope import com.follow.clash.common.GlobalState import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.ServicePlugin @@ -18,9 +17,6 @@ class MainActivity : FlutterActivity(), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - lifecycleScope.launch { - State.destroyServiceEngine() - } } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { diff --git a/android/app/src/main/kotlin/com/follow/clash/Service.kt b/android/app/src/main/kotlin/com/follow/clash/Service.kt index 758818a..9385b55 100644 --- a/android/app/src/main/kotlin/com/follow/clash/Service.kt +++ b/android/app/src/main/kotlin/com/follow/clash/Service.kt @@ -1,5 +1,6 @@ package com.follow.clash +import com.follow.clash.common.GlobalState import com.follow.clash.common.ServiceDelegate import com.follow.clash.common.formatString import com.follow.clash.common.intent @@ -8,6 +9,7 @@ import com.follow.clash.service.ICallbackInterface import com.follow.clash.service.IEventInterface import com.follow.clash.service.IRemoteInterface import com.follow.clash.service.IResultInterface +import com.follow.clash.service.IVoidInterface import com.follow.clash.service.RemoteService import com.follow.clash.service.models.NotificationParams import com.follow.clash.service.models.VpnOptions @@ -40,7 +42,7 @@ object Service { delegate.unbind() } - suspend fun invokeAction(data: String, cb: (result: String) -> Unit): Result { + suspend fun invokeAction(data: String, cb: ((result: String) -> Unit)?): Result { val res = mutableListOf() return delegate.useService { it.invokeAction( @@ -51,13 +53,50 @@ object Service { res.add(result ?: byteArrayOf()) ack?.onAck() if (isSuccess) { - cb(res.formatString()) + cb?.let { cb -> + cb(res.formatString()) + } } } }) } } + suspend fun quickSetup( + initParamsString: String, + setupParamsString: String, + onStarted: (() -> Unit)?, + onResult: ((result: String) -> Unit)?, + ): Result { + val res = mutableListOf() + return delegate.useService { + it.quickSetup( + initParamsString, + setupParamsString, + object : ICallbackInterface.Stub() { + override fun onResult( + result: ByteArray?, isSuccess: Boolean, ack: IAckInterface? + ) { + res.add(result ?: byteArrayOf()) + ack?.onAck() + if (isSuccess) { + onResult?.let { cb -> + cb(res.formatString()) + } + } + } + }, + object : IVoidInterface.Stub() { + override fun invoke() { + onStarted?.let { onStarted -> + onStarted() + } + } + } + ) + } + } + suspend fun setEventListener( cb: ((result: String?) -> Unit)? ): Result { @@ -65,24 +104,24 @@ object Service { return delegate.useService { it.setEventListener( when (cb != null) { - true -> object : IEventInterface.Stub() { - override fun onEvent( - id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface? - ) { - if (results[id] == null) { - results[id] = mutableListOf() - } - results[id]?.add(data ?: byteArrayOf()) - ack?.onAck() - if (isSuccess) { - cb(results[id]?.formatString()) - results.remove(id) + true -> object : IEventInterface.Stub() { + override fun onEvent( + id: String, data: ByteArray?, isSuccess: Boolean, ack: IAckInterface? + ) { + if (results[id] == null) { + results[id] = mutableListOf() + } + results[id]?.add(data ?: byteArrayOf()) + ack?.onAck() + if (isSuccess) { + cb(results[id]?.formatString()) + results.remove(id) + } } } - } - false -> null - }) + false -> null + }) } } @@ -116,6 +155,7 @@ object Service { try { block(callback) } catch (e: Exception) { + GlobalState.log("awaitIResultInterface $e") if (continuation.isActive) { continuation.resumeWithException(e) } diff --git a/android/app/src/main/kotlin/com/follow/clash/State.kt b/android/app/src/main/kotlin/com/follow/clash/State.kt index cb5be3e..f3e1b47 100644 --- a/android/app/src/main/kotlin/com/follow/clash/State.kt +++ b/android/app/src/main/kotlin/com/follow/clash/State.kt @@ -1,18 +1,17 @@ package com.follow.clash +import android.net.VpnService import com.follow.clash.common.GlobalState +import com.follow.clash.models.SharedState import com.follow.clash.plugins.AppPlugin -import com.follow.clash.plugins.ServicePlugin import com.follow.clash.plugins.TilePlugin -import io.flutter.FlutterInjector +import com.follow.clash.service.models.NotificationParams +import com.google.gson.Gson import io.flutter.embedding.engine.FlutterEngine -import io.flutter.embedding.engine.dart.DartExecutor -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext enum class RunState { START, PENDING, STOP @@ -25,20 +24,17 @@ object State { var runTime: Long = 0 + var sharedState: SharedState = SharedState() + val runStateFlow: MutableStateFlow = MutableStateFlow(RunState.STOP) var flutterEngine: FlutterEngine? = null - var serviceFlutterEngine: FlutterEngine? = null val appPlugin: AppPlugin? - get() = flutterEngine?.plugin() ?: serviceFlutterEngine?.plugin() - - val servicePlugin: ServicePlugin? - get() = flutterEngine?.plugin() - ?: serviceFlutterEngine?.plugin() + get() = flutterEngine?.plugin() val tilePlugin: TilePlugin? - get() = flutterEngine?.plugin() ?: serviceFlutterEngine?.plugin() + get() = flutterEngine?.plugin() suspend fun handleToggleAction() { var action: (suspend () -> Unit)? @@ -77,7 +73,7 @@ object State { if (flutterEngine != null) { return } - startServiceWithEngine() + startServiceWithPref() } } @@ -88,9 +84,10 @@ object State { return } tilePlugin?.handleStop() - if (flutterEngine != null || serviceFlutterEngine != null) { + if (flutterEngine != null) { return } + GlobalState.application.showToast(sharedState.stopTip) handleStopService() } } @@ -106,72 +103,101 @@ object State { startService() } - fun handleStopService() { - GlobalState.launch { - runLock.withLock { - if (runStateFlow.value != RunState.START) { - return@launch - } - runStateFlow.tryEmit(RunState.PENDING) - runTime = Service.stopService() - runStateFlow.tryEmit(RunState.STOP) - } - destroyServiceEngine() - } - } - - suspend fun destroyServiceEngine() { - runLock.withLock { - GlobalState.log("Destroy service engine") - withContext(Dispatchers.Main) { - runCatching { - serviceFlutterEngine?.destroy() - serviceFlutterEngine = null - } - } - } - } - - private fun startServiceWithEngine() { + private fun startServiceWithPref() { GlobalState.launch { runLock.withLock { if (runStateFlow.value != RunState.STOP) { return@launch } - GlobalState.log("Create service engine") - withContext(Dispatchers.Main) { - serviceFlutterEngine?.destroy() - serviceFlutterEngine = FlutterEngine(GlobalState.application) - serviceFlutterEngine?.plugins?.add(ServicePlugin()) - serviceFlutterEngine?.plugins?.add(AppPlugin()) - serviceFlutterEngine?.plugins?.add(TilePlugin()) - val dartEntrypoint = DartExecutor.DartEntrypoint( - FlutterInjector.instance().flutterLoader().findAppBundlePath(), "_service" - ) - serviceFlutterEngine?.dartExecutor?.executeDartEntrypoint(dartEntrypoint) - } + sharedState = GlobalState.application.sharedState + setupAndStart() } } } + suspend fun syncState() { + GlobalState.setCrashlytics(sharedState.crashlytics) + Service.updateNotificationParams( + NotificationParams( + title = sharedState.currentProfileName, + stopText = sharedState.stopText, + onlyStatisticsProxy = sharedState.onlyStatisticsProxy + ) + ) + Service.setCrashlytics(sharedState.crashlytics) + } + + private suspend fun setupAndStart() { + Service.bind() + syncState() + GlobalState.application.showToast(sharedState.startTip) + val initParams = mutableMapOf() + initParams["home-dir"] = GlobalState.application.filesDir.path + initParams["version"] = android.os.Build.VERSION.SDK_INT + val initParamsString = Gson().toJson(initParams) + val setupParamsString = Gson().toJson(sharedState.setupParams) + Service.quickSetup( + initParamsString, + setupParamsString, + onStarted = { + startService() + }, + onResult = { + if (it.isNotEmpty()) { + GlobalState.application.showToast(it) + } + }, + ) + } + private fun startService() { GlobalState.launch { runLock.withLock { if (runStateFlow.value != RunState.STOP) { return@launch } - runStateFlow.tryEmit(RunState.PENDING) - if (servicePlugin == null) { - return@launch - } - val options = servicePlugin?.handleGetVpnOptions() ?: return@launch - appPlugin?.prepare(options.enable) { - runTime = Service.startService(options, runTime) - runStateFlow.tryEmit(RunState.START) + try { + runStateFlow.tryEmit(RunState.PENDING) + val options = sharedState.vpnOptions ?: return@launch + appPlugin?.let { + it.prepare(options.enable) { + runTime = Service.startService(options, runTime) + runStateFlow.tryEmit(RunState.START) + } + } ?: run { + val intent = VpnService.prepare(GlobalState.application) + if (intent != null) { + return@launch + } + runTime = Service.startService(options, runTime) + runStateFlow.tryEmit(RunState.START) + } + } finally { + if (runStateFlow.value == RunState.PENDING) { + runStateFlow.tryEmit(RunState.STOP) + } } } } + } + fun handleStopService() { + GlobalState.launch { + runLock.withLock { + if (runStateFlow.value != RunState.START) { + return@launch + } + try { + runStateFlow.tryEmit(RunState.PENDING) + runTime = Service.stopService() + runStateFlow.tryEmit(RunState.STOP) + } finally { + if (runStateFlow.value == RunState.PENDING) { + runStateFlow.tryEmit(RunState.START) + } + } + } + } } } diff --git a/android/app/src/main/kotlin/com/follow/clash/models/State.kt b/android/app/src/main/kotlin/com/follow/clash/models/State.kt index 0ca5175..35e461b 100644 --- a/android/app/src/main/kotlin/com/follow/clash/models/State.kt +++ b/android/app/src/main/kotlin/com/follow/clash/models/State.kt @@ -1,9 +1,22 @@ package com.follow.clash.models +import com.follow.clash.service.models.VpnOptions +import com.google.gson.annotations.SerializedName -data class AppState( +data class SharedState( + val startTip: String = "Starting VPN...", + val stopTip: String = "Stopping VPN...", val crashlytics: Boolean = true, val currentProfileName: String = "FlClash", val stopText: String = "Stop", val onlyStatisticsProxy: Boolean = false, + val vpnOptions: VpnOptions? = null, + val setupParams: SetupParams? = null, +) + +data class SetupParams( + @SerializedName("test-url") + val testUrl: String, + @SerializedName("selected-map") + val selectedMap: Map, ) diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt index 730499e..cf69752 100644 --- a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt +++ b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt @@ -9,7 +9,6 @@ import android.content.pm.ComponentInfo import android.content.pm.PackageManager import android.net.VpnService import android.os.Build -import android.widget.Toast import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getSystemService @@ -24,6 +23,7 @@ import com.follow.clash.common.QuickAction import com.follow.clash.common.quickIntent import com.follow.clash.getPackageIconPath import com.follow.clash.models.Package +import com.follow.clash.showToast import com.google.gson.Gson import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.plugins.FlutterPlugin @@ -193,7 +193,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware } private fun tip(message: String?) { - Toast.makeText(GlobalState.application, message, Toast.LENGTH_LONG).show() + GlobalState.application.showToast(message) } @Suppress("DEPRECATION") diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt index 2b0d1ff..00d8bd3 100644 --- a/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt +++ b/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt @@ -3,13 +3,9 @@ package com.follow.clash.plugins import com.follow.clash.RunState import com.follow.clash.Service import com.follow.clash.State -import com.follow.clash.awaitResult import com.follow.clash.common.Components -import com.follow.clash.common.GlobalState import com.follow.clash.invokeMethodOnMainThread -import com.follow.clash.models.AppState -import com.follow.clash.service.models.NotificationParams -import com.follow.clash.service.models.VpnOptions +import com.follow.clash.models.SharedState import com.google.gson.Gson import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall @@ -38,7 +34,7 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) = when (call.method) { "init" -> { - handleInit(call, result) + handleInit(result) } "shutdown" -> { @@ -94,11 +90,6 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, result.success(true) } - suspend fun handleGetVpnOptions(): VpnOptions? { - val res = flutterMethodChannel.awaitResult("getVpnOptions", null) - return Gson().fromJson(res, VpnOptions::class.java) - } - val semaphore = Semaphore(10) fun handleSendEvent(value: String?) { @@ -116,31 +107,19 @@ class ServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, private fun handleSyncState(call: MethodCall, result: MethodChannel.Result) { val data = call.arguments()!! - val params = Gson().fromJson(data, AppState::class.java) - GlobalState.setCrashlytics(params.crashlytics) + State.sharedState = Gson().fromJson(data, SharedState::class.java) launch { - Service.updateNotificationParams( - NotificationParams( - title = params.currentProfileName, - stopText = params.stopText, - onlyStatisticsProxy = params.onlyStatisticsProxy - ) - ) - Service.setCrashlytics(params.crashlytics) + State.syncState() result.success("") } } - fun handleInit(call: MethodCall, result: MethodChannel.Result) { + + fun handleInit(result: MethodChannel.Result) { Service.bind() launch { - val needSetEventListener = call.arguments() ?: false - when (needSetEventListener) { - true -> Service.setEventListener { - handleSendEvent(it) - } - - false -> Service.setEventListener(null) + Service.setEventListener { + handleSendEvent(it) }.onSuccess { result.success("") }.onFailure { diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 8f89645..dbee657 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,19 +1,10 @@ -buildscript { - dependencies { - classpath(libs.build.kotlin) - } -} - -plugins { - id("com.android.library") apply false -} - allprojects { repositories { google() mavenCentral() } } + val newBuildDir: Directory = rootProject.layout.buildDirectory .dir("../../build") @@ -31,4 +22,3 @@ subprojects { tasks.register("clean") { delete(rootProject.layout.buildDirectory) } - diff --git a/android/core/src/main/cpp/core.cpp b/android/core/src/main/cpp/core.cpp index 5c1251c..878d20c 100644 --- a/android/core/src/main/cpp/core.cpp +++ b/android/core/src/main/cpp/core.cpp @@ -70,6 +70,14 @@ Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean su suspend(suspended); } +extern "C" +JNIEXPORT void JNICALL +Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string, + jstring setup_params_string, jobject cb) { + const auto interface = new_global(cb); + quickSetup(interface, get_string(init_params_string), get_string(setup_params_string)); +} + static jmethodID m_tun_interface_protect; static jmethodID m_tun_interface_resolve_process; @@ -99,12 +107,12 @@ call_tun_interface_resolve_process_impl(void *tun_interface, const int protocol, const int uid) { ATTACH_JNI(); const auto packageName = reinterpret_cast(env->CallObjectMethod( - static_cast(tun_interface), - m_tun_interface_resolve_process, - protocol, - new_string(source), - new_string(target), - uid)); + static_cast(tun_interface), + m_tun_interface_resolve_process, + protocol, + new_string(source), + new_string(target), + uid)); return get_string(packageName); } @@ -191,4 +199,10 @@ extern "C" JNIEXPORT void JNICALL Java_com_follow_clash_core_Core_suspended(JNIEnv *env, jobject thiz, jboolean suspended) { } -#endif + +extern "C" +JNIEXPORT void JNICALL +Java_com_follow_clash_core_Core_quickSetup(JNIEnv *env, jobject thiz, jstring init_params_string, + jstring setup_params_string, jobject cb) { +} +#endif \ No newline at end of file diff --git a/android/core/src/main/java/com/follow/clash/core/Core.kt b/android/core/src/main/java/com/follow/clash/core/Core.kt index 830f080..bbc4e70 100644 --- a/android/core/src/main/java/com/follow/clash/core/Core.kt +++ b/android/core/src/main/java/com/follow/clash/core/Core.kt @@ -102,6 +102,28 @@ data object Core { } } + fun quickSetup( + initParamsString: String, + setupParamsString: String, + cb: (result: String?) -> Unit, + ) { + quickSetup( + initParamsString, + setupParamsString, + object : InvokeInterface { + override fun onResult(result: String?) { + cb(result) + } + }, + ) + } + + private external fun quickSetup( + initParamsString: String, + setupParamsString: String, + cb: InvokeInterface + ) + external fun stopTun() external fun getTraffic(onlyStatisticsProxy: Boolean): String diff --git a/android/service/src/main/aidl/com/follow/clash/service/IRemoteInterface.aidl b/android/service/src/main/aidl/com/follow/clash/service/IRemoteInterface.aidl index a9cf775..4eac0b7 100644 --- a/android/service/src/main/aidl/com/follow/clash/service/IRemoteInterface.aidl +++ b/android/service/src/main/aidl/com/follow/clash/service/IRemoteInterface.aidl @@ -4,11 +4,13 @@ package com.follow.clash.service; import com.follow.clash.service.ICallbackInterface; import com.follow.clash.service.IEventInterface; import com.follow.clash.service.IResultInterface; +import com.follow.clash.service.IVoidInterface; import com.follow.clash.service.models.VpnOptions; import com.follow.clash.service.models.NotificationParams; interface IRemoteInterface { void invokeAction(in String data, in ICallbackInterface callback); + void quickSetup(in String initParamsString, in String setupParamsString, in ICallbackInterface callback, in IVoidInterface onStarted); void updateNotificationParams(in NotificationParams params); void startService(in VpnOptions options, in long runTime, in IResultInterface result); void stopService(in IResultInterface result); diff --git a/android/service/src/main/aidl/com/follow/clash/service/IVoidInterface.aidl b/android/service/src/main/aidl/com/follow/clash/service/IVoidInterface.aidl new file mode 100644 index 0000000..28db4c3 --- /dev/null +++ b/android/service/src/main/aidl/com/follow/clash/service/IVoidInterface.aidl @@ -0,0 +1,6 @@ +// IVoidInterface.aidl +package com.follow.clash.service; + +interface IVoidInterface { + oneway void invoke(); +} \ No newline at end of file diff --git a/android/service/src/main/java/com/follow/clash/service/RemoteService.kt b/android/service/src/main/java/com/follow/clash/service/RemoteService.kt index 5fbb6e1..54e608a 100644 --- a/android/service/src/main/java/com/follow/clash/service/RemoteService.kt +++ b/android/service/src/main/java/com/follow/clash/service/RemoteService.kt @@ -98,6 +98,35 @@ class RemoteService : Service(), } } + override fun quickSetup( + initParamsString: String, + setupParamsString: String, + callback: ICallbackInterface, + onStarted: IVoidInterface + ) { + Core.quickSetup(initParamsString, setupParamsString) { + launch { + runCatching { + val chunks = it?.chunkedForAidl() ?: listOf() + for ((index, chunk) in chunks.withIndex()) { + suspendCancellableCoroutine { cont -> + callback.onResult( + chunk, + index == chunks.lastIndex, + object : IAckInterface.Stub() { + override fun onAck() { + cont.resume(Unit) + } + }, + ) + } + } + } + } + } + onStarted() + } + override fun updateNotificationParams(params: NotificationParams?) { State.notificationParamsFlow.tryEmit(params) } @@ -108,6 +137,7 @@ class RemoteService : Service(), runtime: Long, result: IResultInterface, ) { + GlobalState.log("remote startService") State.options = options handleStartService(runtime, result) } diff --git a/android/service/src/main/java/com/follow/clash/service/VpnService.kt b/android/service/src/main/java/com/follow/clash/service/VpnService.kt index 883064a..f576702 100644 --- a/android/service/src/main/java/com/follow/clash/service/VpnService.kt +++ b/android/service/src/main/java/com/follow/clash/service/VpnService.kt @@ -187,7 +187,7 @@ class VpnService : SystemVpnService(), IBaseService, addDnsServer(DNS6) } setMtu(9000) - options.accessControl.let { accessControl -> + options.accessControlProps.let { accessControl -> if (accessControl.enable) { when (accessControl.mode) { AccessControlMode.ACCEPT_SELECTED -> { diff --git a/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt b/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt index 7d18890..0e2b4ac 100644 --- a/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt +++ b/android/service/src/main/java/com/follow/clash/service/models/VpnOptions.kt @@ -6,7 +6,7 @@ import kotlinx.parcelize.Parcelize import java.net.InetAddress @Parcelize -data class AccessControl( +data class AccessControlProps( val enable: Boolean, val mode: AccessControlMode, val acceptList: List, @@ -19,7 +19,7 @@ data class VpnOptions( val port: Int, val ipv6: Boolean, val dnsHijacking: Boolean, - val accessControl: AccessControl, + val accessControlProps: AccessControlProps, val allowBypass: Boolean, val systemProxy: Boolean, val bypassDomain: List, diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 1fc9b74..dd51c86 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -1,11 +1,12 @@ pluginManagement { - val flutterSdkPath = run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") diff --git a/arb/intl_en.arb b/arb/intl_en.arb index 8c93cdf..84dbe2d 100644 --- a/arb/intl_en.arb +++ b/arb/intl_en.arb @@ -129,14 +129,8 @@ "compatibleDesc": "Opening it will lose part of its application ability and gain the support of full amount of Clash.", "notSelectedTip": "The current proxy group cannot be selected.", "tip": "tip", - "backupAndRecovery": "Backup and Recovery", - "backupAndRecoveryDesc": "Sync data via WebDAV or file", "account": "Account", "backup": "Backup", - "recovery": "Recovery", - "recoveryProfiles": "Only recovery profiles", - "recoveryAll": "Recovery all data", - "recoverySuccess": "Recovery success", "backupSuccess": "Backup success", "noInfo": "No info", "pleaseBindWebDAV": "Please bind WebDAV", @@ -219,9 +213,7 @@ "local": "Local", "remote": "Remote", "remoteBackupDesc": "Backup local data to WebDAV", - "remoteRecoveryDesc": "Recovery data from WebDAV", "localBackupDesc": "Backup local data to local", - "localRecoveryDesc": "Recovery data from file", "mode": "Mode", "time": "Time", "source": "Source", @@ -381,9 +373,9 @@ "systemApp": "System APP", "noNetworkApp": "No network APP", "contactMe": "Contact me", - "recoveryStrategy": "Recovery strategy", - "recoveryStrategy_override": "Override", - "recoveryStrategy_compatible": "Compatible", + "restoreStrategy": "Restore strategy", + "restoreStrategy_override": "Override", + "restoreStrategy_compatible": "Compatible", "logsTest": "Logs test", "emptyTip": "{label} cannot be empty", "urlTip": "{label} must be a url", @@ -466,5 +458,23 @@ "vpnConfigChangeDetected": "VPN configuration change detected", "restart": "Restart", "speedStatistics": "Speed statistics", - "resetPageChangesTip": "The current page has changes. Are you sure you want to reset?" + "resetPageChangesTip": "The current page has changes. Are you sure you want to reset?", + "overwriteTypeCustom": "Custom", + "overwriteTypeCustomDesc": "Custom mode, fully customize proxy groups and rules", + "unknownNetworkError": "Unknown network error", + "networkRequestException": "Network request exception, please try again later.", + "restoreException": "Recovery exception", + "networkException": "Network exception, please check your connection and try again", + "invalidBackupFile": "Invalid backup file", + "pruneCache": "Prune cache", + "backupAndRestore": "Backup and Restore", + "backupAndRestoreDesc": "Sync data via WebDAV or files", + "restore": "Restore", + "restoreSuccess": "Restore success", + "restoreFromWebDAVDesc": "Restore data via WebDAV", + "restoreFromFileDesc": "Restore data via file", + "restoreOnlyConfig": "Restore configuration files only", + "restoreAllData": "Restore all data", + "addProfile": "Add Profile", + "delayTest": "Delay Test" } \ No newline at end of file diff --git a/arb/intl_ja.arb b/arb/intl_ja.arb index f9b7ff4..7df092b 100644 --- a/arb/intl_ja.arb +++ b/arb/intl_ja.arb @@ -129,14 +129,8 @@ "compatibleDesc": "有効化すると一部機能を失いますが、Clashの完全サポートを獲得", "notSelectedTip": "現在のプロキシグループは選択できません", "tip": "ヒント", - "backupAndRecovery": "バックアップと復元", - "backupAndRecoveryDesc": "WebDAVまたはファイルでデータを同期", "account": "アカウント", "backup": "バックアップ", - "recovery": "復元", - "recoveryProfiles": "プロファイルのみ復元", - "recoveryAll": "全データ復元", - "recoverySuccess": "復元成功", "backupSuccess": "バックアップ成功", "noInfo": "情報なし", "pleaseBindWebDAV": "WebDAVをバインドしてください", @@ -219,9 +213,7 @@ "local": "ローカル", "remote": "リモート", "remoteBackupDesc": "WebDAVにデータをバックアップ", - "remoteRecoveryDesc": "WebDAVからデータを復元", "localBackupDesc": "ローカルにデータをバックアップ", - "localRecoveryDesc": "ファイルからデータを復元", "mode": "モード", "time": "時間", "source": "ソース", @@ -382,9 +374,9 @@ "systemApp": "システムアプリ", "noNetworkApp": "ネットワークなしアプリ", "contactMe": "連絡する", - "recoveryStrategy": "リカバリー戦略", - "recoveryStrategy_override": "オーバーライド", - "recoveryStrategy_compatible": "互換性", + "restoreStrategy": "復元ストラテジー", + "restoreStrategy_override": "上書き", + "restoreStrategy_compatible": "互換", "logsTest": "ログテスト", "emptyTip": "{label}は空欄にできません", "urlTip": "{label}はURLである必要があります", @@ -467,5 +459,23 @@ "vpnConfigChangeDetected": "VPN設定の変更が検出されました", "restart": "再起動", "speedStatistics": "速度統計", - "resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?" + "resetPageChangesTip": "現在のページに変更があります。リセットしてもよろしいですか?", + "overwriteTypeCustom": "カスタム", + "overwriteTypeCustomDesc": "カスタムモード、プロキシグループとルールを完全にカスタマイズ可能", + "unknownNetworkError": "不明なネットワークエラー", + "networkRequestException": "ネットワーク要求例外、後でもう一度試してください。", + "restoreException": "復元例外", + "networkException": "ネットワーク例外、接続を確認してもう一度お試しください", + "invalidBackupFile": "無効なバックアップファイル", + "pruneCache": "キャッシュの削除", + "backupAndRestore": "バックアップと復元", + "backupAndRestoreDesc": "WebDAVまたはファイルを介してデータを同期する", + "restore": "復元", + "restoreSuccess": "復元に成功しました", + "restoreFromWebDAVDesc": "WebDAVを介してデータを復元する", + "restoreFromFileDesc": "ファイルを介してデータを復元する", + "restoreOnlyConfig": "設定ファイルのみを復元する", + "restoreAllData": "すべてのデータを復元する", + "addProfile": "プロファイルを追加", + "delayTest": "遅延テスト" } \ No newline at end of file diff --git a/arb/intl_ru.arb b/arb/intl_ru.arb index 433ed57..ff62742 100644 --- a/arb/intl_ru.arb +++ b/arb/intl_ru.arb @@ -382,9 +382,9 @@ "systemApp": "Системное приложение", "noNetworkApp": "Приложение без сети", "contactMe": "Свяжитесь со мной", - "recoveryStrategy": "Стратегия восстановления", - "recoveryStrategy_override": "Переопределение", - "recoveryStrategy_compatible": "Совместимый", + "restoreStrategy": "Стратегия восстановления", + "restoreStrategy_override": "Перезаписать", + "restoreStrategy_compatible": "Совместимый", "logsTest": "Тест журналов", "emptyTip": "{label} не может быть пустым", "urlTip": "{label} должен быть URL", @@ -467,5 +467,23 @@ "vpnConfigChangeDetected": "Обнаружено изменение конфигурации VPN", "restart": "Перезапустить", "speedStatistics": "Статистика скорости", - "resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?" + "resetPageChangesTip": "На текущей странице есть изменения. Вы уверены, что хотите сбросить?", + "overwriteTypeCustom": "Пользовательский", + "overwriteTypeCustomDesc": "Пользовательский режим, полная настройка групп прокси и правил", + "unknownNetworkError": "Неизвестная сетевая ошибка", + "networkRequestException": "Исключение сетевого запроса, пожалуйста, попробуйте позже.", + "restoreException": "Ошибка восстановления", + "networkException": "Ошибка сети, проверьте соединение и попробуйте еще раз", + "invalidBackupFile": "Неверный файл резервной копии", + "pruneCache": "Очистить кэш", + "backupAndRestore": "Резервное копирование и восстановление", + "backupAndRestoreDesc": "Синхронизация данных через WebDAV или файлы", + "restore": "Восстановить", + "restoreSuccess": "Восстановление успешно", + "restoreFromWebDAVDesc": "Восстановить данные через WebDAV", + "restoreFromFileDesc": "Восстановить данные из файла", + "restoreOnlyConfig": "Восстановить только файлы конфигурации", + "restoreAllData": "Восстановить все данные", + "addProfile": "Добавить профиль", + "delayTest": "Тест задержки" } \ No newline at end of file diff --git a/arb/intl_zh_CN.arb b/arb/intl_zh_CN.arb index abba2bc..3e3359b 100644 --- a/arb/intl_zh_CN.arb +++ b/arb/intl_zh_CN.arb @@ -129,14 +129,8 @@ "compatibleDesc": "开启将失去部分应用能力,获得全量的Clash的支持", "notSelectedTip": "当前代理组无法选中", "tip": "提示", - "backupAndRecovery": "备份与恢复", - "backupAndRecoveryDesc": "通过WebDAV或者文件同步数据", "account": "账号", "backup": "备份", - "recovery": "恢复", - "recoveryProfiles": "仅恢复配置文件", - "recoveryAll": "恢复所有数据", - "recoverySuccess": "恢复成功", "backupSuccess": "备份成功", "noInfo": "暂无信息", "pleaseBindWebDAV": "请绑定WebDAV", @@ -219,9 +213,7 @@ "local": "本地", "remote": "远程", "remoteBackupDesc": "备份数据到WebDAV", - "remoteRecoveryDesc": "通过WebDAV恢复数据", "localBackupDesc": "备份数据到本地", - "localRecoveryDesc": "通过文件恢复数据", "mode": "模式", "time": "时间", "source": "来源", @@ -382,9 +374,9 @@ "systemApp": "系统应用", "noNetworkApp": "无网络应用", "contactMe": "联系我", - "recoveryStrategy": "恢复策略", - "recoveryStrategy_override": "覆盖", - "recoveryStrategy_compatible": "兼容", + "restoreStrategy": "恢复策略", + "restoreStrategy_override": "覆盖", + "restoreStrategy_compatible": "兼容", "logsTest": "日志测试", "emptyTip": "{label}不能为空", "urlTip": "{label}必须为URL", @@ -467,5 +459,23 @@ "vpnConfigChangeDetected": "检测到VPN相关配置改动", "restart": "重启", "speedStatistics": "网速统计", - "resetPageChangesTip": "当前页面存在更改,确定重置吗?" + "resetPageChangesTip": "当前页面存在更改,确定重置吗?", + "overwriteTypeCustom": "自定义", + "overwriteTypeCustomDesc": "自定义模式,支持完全自定义修改代理组以及规则", + "unknownNetworkError": "未知网络错误", + "networkRequestException": "网络请求异常,请稍后再试。", + "restoreException": "恢复异常", + "networkException": "网络异常,请检查连接后重试", + "invalidBackupFile": "无效备份文件", + "pruneCache": "修剪缓存", + "backupAndRestore": "备份与恢复", + "backupAndRestoreDesc": "通过WebDAV或者文件同步数据", + "restore": "恢复", + "restoreSuccess": "恢复成功", + "restoreFromWebDAVDesc": "通过WebDAV恢复数据", + "restoreFromFileDesc": "通过文件恢复数据", + "restoreOnlyConfig": "仅恢复配置文件", + "restoreAllData": "恢复所有数据", + "addProfile": "添加配置", + "delayTest": "延迟测试" } diff --git a/build.yaml b/build.yaml index 5098650..efbbe97 100644 --- a/build.yaml +++ b/build.yaml @@ -6,6 +6,7 @@ targets: build_extensions: '^lib/models/{{}}.dart': 'lib/models/generated/{{}}.g.dart' '^lib/providers/{{}}.dart': 'lib/providers/generated/{{}}.g.dart' + '^lib/database/{{}}.dart': 'lib/database/generated/{{}}.g.dart' freezed: options: build_extensions: diff --git a/core/Clash.Meta b/core/Clash.Meta index e0cf7fb..c27f82f 160000 --- a/core/Clash.Meta +++ b/core/Clash.Meta @@ -1 +1 @@ -Subproject commit e0cf7fb4e641741592e87890be8427594932535e +Subproject commit c27f82fbdf2a79b1b9ed0dd46d7a50829b168bc4 diff --git a/core/common.go b/core/common.go index ca73f6f..410c45b 100644 --- a/core/common.go +++ b/core/common.go @@ -37,12 +37,6 @@ var ( mBatch, _ = batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](50)) ) -type ExternalProviders []ExternalProvider - -func (a ExternalProviders) Len() int { return len(a) } -func (a ExternalProviders) Less(i, j int) bool { return a[i].Name < a[j].Name } -func (a ExternalProviders) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - func getExternalProvidersRaw() map[string]cp.Provider { eps := make(map[string]cp.Provider) for n, p := range tunnel.Providers() { @@ -241,25 +235,8 @@ func updateConfig(params *UpdateParams) { updateListeners() } -func parseWithPath(path string) (*config.Config, error) { - buf, err := readFile(path) - if err != nil { - return nil, err - } - rawConfig := config.DefaultRawConfig() - err = UnmarshalJson(buf, rawConfig) - if err != nil { - return nil, err - } - parseRawConfig, err := config.ParseRawConfig(rawConfig) - if err != nil { - return nil, err - } - - return parseRawConfig, nil -} - -func setupConfig(params *SetupParams) error { +func applyConfig(params *SetupParams) error { + runtime.GC() runLock.Lock() defer runLock.Unlock() var err error @@ -271,7 +248,6 @@ func setupConfig(params *SetupParams) error { hub.ApplyConfig(currentConfig) patchSelectGroup(params.SelectedMap) updateListeners() - runtime.GC() return err } diff --git a/core/constant.go b/core/constant.go index 1cd68f5..af79f1e 100644 --- a/core/constant.go +++ b/core/constant.go @@ -66,6 +66,11 @@ type ExternalProvider struct { SubscriptionInfo *provider.SubscriptionInfo `json:"subscription-info"` } +type ProxiesData struct { + Proxies map[string]constant.Proxy `json:"proxies"` + All []string `json:"all"` +} + const ( messageMethod Method = "message" initClashMethod Method = "initClash" diff --git a/core/go.mod b/core/go.mod index 76079a4..9e7597d 100644 --- a/core/go.mod +++ b/core/go.mod @@ -10,6 +10,7 @@ require ( ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/RyuaNerin/go-krypto v1.3.0 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/ajg/form v1.5.1 // indirect @@ -18,18 +19,14 @@ require ( github.com/buger/jsonparser v1.1.1 // indirect github.com/coreos/go-iptables v0.8.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect - github.com/ebitengine/purego v0.9.1 // indirect - github.com/enfein/mieru/v3 v3.22.1 // indirect + github.com/enfein/mieru/v3 v3.26.2 // indirect github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gaukas/godicttls v0.0.4 // indirect - github.com/go-chi/chi/v5 v5.2.3 // indirect - github.com/go-chi/render v1.0.3 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect @@ -37,7 +34,7 @@ require ( github.com/golang/snappy v1.0.0 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 // indirect github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.17.9 // indirect @@ -52,37 +49,42 @@ require ( github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b // indirect github.com/metacubex/blake3 v0.1.0 // indirect github.com/metacubex/chacha v0.1.5 // indirect + github.com/metacubex/chi v0.1.0 // indirect + github.com/metacubex/cpu v0.1.0 // indirect github.com/metacubex/fswatch v0.1.1 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect - github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 // indirect - github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be // indirect + github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 // indirect + github.com/metacubex/hkdf v0.1.0 // indirect + github.com/metacubex/hpke v0.1.0 // indirect + github.com/metacubex/http v0.1.0 // indirect + github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect + github.com/metacubex/mlkem v0.1.0 // indirect github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect - github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 // indirect + github.com/metacubex/qpack v0.6.0 // indirect + github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 // indirect github.com/metacubex/randv2 v0.2.0 // indirect github.com/metacubex/restls-client-go v0.1.7 // indirect github.com/metacubex/sing v0.5.6 // indirect github.com/metacubex/sing-mux v0.3.4 // indirect - github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb // indirect + github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e // indirect github.com/metacubex/sing-shadowsocks v0.2.12 // indirect github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect - github.com/metacubex/sing-tun v0.4.9 // indirect + github.com/metacubex/sing-tun v0.4.11 // indirect github.com/metacubex/sing-vmess v0.2.4 // indirect github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect - github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 // indirect - github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 // indirect - github.com/metacubex/utls v1.8.3 // indirect + github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect + github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect + github.com/metacubex/tls v0.1.1 // indirect + github.com/metacubex/utls v1.8.4 // indirect github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect github.com/miekg/dns v1.1.63 // indirect github.com/mroth/weightedrand/v2 v2.1.0 // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect - github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/openacid/low v0.1.21 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect - github.com/quic-go/qpack v0.4.0 // indirect - github.com/sagernet/cors v1.2.1 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/samber/lo v1.52.0 // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect @@ -103,7 +105,7 @@ require ( golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/time v0.10.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/core/go.sum b/core/go.sum index deb1b71..f670c13 100644 --- a/core/go.sum +++ b/core/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg= github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM= github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss= @@ -12,9 +14,6 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -22,10 +21,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= -github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY= -github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/enfein/mieru/v3 v3.26.2 h1:U/2XJc+3vrJD9r815FoFdwToQFEcqSOzzzWIPPhjfEU= +github.com/enfein/mieru/v3 v3.26.2/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= @@ -39,15 +36,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= -github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -57,17 +47,15 @@ github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakr github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4= github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -98,47 +86,62 @@ github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cq github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk= github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M= github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= +github.com/metacubex/chi v0.1.0 h1:rjNDyDj50nRpicG43CNkIw4ssiCbmDL8d7wJXKlUCsg= +github.com/metacubex/chi v0.1.0/go.mod h1:zM5u5oMQt8b2DjvDHvzadKrP6B2ztmasL1YHRMbVV+g= +github.com/metacubex/cpu v0.1.0 h1:8PeTdV9j6UKbN1K5Jvtbi/Jock7dknvzyYuLb8Conmk= +github.com/metacubex/cpu v0.1.0/go.mod h1:09VEt4dSRLR+bOA8l4w4NDuzGZ8n5dkMv7e8axgEeTU= github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU= github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= -github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ= -github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= -github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM= -github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs= +github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 h1:hUL81H0Ic/XIDkvtn9M1pmfDdfid7JzYQToY4Ps1TvQ= +github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= +github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA= +github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4= +github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ= +github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U= +github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o= +github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg= +github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI= +github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc= +github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I= +github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA= -github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o= -github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c= +github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw= +github.com/metacubex/qpack v0.6.0/go.mod h1:lKGSi7Xk94IMvHGOmxS9eIei3bvIqpOAImEBsaOwTkA= +github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001 h1:RlT3bFCIDM/NR9GWaDbFCrweOwpHRfgaT9c0zuRlPhY= +github.com/metacubex/quic-go v0.59.1-0.20260112033758-aa29579f2001/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k= github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g= -github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.6 h1:mEPDCadsCj3DB8gn+t/EtposlYuALEkExa/LUguw6/c= github.com/metacubex/sing v0.5.6/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing-mux v0.3.4 h1:tf4r27CIkzaxq9kBlAXQkgMXq2HPp5Mta60Kb4RCZF0= github.com/metacubex/sing-mux v0.3.4/go.mod h1:SEJfAuykNj/ozbPqngEYqyggwSr81+L7Nu09NRD5mh4= -github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb h1:gxrJmnxuEAel+kh3V7ntqkHjURif0xKDu76nzr/BF5Y= -github.com/metacubex/sing-quic v0.0.0-20251004051927-c45ee18473bb/go.mod h1:JK4+PYUKps6pnlicKjsSUAjAcvIUjhorIjdNZGg930M= +github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e h1:MLxp42z9Jd6LtY2suyawnl24oNzIsFxWc15bNeDIGxA= +github.com/metacubex/sing-quic v0.0.0-20260112044712-65d17608159e/go.mod h1:+lgKTd52xAarGtqugALISShyw4KxnoEpYe2u0zJh26w= github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE= github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU= github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A= github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= -github.com/metacubex/sing-tun v0.4.9 h1:jY0Yyt8nnN3yQRN/jTxgqNCmGi1dsFdxdIi7pQUlVVU= -github.com/metacubex/sing-tun v0.4.9/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= +github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM= +github.com/metacubex/sing-tun v0.4.11/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I= github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= -github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM= -github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= -github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU= -github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= -github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4= -github.com/metacubex/utls v1.8.3/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= +github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk= +github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg= +github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o= +github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= +github.com/metacubex/tls v0.1.1 h1:BEcZrsPTTfNf4sKZ02EbZodv4UIj7fgHWa1Eqo12Bc0= +github.com/metacubex/tls v0.1.1/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM= +github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg= +github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk= github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4= github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 h1:lhlqpYHopuTLx9xQt22kSA9HtnyTDmk5XjjQVCGHe2E= @@ -149,9 +152,6 @@ github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0= github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo= github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0= @@ -164,10 +164,6 @@ github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= -github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= @@ -181,16 +177,9 @@ github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= @@ -234,22 +223,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/core/hub.go b/core/hub.go index fd973da..2f721a2 100644 --- a/core/hub.go +++ b/core/hub.go @@ -1,6 +1,7 @@ package main import ( + "cmp" "context" "encoding/json" "github.com/metacubex/mihomo/adapter" @@ -19,11 +20,11 @@ import ( "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel/statistic" + "golang.org/x/exp/slices" "net" "os" "runtime" "runtime/debug" - "sort" "strconv" "time" ) @@ -43,10 +44,8 @@ func handleInitClash(paramsString string) bool { return false } version = params.Version - if !isInit { - constant.SetHomeDir(params.HomeDir) - isInit = true - } + constant.SetHomeDir(params.HomeDir) + isInit = true return isInit } @@ -97,10 +96,52 @@ func handleValidateConfig(path string) string { return "" } -func handleGetProxies() map[string]constant.Proxy { +func handleGetProxies() ProxiesData { runLock.Lock() defer runLock.Unlock() - return tunnel.ProxiesWithProviders() + + nameList := config.GetProxyNameList() + + proxies := make(map[string]constant.Proxy) + + for name, proxy := range tunnel.Proxies() { + proxies[name] = proxy + } + for _, p := range tunnel.Providers() { + for _, proxy := range p.Proxies() { + proxies[proxy.Name()] = proxy + } + } + + hasGlobal := false + allNames := make([]string, 0, len(nameList)+1) + + for _, name := range nameList { + if name == "GLOBAL" { + hasGlobal = true + } + + p, ok := proxies[name] + if !ok || p == nil { + continue + } + switch p.Type() { + case constant.Selector, constant.URLTest, constant.Fallback, constant.Relay, constant.LoadBalance: + allNames = append(allNames, name) + default: + } + } + + if !hasGlobal { + if p, ok := proxies["GLOBAL"]; ok && p != nil { + allNames = append([]string{"GLOBAL"}, allNames...) + } + } + + return ProxiesData{ + All: allNames, + Proxies: proxies, + } } func handleChangeProxy(data string, fn func(string string)) { @@ -143,7 +184,7 @@ func handleChangeProxy(data string, fn func(string string)) { } func handleGetTraffic(onlyStatisticsProxy bool) string { - up, down := statistic.DefaultManager.Current(onlyStatisticsProxy) + up, down := statistic.DefaultManager.NowTraffic(onlyStatisticsProxy) traffic := map[string]int64{ "up": up, "down": down, @@ -157,7 +198,7 @@ func handleGetTraffic(onlyStatisticsProxy bool) string { } func handleGetTotalTraffic(onlyStatisticsProxy bool) string { - up, down := statistic.DefaultManager.Total(onlyStatisticsProxy) + up, down := statistic.DefaultManager.TotalTraffic(onlyStatisticsProxy) traffic := map[string]int64{ "up": up, "down": down, @@ -287,7 +328,9 @@ func handleGetExternalProviders() string { } eps = append(eps, *externalProvider) } - sort.Sort(ExternalProviders(eps)) + slices.SortFunc(eps, func(a, b ExternalProvider) int { + return cmp.Compare(a.Name, b.Name) + }) data, err := json.Marshal(eps) if err != nil { return "" @@ -489,14 +532,17 @@ func handleDelFile(path string, result ActionResult) { } func handleSetupConfig(bytes []byte) string { + if !isInit { + return "not initialized" + } var params = defaultSetupParams() err := UnmarshalJson(bytes, params) if err != nil { log.Errorln("unmarshalRawConfig error %v", err) - _ = setupConfig(defaultSetupParams()) + _ = applyConfig(defaultSetupParams()) return err.Error() } - err = setupConfig(params) + err = applyConfig(params) if err != nil { return err.Error() } diff --git a/core/lib.go b/core/lib.go index d4fe45d..61d0089 100644 --- a/core/lib.go +++ b/core/lib.go @@ -201,9 +201,29 @@ func invokeAction(callback unsafe.Pointer, paramsChar *C.char) { //export startTUN func startTUN(callback unsafe.Pointer, fd C.int, stackChar, addressChar, dnsChar *C.char) bool { handleStartTun(callback, int(fd), takeCString(stackChar), takeCString(addressChar), takeCString(dnsChar)) + if !isRunning { + handleStartListener() + } else { + handleResetConnections() + } return true } +//export quickSetup +func quickSetup(callback unsafe.Pointer, initParamsChar *C.char, setupParamsChar *C.char) { + go func() { + initParamsString := takeCString(initParamsChar) + setupParamsString := takeCString(setupParamsChar) + if !handleInitClash(initParamsString) { + invokeResult(callback, "init failed") + return + } + isRunning = true + message := handleSetupConfig([]byte(setupParamsString)) + invokeResult(callback, message) + }() +} + //export setEventListener func setEventListener(listener unsafe.Pointer) { if eventListener != nil || listener == nil { @@ -241,6 +261,9 @@ func sendMessage(message Message) { //export stopTun func stopTun() { handleStopTun() + if isRunning { + handleStopListener() + } } //export suspend diff --git a/lib/application.dart b/lib/application.dart index e1a1bbc..dbf2dda 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:fl_clash/common/common.dart'; @@ -25,6 +26,7 @@ class Application extends ConsumerStatefulWidget { class ApplicationState extends ConsumerState { Timer? _autoUpdateProfilesTaskTimer; + bool _preHasVpn = false; final _pageTransitionsTheme = const PageTransitionsTheme( builders: { @@ -45,22 +47,22 @@ class ApplicationState extends ConsumerState { @override void initState() { super.initState(); - _autoUpdateProfilesTask(); - globalState.appController = AppController(context, ref); WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { final currentContext = globalState.navigatorKey.currentContext; if (currentContext != null) { - globalState.appController = AppController(currentContext, ref); + await appController.attach(currentContext, ref); + } else { + exit(0); } - await globalState.appController.init(); - globalState.appController.initLink(); + _autoUpdateProfilesTask(); + appController.initLink(); app?.initShortcuts(); }); } void _autoUpdateProfilesTask() { _autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async { - await globalState.appController.autoUpdateProfiles(); + await appController.autoUpdateProfiles(); _autoUpdateProfilesTask(); }); } @@ -81,11 +83,13 @@ class ApplicationState extends ConsumerState { child: CoreManager( child: ConnectivityManager( onConnectivityChanged: (results) async { - if (!results.contains(ConnectivityResult.vpn)) { - coreController.closeConnections(); + commonPrint.log('connectivityChanged ${results.toString()}'); + appController.updateLocalIp(); + final hasVpn = results.contains(ConnectivityResult.vpn); + if (_preHasVpn == hasVpn) { + appController.addCheckIp(); } - globalState.appController.updateLocalIp(); - globalState.appController.addCheckIpNumDebounce(); + _preHasVpn = hasVpn; }, child: child, ), @@ -163,8 +167,7 @@ class ApplicationState extends ConsumerState { linkManager.destroy(); _autoUpdateProfilesTaskTimer?.cancel(); await coreController.destroy(); - await globalState.appController.savePreferences(); - await globalState.appController.handleExit(); + await appController.handleExit(); super.dispose(); } } diff --git a/lib/common/archive.dart b/lib/common/archive.dart index 37889f2..1d67cb6 100644 --- a/lib/common/archive.dart +++ b/lib/common/archive.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:archive/archive_io.dart'; @@ -18,14 +17,11 @@ extension ArchiveExt on Archive { final archiveFile = ArchiveFile(relativePath, data.length, data); addFile(archiveFile); } - // else if (entity is Directory) { - // addDirectoryToArchive(entity.path, parentPath); - // } } } - void addTextFile(String name, T raw) { - final data = json.encode(raw); - addFile(ArchiveFile.string(name, data)); - } + // void addTextFile(String name, T raw) { + // final data = json.encode(raw); + // addFile(ArchiveFile.string(name, data)); + // } } diff --git a/lib/common/common.dart b/lib/common/common.dart index b1e05b2..e75514e 100644 --- a/lib/common/common.dart +++ b/lib/common/common.dart @@ -6,17 +6,21 @@ export 'constant.dart'; export 'context.dart'; export 'converter.dart'; export 'datetime.dart'; +export 'file.dart'; export 'fixed.dart'; export 'function.dart'; export 'future.dart'; +export 'hive.dart'; export 'http.dart'; export 'icons.dart'; +export 'indexing.dart'; export 'iterable.dart'; export 'keyboard.dart'; export 'launch.dart'; export 'link.dart'; export 'lock.dart'; export 'measure.dart'; +export 'migration.dart'; export 'mixin.dart'; export 'navigation.dart'; export 'navigator.dart'; @@ -32,8 +36,10 @@ export 'proxy.dart'; export 'render.dart'; export 'request.dart'; export 'scroll.dart'; +export 'snowflake.dart'; export 'string.dart'; export 'system.dart'; +export 'task.dart'; export 'text.dart'; export 'tray.dart'; export 'utils.dart'; diff --git a/lib/common/compute.dart b/lib/common/compute.dart index fa91462..1564a72 100644 --- a/lib/common/compute.dart +++ b/lib/common/compute.dart @@ -1,8 +1,7 @@ +import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; -import 'string.dart'; - List computeSort({ required List groups, required ProxiesSortType sortType, @@ -10,53 +9,54 @@ List computeSort({ required Map selectedMap, required String defaultTestUrl, }) { + List sortOfDelay({ + required List groups, + required List proxies, + required DelayMap delayMap, + required Map selectedMap, + required String testUrl, + }) { + return List.from(proxies)..sort((a, b) { + final aDelayState = computeProxyDelayState( + proxyName: a.name, + testUrl: testUrl, + groups: groups, + selectedMap: selectedMap, + delayMap: delayMap, + ); + final bDelayState = computeProxyDelayState( + proxyName: b.name, + testUrl: testUrl, + groups: groups, + selectedMap: selectedMap, + delayMap: delayMap, + ); + return aDelayState.compareTo(bDelayState); + }); + } + + List sortOfName(List proxies) { + return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name)); + } + return groups.map((group) { final proxies = group.all; final newProxies = switch (sortType) { ProxiesSortType.none => proxies, - ProxiesSortType.delay => _sortOfDelay( + ProxiesSortType.delay => sortOfDelay( groups: groups, proxies: proxies, delayMap: delayMap, selectedMap: selectedMap, - testUrl: group.testUrl.getSafeValue(defaultTestUrl), + testUrl: group.testUrl.takeFirstValid([defaultTestUrl]), ), - ProxiesSortType.name => _sortOfName(proxies), + ProxiesSortType.name => sortOfName(proxies), }; return group.copyWith(all: newProxies); }).toList(); } -DelayState computeProxyDelayState({ - required String proxyName, - required String testUrl, - required List groups, - required Map selectedMap, - required DelayMap delayMap, -}) { - final state = computeRealSelectedProxyState( - proxyName, - groups: groups, - selectedMap: selectedMap, - ); - final currentDelayMap = delayMap[state.testUrl.getSafeValue(testUrl)] ?? {}; - final delay = currentDelayMap[state.proxyName]; - return DelayState(delay: delay ?? 0, group: state.group); -} - -SelectedProxyState computeRealSelectedProxyState( - String proxyName, { - required List groups, - required Map selectedMap, -}) { - return _getRealSelectedProxyState( - SelectedProxyState(proxyName: proxyName), - groups: groups, - selectedMap: selectedMap, - ); -} - -SelectedProxyState _getRealSelectedProxyState( +SelectedProxyState getRealSelectedProxyState( SelectedProxyState state, { required List groups, required Map selectedMap, @@ -72,39 +72,39 @@ SelectedProxyState _getRealSelectedProxyState( if (currentSelectedName.isEmpty) { return newState; } - return _getRealSelectedProxyState( + return getRealSelectedProxyState( newState.copyWith(proxyName: currentSelectedName, testUrl: group.testUrl), groups: groups, selectedMap: selectedMap, ); } -List _sortOfDelay({ +SelectedProxyState computeRealSelectedProxyState( + String proxyName, { required List groups, - required List proxies, - required DelayMap delayMap, required Map selectedMap, - required String testUrl, }) { - return List.from(proxies)..sort((a, b) { - final aDelayState = computeProxyDelayState( - proxyName: a.name, - testUrl: testUrl, - groups: groups, - selectedMap: selectedMap, - delayMap: delayMap, - ); - final bDelayState = computeProxyDelayState( - proxyName: b.name, - testUrl: testUrl, - groups: groups, - selectedMap: selectedMap, - delayMap: delayMap, - ); - return aDelayState.compareTo(bDelayState); - }); + return getRealSelectedProxyState( + SelectedProxyState(proxyName: proxyName), + groups: groups, + selectedMap: selectedMap, + ); } -List _sortOfName(List proxies) { - return List.of(proxies)..sort((a, b) => a.name.compareTo(b.name)); +DelayState computeProxyDelayState({ + required String proxyName, + required String testUrl, + required List groups, + required Map selectedMap, + required DelayMap delayMap, +}) { + final state = computeRealSelectedProxyState( + proxyName, + groups: groups, + selectedMap: selectedMap, + ); + final currentDelayMap = + delayMap[state.testUrl.takeFirstValid([testUrl])] ?? {}; + final delay = currentDelayMap[state.proxyName]; + return DelayState(delay: delay ?? 0, group: state.group); } diff --git a/lib/common/constant.dart b/lib/common/constant.dart index c924a73..289f73c 100644 --- a/lib/common/constant.dart +++ b/lib/common/constant.dart @@ -20,16 +20,18 @@ const helperPort = 47890; const maxTextScale = 1.4; const minTextScale = 0.8; final baseInfoEdgeInsets = EdgeInsets.symmetric( - vertical: 16.ap, - horizontal: 16.ap, + vertical: 16.mAp, + horizontal: 16.mAp, ); final listHeaderPadding = EdgeInsets.only( - left: 16.ap, - right: 8.ap, - top: 24.ap, - bottom: 8.ap, + left: 16.mAp, + right: 8.mAp, + top: 24.mAp, + bottom: 8.mAp, ); +const watchExecution = true; + final defaultTextScaleFactor = WidgetsBinding.instance.platformDispatcher.textScaleFactor; const httpTimeoutDuration = Duration(milliseconds: 5000); @@ -63,6 +65,7 @@ final commonFilter = ImageFilter.blur( tileMode: TileMode.mirror, ); +const listEquality = ListEquality(); const navigationItemListEquality = ListEquality(); const trackerInfoListEquality = ListEquality(); const stringListEquality = ListEquality(); @@ -70,15 +73,18 @@ const intListEquality = ListEquality(); const logListEquality = ListEquality(); const groupListEquality = ListEquality(); const ruleListEquality = ListEquality(); -const scriptEquality = ListEquality