From 18f1f389e0667729ea732993c75799ccce44d74f Mon Sep 17 00:00:00 2001 From: Hiddify Date: Sat, 3 Feb 2024 07:40:15 +0100 Subject: [PATCH 1/8] change again to ubuntu 22.04 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bffc4b80..4207ae9e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: filename: hiddify-windows-x64 - platform: linux-appimage - os: ubuntu-20.04 + os: ubuntu-22.04 aarch: amd64 targets: AppImage filename: hiddify-linux-x64 From 11defeb0102ef74d9be59660ad114f372fe09edf Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Sat, 3 Feb 2024 12:36:27 +0330 Subject: [PATCH 2/8] Add cloudflare warp options --- assets/translations/strings_en.i18n.json | 22 +- assets/translations/strings_es.i18n.json | 22 +- assets/translations/strings_fa.i18n.json | 22 +- assets/translations/strings_ru.i18n.json | 22 +- assets/translations/strings_tr.i18n.json | 22 +- assets/translations/strings_zh-CN.i18n.json | 22 +- lib/core/model/constants.dart | 4 + lib/core/model/range.dart | 24 ++- .../data/config_option_repository.dart | 6 + .../model/config_option_entity.dart | 20 +- .../model/config_option_patch.dart | 6 + .../notifier/warp_option_notifier.dart | 26 +++ .../overview/config_options_page.dart | 14 +- .../overview/warp_options_widgets.dart | 199 ++++++++++++++++++ lib/singbox/model/singbox_config_enum.dart | 10 + lib/singbox/model/singbox_config_option.dart | 7 + 16 files changed, 426 insertions(+), 22 deletions(-) create mode 100644 lib/features/config_option/notifier/warp_option_notifier.dart create mode 100644 lib/features/config_option/overview/warp_options_widgets.dart diff --git a/assets/translations/strings_en.i18n.json b/assets/translations/strings_en.i18n.json index ae723f96..669d4e97 100644 --- a/assets/translations/strings_en.i18n.json +++ b/assets/translations/strings_en.i18n.json @@ -11,7 +11,10 @@ }, "sort": "Sort", "sortBy": "Sort by", - "addToClipboard": "Add to clipboard" + "addToClipboard": "Add to clipboard", + "notSet": "Not Set", + "agree": "Agree", + "decline": "Decline" }, "intro": { "termsAndPolicyCaution(rich)": "by continuing you agree with ${tap(@:about.termsAndConditions)}", @@ -204,8 +207,13 @@ "mux": "Multiplexer", "outbound": "Outbound Options", "tlsTricks": "TLS Tricks", + "warp": "WARP Options", "misc": "Misc Options" }, + "warpConsent": { + "title": "Cloudflare WARP Consent", + "description(rich)": "Cloudflare WARP is a free WireGuard VPN provider. By enabling this option you are agreeing to the Cloudflare WARP's ${tos(Terms of Service)} and ${privacy(Privacy Policy)}." + }, "pageTitle": "Config Options", "logLevel": "Log Level", "resolveDestination": "Resolve Destination", @@ -243,7 +251,17 @@ "tlsPaddingSize": "TLS Padding", "enableMux": "Enable Mux", "muxProtocol": "Mux Protocol", - "muxMaxStreams": "Max Concurrent Streams" + "muxMaxStreams": "Max Concurrent Streams", + "enableWarp": "Enable WARP", + "warpDetourMode": "Detour Mode", + "warpDetourModes": { + "inbound": "Detour WARP through proxies", + "outbound": "Detour proxies through WARP" + }, + "warpLicenseKey": "License Key", + "warpCleanIp": "Clean IP", + "warpPort": "Port", + "warpNoise": "Noise" }, "geoAssets": { "pageTitle": "Routing Assets", diff --git a/assets/translations/strings_es.i18n.json b/assets/translations/strings_es.i18n.json index 0fb923cb..b15358cf 100644 --- a/assets/translations/strings_es.i18n.json +++ b/assets/translations/strings_es.i18n.json @@ -11,7 +11,10 @@ }, "sort": "Clasificar", "sortBy": "Ordenar por", - "addToClipboard": "Añadir al portapapeles" + "addToClipboard": "Añadir al portapapeles", + "notSet": "No establecido", + "agree": "Aceptar", + "decline": "Rechazar" }, "home": { "emptyProfilesMsg": "Comience agregando un perfil de suscripción", @@ -211,6 +214,7 @@ "mux": "Multiplexer", "outbound": "Opciones de salida", "tlsTricks": "Trucos TLS", + "warp": "WARP Options", "misc": "Opciones varias" }, "pageTitle": "Opciones de configuración", @@ -237,7 +241,21 @@ "tlsPaddingSize": "Relleno TLS", "enableMux": "Enable Mux", "muxProtocol": "Mux Protocol", - "muxMaxStreams": "Max Concurrent Streams" + "muxMaxStreams": "Max Concurrent Streams", + "enableWarp": "Enable WARP", + "warpDetourMode": "Detour Mode", + "warpDetourModes": { + "inbound": "Detour WARP through proxies", + "outbound": "Detour proxies through WARP" + }, + "warpLicenseKey": "License Key", + "warpCleanIp": "Clean IP", + "warpPort": "Port", + "warpNoise": "Noise", + "warpConsent": { + "title": "Consentimiento WARP de Cloudflare", + "description(rich)": "Cloudflare WARP es un proveedor de VPN WireGuard gratuito. Al habilitar esta opción, acepta los ${tos(Términos de servicio)} y ${privacy(Política de privacidad)} de Cloudflare WARP." + } }, "geoAssets": { "successMsg": "Activo actualizado correctamente", diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json index dd81bf08..024da3bc 100644 --- a/assets/translations/strings_fa.i18n.json +++ b/assets/translations/strings_fa.i18n.json @@ -11,7 +11,10 @@ }, "sort": "مرتب‌سازی", "sortBy": "مرتب‌سازی براساس", - "addToClipboard": "به کلیپ بورد اضافه کنید" + "addToClipboard": "به کلیپ بورد اضافه کنید", + "notSet": "تنظیم نشده", + "agree": "موافق", + "decline": "کاهش می یابد" }, "intro": { "termsAndPolicyCaution(rich)": "در صورت ادامه با ${tap(@:about.termsAndConditions)} موافقت میکنید", @@ -204,6 +207,7 @@ "mux": "Multiplexer", "outbound": "Outbound Options", "tlsTricks": "TLS Tricks", + "warp": "WARP Options", "misc": "تنظیمات متفرقه" }, "pageTitle": "تنظیمات کانفیگ", @@ -243,7 +247,21 @@ "tlsPaddingSize": "TLS Padding", "enableMux": "Enable Mux", "muxProtocol": "Mux Protocol", - "muxMaxStreams": "Max Concurrent Streams" + "muxMaxStreams": "Max Concurrent Streams", + "enableWarp": "Enable WARP", + "warpDetourMode": "Detour Mode", + "warpDetourModes": { + "inbound": "Detour WARP through proxies", + "outbound": "Detour proxies through WARP" + }, + "warpLicenseKey": "License Key", + "warpCleanIp": "Clean IP", + "warpPort": "Port", + "warpNoise": "Noise", + "warpConsent": { + "title": "رضایت Cloudflare WARP", + "description(rich)": "Cloudflare WARP یک ارائه دهنده رایگان WireGuard VPN است. با فعال کردن این گزینه، با ${tos(شرایط خدمات)} و ${privacy(خط‌مشی رازداری)} Cloudflare WARP موافقت می‌کنید." + } }, "geoAssets": { "pageTitle": "فایل‌های مسیریابی", diff --git a/assets/translations/strings_ru.i18n.json b/assets/translations/strings_ru.i18n.json index 12a4b278..ce4f1eab 100644 --- a/assets/translations/strings_ru.i18n.json +++ b/assets/translations/strings_ru.i18n.json @@ -11,7 +11,10 @@ }, "sort": "Сортировка", "sortBy": "Сортировка", - "addToClipboard": "Копировать в буфер обмена" + "addToClipboard": "Копировать в буфер обмена", + "notSet": "Не задано", + "agree": "Соглашаться", + "decline": "Отклонить" }, "intro": { "termsAndPolicyCaution(rich)": "Продолжая, Вы соглашаетесь с ${tap(@:about.termsAndConditions)}", @@ -204,6 +207,7 @@ "mux": "Multiplexer", "outbound": "Outbound Options", "tlsTricks": "TLS Tricks", + "warp": "WARP Options", "misc": "Разные параметры" }, "pageTitle": "Параметры конфигурации", @@ -243,7 +247,21 @@ "tlsPaddingSize": "TLS Padding", "enableMux": "Enable Mux", "muxProtocol": "Mux Protocol", - "muxMaxStreams": "Max Concurrent Streams" + "muxMaxStreams": "Max Concurrent Streams", + "enableWarp": "Enable WARP", + "warpDetourMode": "Detour Mode", + "warpDetourModes": { + "inbound": "Detour WARP through proxies", + "outbound": "Detour proxies through WARP" + }, + "warpLicenseKey": "License Key", + "warpCleanIp": "Clean IP", + "warpPort": "Port", + "warpNoise": "Noise", + "warpConsent": { + "title": "Согласие Cloudflare WARP", + "description(rich)": "Cloudflare WARP — бесплатный провайдер WireGuard VPN. Включая эту опцию, вы соглашаетесь с ${tos(Условиями обслуживания)} и ${privacy(Политикой конфиденциальности)} Cloudflare WARP." + } }, "geoAssets": { "pageTitle": "Активы маршрутизации", diff --git a/assets/translations/strings_tr.i18n.json b/assets/translations/strings_tr.i18n.json index e1aa5e69..5f4148b9 100644 --- a/assets/translations/strings_tr.i18n.json +++ b/assets/translations/strings_tr.i18n.json @@ -11,7 +11,10 @@ }, "sort": "Sırala", "sortBy": "Sırala", - "addToClipboard": "Panoya ekle" + "addToClipboard": "Panoya ekle", + "notSet": "Ayarlanmadı", + "agree": "Kabul etmek", + "decline": "Reddetmek" }, "intro": { "termsAndPolicyCaution(rich)": "devam ederek ${tap(@:about.termsAndConditions)} kabul etmiş olursunuz", @@ -204,6 +207,7 @@ "mux": "Multiplexer", "outbound": "Outbound Options", "tlsTricks": "TLS Tricks", + "warp": "WARP Options", "misc": "Çeşitli Seçenekler" }, "pageTitle": "Yapılandırma Seçenekleri", @@ -243,7 +247,21 @@ "tlsPaddingSize": "TLS Padding", "enableMux": "Enable Mux", "muxProtocol": "Mux Protocol", - "muxMaxStreams": "Max Concurrent Streams" + "muxMaxStreams": "Max Concurrent Streams", + "enableWarp": "Enable WARP", + "warpDetourMode": "Detour Mode", + "warpDetourModes": { + "inbound": "Detour WARP through proxies", + "outbound": "Detour proxies through WARP" + }, + "warpLicenseKey": "License Key", + "warpCleanIp": "Clean IP", + "warpPort": "Port", + "warpNoise": "Noise", + "warpConsent": { + "title": "Cloudflare WARP Onayı", + "description(rich)": "Cloudflare WARP ücretsiz bir WireGuard VPN sağlayıcısıdır. Bu seçeneği etkinleştirerek Cloudflare WARP'ın ${tos(Hizmet Şartları)} ve ${privacy(Gizlilik Politikası)}'nı kabul etmiş olursunuz." + } }, "geoAssets": { "pageTitle": "Varlıkları Yönlendirme", diff --git a/assets/translations/strings_zh-CN.i18n.json b/assets/translations/strings_zh-CN.i18n.json index fc7a4f77..3f4e3b25 100644 --- a/assets/translations/strings_zh-CN.i18n.json +++ b/assets/translations/strings_zh-CN.i18n.json @@ -11,7 +11,10 @@ }, "sort": "排序", "sortBy": "排序方式", - "addToClipboard": "添加到剪贴板" + "addToClipboard": "添加到剪贴板", + "notSet": "没有设置", + "agree": "同意", + "decline": "衰退" }, "intro": { "termsAndPolicyCaution(rich)": "继续即表示您同意 ${tap(@:about.termsAndConditions)}", @@ -204,6 +207,7 @@ "mux": "Multiplexer", "outbound": "出站选项", "tlsTricks": "TLS Tricks", + "warp": "WARP Options", "misc": "其它选项" }, "pageTitle": "配置选项", @@ -243,7 +247,21 @@ "tlsPaddingSize": "TLS 填充", "enableMux": "Enable Mux", "muxProtocol": "Mux Protocol", - "muxMaxStreams": "Max Concurrent Streams" + "muxMaxStreams": "Max Concurrent Streams", + "enableWarp": "Enable WARP", + "warpDetourMode": "Detour Mode", + "warpDetourModes": { + "inbound": "Detour WARP through proxies", + "outbound": "Detour proxies through WARP" + }, + "warpLicenseKey": "License Key", + "warpCleanIp": "Clean IP", + "warpPort": "Port", + "warpNoise": "Noise", + "warpConsent": { + "title": "Cloudflare WARP 同意", + "description(rich)": "Cloudflare WARP 是免费的 WireGuard VPN 提供商。启用此选项即表示您同意 Cloudflare WARP 的 ${tos(服务条款)} 和 ${privacy(隐私政策)}" + } }, "geoAssets": { "pageTitle": "路由资源文件", diff --git a/lib/core/model/constants.dart b/lib/core/model/constants.dart index 8882ab83..fcb7bddf 100644 --- a/lib/core/model/constants.dart +++ b/lib/core/model/constants.dart @@ -10,4 +10,8 @@ abstract class Constants { static const telegramChannelUrl = "https://t.me/hiddify"; static const privacyPolicyUrl = "https://hiddify.com/privacy-policy/"; static const termsAndConditionsUrl = "https://hiddify.com/terms/"; + static const cfWarpPrivacyPolicy = + "https://www.cloudflare.com/application/privacypolicy/"; + static const cfWarpTermsOfService = + "https://www.cloudflare.com/application/terms/"; } diff --git a/lib/core/model/range.dart b/lib/core/model/range.dart index fba247b3..d644da6f 100644 --- a/lib/core/model/range.dart +++ b/lib/core/model/range.dart @@ -1,4 +1,6 @@ +import 'package:dartx/dartx.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hiddify/core/localization/translations.dart'; part 'range.freezed.dart'; @@ -7,14 +9,21 @@ class RangeWithOptionalCeil with _$RangeWithOptionalCeil { const RangeWithOptionalCeil._(); const factory RangeWithOptionalCeil({ - required int min, + int? min, int? max, }) = _RangeWithOptionalCeil; - String format() => "$min${max != null ? "-$max" : ""}"; + String format() => [min, max].whereNotNull().join("-"); + String present(TranslationsEn t) => + format().isEmpty ? t.general.notSet : format(); - factory RangeWithOptionalCeil.fromString(String input) => + factory RangeWithOptionalCeil._fromString( + String input, { + bool allowEmpty = true, + }) => switch (input.split("-")) { + [final String val] when val.isEmpty && allowEmpty => + const RangeWithOptionalCeil(), [final String min] => RangeWithOptionalCeil(min: int.parse(min)), [final String min, final String max] => RangeWithOptionalCeil( min: int.parse(min), @@ -23,9 +32,12 @@ class RangeWithOptionalCeil with _$RangeWithOptionalCeil { _ => throw Exception("Invalid range: $input"), }; - static RangeWithOptionalCeil? tryParse(String input) { + static RangeWithOptionalCeil? tryParse( + String input, { + bool allowEmpty = false, + }) { try { - return RangeWithOptionalCeil.fromString(input); + return RangeWithOptionalCeil._fromString(input); } catch (_) { return null; } @@ -38,7 +50,7 @@ class RangeWithOptionalCeilJsonConverter @override RangeWithOptionalCeil fromJson(String json) => - RangeWithOptionalCeil.fromString(json); + RangeWithOptionalCeil._fromString(json); @override String toJson(RangeWithOptionalCeil object) => object.format(); diff --git a/lib/features/config_option/data/config_option_repository.dart b/lib/features/config_option/data/config_option_repository.dart index 7d8f985d..82faa16f 100644 --- a/lib/features/config_option/data/config_option_repository.dart +++ b/lib/features/config_option/data/config_option_repository.dart @@ -117,6 +117,12 @@ class ConfigOptionRepositoryImpl muxPadding: persisted.muxPadding, muxMaxStreams: persisted.muxMaxStreams, muxProtocol: persisted.muxProtocol, + enableWarp: persisted.enableWarp, + warpDetourMode: persisted.warpDetourMode, + warpLicenseKey: persisted.warpLicenseKey, + warpCleanIp: persisted.warpCleanIp, + warpPort: persisted.warpPort, + warpNoise: persisted.warpNoise, geoipPath: geoAssetPathResolver.relativePath( geoAssets.geoip.providerName, geoAssets.geoip.fileName, diff --git a/lib/features/config_option/model/config_option_entity.dart b/lib/features/config_option/model/config_option_entity.dart index 4412f07e..676f575a 100644 --- a/lib/features/config_option/model/config_option_entity.dart +++ b/lib/features/config_option/model/config_option_entity.dart @@ -57,6 +57,14 @@ class ConfigOptionEntity with _$ConfigOptionEntity { @Default(false) bool muxPadding, @Default(8) int muxMaxStreams, @Default(MuxProtocol.h2mux) MuxProtocol muxProtocol, + @Default(false) bool enableWarp, + @Default(WarpDetourMode.outbound) WarpDetourMode warpDetourMode, + @Default("") String warpLicenseKey, + @Default("auto") String warpCleanIp, + @Default(0) int warpPort, + @RangeWithOptionalCeilJsonConverter() + @Default(RangeWithOptionalCeil()) + RangeWithOptionalCeil warpNoise, }) = _ConfigOptionEntity; static ConfigOptionEntity initial = ConfigOptionEntity( @@ -67,7 +75,11 @@ class ConfigOptionEntity with _$ConfigOptionEntity { if (PlatformUtils.isDesktop && serviceMode == ServiceMode.tun) { return true; } - if (enableTlsFragment || enableTlsMixedSniCase || enableTlsPadding||enableMux) { + if (enableTlsFragment || + enableTlsMixedSniCase || + enableTlsPadding || + enableMux || + enableWarp) { return true; } @@ -117,6 +129,12 @@ class ConfigOptionEntity with _$ConfigOptionEntity { muxPadding: patch.muxPadding ?? muxPadding, muxMaxStreams: patch.muxMaxStreams ?? muxMaxStreams, muxProtocol: patch.muxProtocol ?? muxProtocol, + enableWarp: patch.enableWarp ?? enableWarp, + warpDetourMode: patch.warpDetourMode ?? warpDetourMode, + warpLicenseKey: patch.warpLicenseKey ?? warpLicenseKey, + warpCleanIp: patch.warpCleanIp ?? warpCleanIp, + warpPort: patch.warpPort ?? warpPort, + warpNoise: patch.warpNoise ?? warpNoise, ); } diff --git a/lib/features/config_option/model/config_option_patch.dart b/lib/features/config_option/model/config_option_patch.dart index 501117a4..b9558a3f 100644 --- a/lib/features/config_option/model/config_option_patch.dart +++ b/lib/features/config_option/model/config_option_patch.dart @@ -47,6 +47,12 @@ class ConfigOptionPatch with _$ConfigOptionPatch { bool? muxPadding, int? muxMaxStreams, MuxProtocol? muxProtocol, + bool? enableWarp, + WarpDetourMode? warpDetourMode, + String? warpLicenseKey, + String? warpCleanIp, + int? warpPort, + @RangeWithOptionalCeilJsonConverter() RangeWithOptionalCeil? warpNoise, }) = _ConfigOptionPatch; factory ConfigOptionPatch.fromJson(Map json) => diff --git a/lib/features/config_option/notifier/warp_option_notifier.dart b/lib/features/config_option/notifier/warp_option_notifier.dart new file mode 100644 index 00000000..44e21953 --- /dev/null +++ b/lib/features/config_option/notifier/warp_option_notifier.dart @@ -0,0 +1,26 @@ +import 'package:hiddify/core/preferences/preferences_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'warp_option_notifier.g.dart'; + +@Riverpod(keepAlive: true) +class WarpOptionNotifier extends _$WarpOptionNotifier { + @override + bool build() { + return ref + .read(sharedPreferencesProvider) + .requireValue + .getBool(warpConsentGiven) ?? + false; + } + + Future agree() async { + await ref + .read(sharedPreferencesProvider) + .requireValue + .setBool(warpConsentGiven, true); + state = true; + } + + static const warpConsentGiven = "warp_consent_given"; +} diff --git a/lib/features/config_option/overview/config_options_page.dart b/lib/features/config_option/overview/config_options_page.dart index ad8bb52a..c2df00cd 100644 --- a/lib/features/config_option/overview/config_options_page.dart +++ b/lib/features/config_option/overview/config_options_page.dart @@ -9,6 +9,7 @@ import 'package:hiddify/core/widget/tip_card.dart'; import 'package:hiddify/features/config_option/model/config_option_entity.dart'; import 'package:hiddify/features/config_option/model/config_option_patch.dart'; import 'package:hiddify/features/config_option/notifier/config_option_notifier.dart'; +import 'package:hiddify/features/config_option/overview/warp_options_widgets.dart'; import 'package:hiddify/features/log/model/log_level.dart'; import 'package:hiddify/features/settings/widgets/sections_widgets.dart'; import 'package:hiddify/features/settings/widgets/settings_input_dialog.dart'; @@ -319,7 +320,7 @@ class ConfigOptionsPage extends HookConsumerWidget { ), ListTile( title: Text(t.settings.config.tlsFragmentSize), - subtitle: Text(options.tlsFragmentSize.format()), + subtitle: Text(options.tlsFragmentSize.present(t)), onTap: () async { final range = await SettingsInputDialog( title: t.settings.config.tlsFragmentSize, @@ -336,7 +337,7 @@ class ConfigOptionsPage extends HookConsumerWidget { ), ListTile( title: Text(t.settings.config.tlsFragmentSleep), - subtitle: Text(options.tlsFragmentSleep.format()), + subtitle: Text(options.tlsFragmentSleep.present(t)), onTap: () async { final range = await SettingsInputDialog( title: t.settings.config.tlsFragmentSleep, @@ -368,7 +369,7 @@ class ConfigOptionsPage extends HookConsumerWidget { ), ListTile( title: Text(t.settings.config.tlsPaddingSize), - subtitle: Text(options.tlsPaddingSize.format()), + subtitle: Text(options.tlsPaddingSize.present(t)), onTap: () async { final range = await SettingsInputDialog( title: t.settings.config.tlsPaddingSize, @@ -384,6 +385,13 @@ class ConfigOptionsPage extends HookConsumerWidget { }, ), const SettingsDivider(), + SettingsSection(experimental(t.settings.config.section.warp)), + WarpOptionsTiles( + options: options, + defaultOptions: defaultOptions, + onChange: changeOption, + ), + const SettingsDivider(), SettingsSection(t.settings.config.section.misc), ListTile( title: Text(t.settings.config.connectionTestUrl), diff --git a/lib/features/config_option/overview/warp_options_widgets.dart b/lib/features/config_option/overview/warp_options_widgets.dart new file mode 100644 index 00000000..a5b798a4 --- /dev/null +++ b/lib/features/config_option/overview/warp_options_widgets.dart @@ -0,0 +1,199 @@ +import 'package:dartx/dartx.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:hiddify/core/localization/translations.dart'; +import 'package:hiddify/core/model/constants.dart'; +import 'package:hiddify/core/model/range.dart'; +import 'package:hiddify/features/config_option/model/config_option_entity.dart'; +import 'package:hiddify/features/config_option/model/config_option_patch.dart'; +import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart'; +import 'package:hiddify/features/settings/widgets/settings_input_dialog.dart'; +import 'package:hiddify/singbox/model/singbox_config_enum.dart'; +import 'package:hiddify/utils/uri_utils.dart'; +import 'package:hiddify/utils/validators.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class WarpOptionsTiles extends HookConsumerWidget { + const WarpOptionsTiles({ + required this.options, + required this.defaultOptions, + required this.onChange, + super.key, + }); + + final ConfigOptionEntity options; + final ConfigOptionEntity defaultOptions; + final Future Function(ConfigOptionPatch patch) onChange; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); + + final warpPrefaceCompleted = ref.watch(warpOptionNotifierProvider); + final canChangeOptions = warpPrefaceCompleted && options.enableWarp; + + return Column( + children: [ + SwitchListTile.adaptive( + title: Text(t.settings.config.enableWarp), + value: options.enableWarp, + onChanged: (value) async { + if (!warpPrefaceCompleted) { + final agreed = await showAdaptiveDialog( + context: context, + builder: (context) => const WarpLicenseAgreementModal(), + ); + if (agreed ?? false) { + await ref.read(warpOptionNotifierProvider.notifier).agree(); + await onChange(ConfigOptionPatch(enableWarp: value)); + } + } else { + await onChange(ConfigOptionPatch(enableWarp: value)); + } + }, + ), + ListTile( + title: Text(t.settings.config.warpDetourMode), + subtitle: Text(options.warpDetourMode.present(t)), + enabled: canChangeOptions, + onTap: () async { + final warpDetourMode = await SettingsPickerDialog( + title: t.settings.config.warpDetourMode, + selected: options.warpDetourMode, + options: WarpDetourMode.values, + getTitle: (e) => e.present(t), + resetValue: defaultOptions.warpDetourMode, + ).show(context); + if (warpDetourMode == null) return; + await onChange( + ConfigOptionPatch(warpDetourMode: warpDetourMode), + ); + }, + ), + ListTile( + title: Text(t.settings.config.warpLicenseKey), + subtitle: Text( + options.warpLicenseKey.isEmpty + ? t.general.notSet + : options.warpLicenseKey, + ), + enabled: canChangeOptions, + onTap: () async { + final licenseKey = await SettingsInputDialog( + title: t.settings.config.warpLicenseKey, + initialValue: options.warpLicenseKey, + resetValue: defaultOptions.warpLicenseKey, + ).show(context); + if (licenseKey == null) return; + await onChange(ConfigOptionPatch(warpLicenseKey: licenseKey)); + }, + ), + ListTile( + title: Text(t.settings.config.warpCleanIp), + subtitle: Text(options.warpCleanIp), + enabled: canChangeOptions, + onTap: () async { + final warpCleanIp = await SettingsInputDialog( + title: t.settings.config.warpCleanIp, + initialValue: options.warpCleanIp, + resetValue: defaultOptions.warpCleanIp, + ).show(context); + if (warpCleanIp == null || warpCleanIp.isBlank) return; + await onChange(ConfigOptionPatch(warpCleanIp: warpCleanIp)); + }, + ), + ListTile( + title: Text(t.settings.config.warpPort), + subtitle: Text(options.warpPort.toString()), + enabled: canChangeOptions, + onTap: () async { + final warpPort = await SettingsInputDialog( + title: t.settings.config.warpPort, + initialValue: options.warpPort, + resetValue: defaultOptions.warpPort, + validator: isPort, + mapTo: int.tryParse, + digitsOnly: true, + ).show(context); + if (warpPort == null) return; + await onChange( + ConfigOptionPatch(warpPort: warpPort), + ); + }, + ), + ListTile( + title: Text(t.settings.config.warpNoise), + subtitle: Text(options.warpNoise.present(t)), + enabled: canChangeOptions, + onTap: () async { + final warpNoise = await SettingsInputDialog( + title: t.settings.config.warpNoise, + initialValue: options.warpNoise.format(), + resetValue: defaultOptions.warpNoise.format(), + ).show(context); + if (warpNoise == null) return; + await onChange( + ConfigOptionPatch( + warpNoise: RangeWithOptionalCeil.tryParse( + warpNoise, + allowEmpty: true, + ), + ), + ); + }, + ), + ], + ); + } +} + +class WarpLicenseAgreementModal extends HookConsumerWidget { + const WarpLicenseAgreementModal({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); + + return AlertDialog.adaptive( + title: Text(t.settings.config.warpConsent.title), + content: Text.rich( + t.settings.config.warpConsent.description( + tos: (text) => TextSpan( + text: text, + style: const TextStyle(color: Colors.blue), + recognizer: TapGestureRecognizer() + ..onTap = () async { + await UriUtils.tryLaunch( + Uri.parse(Constants.cfWarpTermsOfService), + ); + }, + ), + privacy: (text) => TextSpan( + text: text, + style: const TextStyle(color: Colors.blue), + recognizer: TapGestureRecognizer() + ..onTap = () async { + await UriUtils.tryLaunch( + Uri.parse(Constants.cfWarpPrivacyPolicy), + ); + }, + ), + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(t.general.decline), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text(t.general.agree), + ), + ], + ); + } +} diff --git a/lib/singbox/model/singbox_config_enum.dart b/lib/singbox/model/singbox_config_enum.dart index 5c1a966e..30aaac78 100644 --- a/lib/singbox/model/singbox_config_enum.dart +++ b/lib/singbox/model/singbox_config_enum.dart @@ -78,3 +78,13 @@ enum MuxProtocol { smux, yamux; } + +enum WarpDetourMode { + outbound, + inbound; + + String present(TranslationsEn t) => switch (this) { + outbound => t.settings.config.warpDetourModes.outbound, + inbound => t.settings.config.warpDetourModes.inbound, + }; +} diff --git a/lib/singbox/model/singbox_config_option.dart b/lib/singbox/model/singbox_config_option.dart index 3a0c66fc..5cbf4614 100644 --- a/lib/singbox/model/singbox_config_option.dart +++ b/lib/singbox/model/singbox_config_option.dart @@ -52,6 +52,13 @@ class SingboxConfigOption with _$SingboxConfigOption { required bool muxPadding, required int muxMaxStreams, required MuxProtocol muxProtocol, + required bool enableWarp, + required WarpDetourMode warpDetourMode, + required String warpLicenseKey, + required String warpCleanIp, + required int warpPort, + @RangeWithOptionalCeilJsonConverter() + required RangeWithOptionalCeil warpNoise, required String geoipPath, required String geositePath, required List rules, From 3106b58b910574560a8ce126a7d521e30f96ea8d Mon Sep 17 00:00:00 2001 From: Hiddify Date: Sat, 3 Feb 2024 10:08:54 +0100 Subject: [PATCH 3/8] temporary disable app image build --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4207ae9e..944bc00b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,11 +39,11 @@ jobs: targets: exe filename: hiddify-windows-x64 - - platform: linux-appimage - os: ubuntu-22.04 - aarch: amd64 - targets: AppImage - filename: hiddify-linux-x64 + # - platform: linux-appimage + # os: ubuntu-20.04 + # aarch: amd64 + # targets: AppImage + # filename: hiddify-linux-x64 - platform: linux-deb os: ubuntu-20.04 From 3950d3349f2bf18073818cd28f1ccfb72a3c3813 Mon Sep 17 00:00:00 2001 From: Hiddify Date: Sat, 3 Feb 2024 16:53:33 +0100 Subject: [PATCH 4/8] make makefile cross platform --- Makefile | 75 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index e8308cdd..ec75880e 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,25 @@ include dependencies.properties +ifeq ($(OS),Windows_NT) + MKDIR := -mkdir + RM := rmdir /s /q + SEP:=\\ + PLATFORM_REQ:= @set /p platform="Run 'make prepare platform=ios' or enter platform name:"; +else + MKDIR := mkdir -p + RM := rm -rf + SEP :=/ + PLATFORM_REQ:= @read -p "Run make prepare platform=ios or enter platform name: " platform; +endif -BINDIR=./libcore/bin -ANDROID_OUT=./android/app/libs -IOS_OUT=./ios/Frameworks -DESKTOP_OUT=./libcore/bin -GEO_ASSETS_DIR=./assets/core +BINDIR=libcore$(SEP)bin +ANDROID_OUT=android$(SEP)app$(SEP)libs +IOS_OUT=ios$(SEP)Frameworks +DESKTOP_OUT=libcore$(SEP)bin +GEO_ASSETS_DIR=assets$(SEP)core CORE_PRODUCT_NAME=hiddify-core CORE_NAME=$(CORE_PRODUCT_NAME) -SRV_NAME=hiddify-service +SRV_NAME=HiddifyService ifeq ($(CHANNEL),prod) CORE_URL=https://github.com/hiddify/hiddify-next-core/releases/download/v$(core.version) else @@ -24,6 +35,10 @@ endif BUILD_ARGS=--dart-define sentry_dsn=$(SENTRY_DSN) DISTRIBUTOR_ARGS=--skip-clean --build-target $(TARGET) --build-dart-define sentry_dsn=$(SENTRY_DSN) + + + + get: flutter pub get @@ -33,19 +48,23 @@ gen: translate: dart run slang -prepare: get-geo-assets get gen translate - @echo "Available platforms:" - @echo "android" - @echo "windows" - @echo "linux" - @echo "macos" - @echo "ios" - if [ -z "$$platform" ]; then \ - read -p "run make prepare platform=ios or Enter platform name: " choice; \ - else \ - choice=$$platform; \ - fi; \ - make $$choice-libs + + +prepare: #get-geo-assets get gen translate + @echo use the following commands to prepare the library for each platform: + @echo make prepare-android + @echo make prepare-windows + @echo make prepare-linux + @echo make prepare-macos + @echo make prepare-ios + +prepare-windows: get-geo-assets get gen translate windows-libs +prepare-ios: get-geo-assets get gen translate ios-libs +prepare-macos: get-geo-assets get gen translate macos-libs +prepare-linux: get-geo-assets get gen translate linux-libs +prepare-android: get-geo-assets get gen translate android-libs + + sync_translate: cd .github && bash sync_translate.sh @@ -81,18 +100,18 @@ ios-release: #not tested flutter_distributor package --platform ios --targets ipa --build-export-options-plist ios/exportOptions.plist $(DISTRIBUTOR_ARGS) android-libs: - mkdir -p $(ANDROID_OUT) + @$(MKDIR) $(ANDROID_OUT) || echo Folder already exists. Skipping... curl -L $(CORE_URL)/$(CORE_NAME)-android.tar.gz | tar xz -C $(ANDROID_OUT)/ android-apk-libs: android-libs android-aab-libs: android-libs windows-libs: - mkdir -p $(DESKTOP_OUT) + @$(MKDIR) $(DESKTOP_OUT) || echo Folder already exists. Skipping... curl -L $(CORE_URL)/$(CORE_NAME)-windows-amd64.tar.gz | tar xz -C $(DESKTOP_OUT)/ linux-libs: - mkdir -p $(DESKTOP_OUT) + @$(MKDIR) $(DESKTOP_OUT) || echo Folder already exists. Skipping... curl -L $(CORE_URL)/$(CORE_NAME)-linux-amd64.tar.gz | tar xz -C $(DESKTOP_OUT)/ @@ -101,13 +120,13 @@ linux-rpm-libs:linux-libs linux-appimage-libs:linux-libs macos-libs: - mkdir -p $(DESKTOP_OUT)/ &&\ - curl -L $(CORE_URL)/$(CORE_NAME)-macos-universal.tar.gz | tar xz -C $(DESKTOP_OUT)/ + @$(MKDIR) $(DESKTOP_OUT) || echo Folder already exists. Skipping... + curl -L $(CORE_URL)/$(CORE_NAME)-macos-universal.tar.gz | tar xz -C $(DESKTOP_OUT) ios-libs: #not tested - mkdir -p $(DESKTOP_OUT)/ && \ - rm -rf $(IOS_OUT)/Libcore.xcframework && \ - curl -L $(CORE_URL)/$(CORE_NAME)-ios.tar.gz | tar xz -C "$(IOS_OUT)" + @$(MKDIR) $(IOS_OUT) || echo Folder already exists. Skipping... + @$(RM) $(IOS_OUT)/Libcore.xcframework + curl -L $(CORE_URL)/$(CORE_NAME)-ios.tar.gz | tar xz -C "$(IOS_OUT)" get-geo-assets: curl -L https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db -o $(GEO_ASSETS_DIR)/geoip.db @@ -136,7 +155,7 @@ build-macos-libs: mv $(BINDIR)/$(SRV_NAME) $(DESKTOP_OUT)/ build-ios-libs: - rm -rf $(IOS_OUT)/Libcore.xcframework && \ + @$(RM) $(IOS_OUT)/Libcore.xcframework && \ make -C libcore -f Makefile ios && \ mv $(BINDIR)/$(CORE_NAME)-ios.xcframework $(IOS_OUT)/Libcore.xcframework From d331991575dea646b090cc37c39fa084a90c1526 Mon Sep 17 00:00:00 2001 From: Hiddify Date: Sat, 3 Feb 2024 16:54:21 +0100 Subject: [PATCH 5/8] change the name of tunnel service to HiddifyService --- .../connection/data/connection_platform_source.dart | 6 +++--- linux/CMakeLists.txt | 7 +++++-- windows/CMakeLists.txt | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/features/connection/data/connection_platform_source.dart b/lib/features/connection/data/connection_platform_source.dart index 65e29914..3fc5dd67 100644 --- a/lib/features/connection/data/connection_platform_source.dart +++ b/lib/features/connection/data/connection_platform_source.dart @@ -170,11 +170,11 @@ class ConnectionPlatformSourceImpl fullPath = "libcore"; } if (Platform.isWindows) { - fullPath = p.join(fullPath, "hiddify-service.exe"); + fullPath = p.join(fullPath, "HiddifyService.exe"); } else if (Platform.isMacOS) { - fullPath = p.join(fullPath, "hiddify-service"); + fullPath = p.join(fullPath, "HiddifyService"); } else { - fullPath = p.join(fullPath, "hiddify-service"); + fullPath = p.join(fullPath, "HiddifyService"); } return "$binFolder/$fullPath"; diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 398f3343..ba38a4ab 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -120,8 +120,11 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "../libcore/bin/libcore.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -install(FILES "../libcore/bin/hiddify-service" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) +install( + FILES "../libcore/bin/HiddifyService" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime +) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 1a1e60df..7bca9452 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -87,9 +87,9 @@ set(HIDDIFY_NEXT_LIB "../libcore/bin/libcore.dll") install(FILES "${HIDDIFY_NEXT_LIB}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime RENAME libcore.dll) -set(HIDDIFY_NEXT_LIB "../libcore/bin/hiddify-service.exe") +set(HIDDIFY_NEXT_LIB "../libcore/bin/HiddifyService.exe") install(FILES "${HIDDIFY_NEXT_LIB}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" -COMPONENT Runtime RENAME hiddify-service.exe) +COMPONENT Runtime RENAME HiddifyService.exe) if(PLUGIN_BUNDLED_LIBRARIES) From d6bd161a750b145ba47527c4d3e86e60c18e5b5d Mon Sep 17 00:00:00 2001 From: Hiddify Date: Sat, 3 Feb 2024 17:39:13 +0100 Subject: [PATCH 6/8] change make flags --- Makefile | 20 ++++++++++---------- windows/packaging/exe/make_config.yaml | 9 ++++++++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index ec75880e..1cf36ddc 100644 --- a/Makefile +++ b/Makefile @@ -52,17 +52,17 @@ translate: prepare: #get-geo-assets get gen translate @echo use the following commands to prepare the library for each platform: - @echo make prepare-android - @echo make prepare-windows - @echo make prepare-linux - @echo make prepare-macos - @echo make prepare-ios + @echo make android-prepare + @echo make windows-prepare + @echo make linux-prepare + @echo make macos-prepare + @echo make ios-prepare -prepare-windows: get-geo-assets get gen translate windows-libs -prepare-ios: get-geo-assets get gen translate ios-libs -prepare-macos: get-geo-assets get gen translate macos-libs -prepare-linux: get-geo-assets get gen translate linux-libs -prepare-android: get-geo-assets get gen translate android-libs +windows-prepare: get-geo-assets get gen translate windows-libs +ios-prepare: get-geo-assets get gen translate ios-libs +macos-prepare: get-geo-assets get gen translate macos-libs +linux-prepare: get-geo-assets get gen translate linux-libs +android-prepare: get-geo-assets get gen translate android-libs diff --git a/windows/packaging/exe/make_config.yaml b/windows/packaging/exe/make_config.yaml index 1cb07acb..78f61e98 100644 --- a/windows/packaging/exe/make_config.yaml +++ b/windows/packaging/exe/make_config.yaml @@ -2,9 +2,16 @@ app_id: 6L903538-42B1-4596-G479-BJ779F21A65D publisher: Hiddify publisher_url: https://github.com/hiddify/hiddify-next display_name: Hiddify Next +executable_name: HiddifyNext.exe +output_base_file_name: HiddifyNext.exe create_desktop_icon: true install_dir_name: "{autopf64}\\hiddify" setup_icon_file: ..\..\windows\runner\resources\app_icon.ico locales: - en - - fa \ No newline at end of file + - fa + - zh + - ru + - pt + - tr +#script_template: inno_setup.sas \ No newline at end of file From b1d6c56a3edeef7a2f8eeaf106afc369e46decb5 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Sat, 3 Feb 2024 21:21:46 +0330 Subject: [PATCH 7/8] Add ios command client --- ios/Network/CommandClient.swift | 160 ++++++++++++++++ ios/Network/Extension+Iterator.swift | 12 ++ ios/Runner.xcodeproj/project.pbxproj | 203 +++++++-------------- ios/Runner/Handlers/LogsEventHandler.swift | 15 +- ios/Runner/VPN/VPNManager.swift | 3 + 5 files changed, 246 insertions(+), 147 deletions(-) create mode 100644 ios/Network/CommandClient.swift create mode 100644 ios/Network/Extension+Iterator.swift diff --git a/ios/Network/CommandClient.swift b/ios/Network/CommandClient.swift new file mode 100644 index 00000000..4b2cac05 --- /dev/null +++ b/ios/Network/CommandClient.swift @@ -0,0 +1,160 @@ +import Foundation +import Libcore + +public class CommandClient: ObservableObject { + public enum ConnectionType { + case status + case groups + case log + case clashMode + } + + private let connectionType: ConnectionType + private let logMaxLines: Int + private var commandClient: LibboxCommandClient? + private var connectTask: Task? + + @Published public var isConnected: Bool + @Published public var status: LibboxStatusMessage? + @Published public var groups: [LibboxOutboundGroup]? + @Published public var logList: [String] + @Published public var clashModeList: [String] + @Published public var clashMode: String + + public init(_ connectionType: ConnectionType, logMaxLines: Int = 300) { + self.connectionType = connectionType + self.logMaxLines = logMaxLines + logList = [] + clashModeList = [] + clashMode = "" + isConnected = false + } + + public func connect() { + if isConnected { + return + } + if let connectTask { + connectTask.cancel() + } + connectTask = Task { + await connect0() + } + } + + public func disconnect() { + if let connectTask { + connectTask.cancel() + self.connectTask = nil + } + if let commandClient { + try? commandClient.disconnect() + self.commandClient = nil + } + } + + private nonisolated func connect0() async { + let clientOptions = LibboxCommandClientOptions() + switch connectionType { + case .status: + clientOptions.command = LibboxCommandStatus + case .groups: + clientOptions.command = LibboxCommandGroup + case .log: + clientOptions.command = LibboxCommandLog + case .clashMode: + clientOptions.command = LibboxCommandClashMode + } + clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC) + let client = LibboxNewCommandClient(clientHandler(self), clientOptions)! + do { + for i in 0 ..< 10 { + try await Task.sleep(nanoseconds: UInt64(Double(100 + (i * 50)) * Double(NSEC_PER_MSEC))) + try Task.checkCancellation() + do { + try client.connect() + await MainActor.run { + commandClient = client + } + return + } catch {} + try Task.checkCancellation() + } + } catch { + try? client.disconnect() + } + } + + private class clientHandler: NSObject, LibboxCommandClientHandlerProtocol { + private let commandClient: CommandClient + + init(_ commandClient: CommandClient) { + self.commandClient = commandClient + } + + func connected() { + DispatchQueue.main.async { [self] in + if commandClient.connectionType == .log { + commandClient.logList = [] + } + commandClient.isConnected = true + } + } + + func disconnected(_: String?) { + DispatchQueue.main.async { [self] in + commandClient.isConnected = false + } + } + + func clearLog() { + DispatchQueue.main.async { [self] in + commandClient.logList.removeAll() + } + } + + func writeLog(_ message: String?) { + guard let message else { + return + } + DispatchQueue.main.async { [self] in + if commandClient.logList.count > commandClient.logMaxLines { + commandClient.logList.removeFirst() + } + commandClient.logList.append(message) + } + } + + func writeStatus(_ message: LibboxStatusMessage?) { + DispatchQueue.main.async { [self] in + commandClient.status = message + } + } + + func writeGroups(_ groups: LibboxOutboundGroupIteratorProtocol?) { + guard let groups else { + return + } + var newGroups: [LibboxOutboundGroup] = [] + while groups.hasNext() { + newGroups.append(groups.next()!) + } + DispatchQueue.main.async { [self] in + commandClient.groups = newGroups + } + } + + func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) { + DispatchQueue.main.async { [self] in + commandClient.clashModeList = modeList!.toArray() + commandClient.clashMode = currentMode! + } + } + + func updateClashMode(_ newMode: String?) { + DispatchQueue.main.async { [self] in + commandClient.clashMode = newMode! + } + } + } +} \ No newline at end of file diff --git a/ios/Network/Extension+Iterator.swift b/ios/Network/Extension+Iterator.swift new file mode 100644 index 00000000..b54611d0 --- /dev/null +++ b/ios/Network/Extension+Iterator.swift @@ -0,0 +1,12 @@ +import Foundation +import Libcore + +extension LibboxStringIteratorProtocol { + func toArray() -> [String] { + var array: [String] = [] + while hasNext() { + array.append(next()) + } + return array + } +} diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index cd4282c9..22c5f1fb 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -34,7 +34,7 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 54EA599BF9C050F2827533D5 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDA50BDF2E5E5DDA3995F24D /* Pods_RunnerTests.framework */; }; + 59E8864FB99B37076B22F32B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30560DB3EFDF5E86AAD00AB8 /* Pods_RunnerTests.framework */; }; 6836D3FD2B57FE4300A79D75 /* Libcore in Frameworks */ = {isa = PBXBuildFile; productRef = 6836D3FC2B57FE4300A79D75 /* Libcore */; }; 6836D4022B57FEFF00A79D75 /* Libcore in Frameworks */ = {isa = PBXBuildFile; productRef = 6836D4012B57FEFF00A79D75 /* Libcore */; }; 68885DD72B4EF33400D214BA /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03E392B72ADDA00E000ADF15 /* NetworkExtension.framework */; }; @@ -42,6 +42,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + D78B64E92B6EB41900C5D800 /* Extension+Iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B64E82B6EB41900C5D800 /* Extension+Iterator.swift */; }; + D7A729302B6E44F200FFFF41 /* CommandClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A7292F2B6E44F200FFFF41 /* CommandClient.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -89,6 +91,7 @@ 032158B72ADDF8BF008D943B /* VPNManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNManager.swift; sourceTree = ""; }; 032158B92ADDFCC9008D943B /* TrafficReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficReader.swift; sourceTree = ""; }; 032158BB2ADDFD09008D943B /* SingBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingBox.swift; sourceTree = ""; }; + 0337C822BEDF7A95576475B3 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 03B516662AE6B93A00EA47E2 /* MethodHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodHandler.swift; sourceTree = ""; }; 03B516682AE7306B00EA47E2 /* StatusEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEventHandler.swift; sourceTree = ""; }; 03B5166A2AE7315E00EA47E2 /* AlertsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertsEventHandler.swift; sourceTree = ""; }; @@ -109,6 +112,7 @@ 03E392CE2ADDEFC8000ADF15 /* FilePath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePath.swift; sourceTree = ""; }; 03E392D12ADDF1F4000ADF15 /* ExtensionPlatformInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPlatformInterface.swift; sourceTree = ""; }; 03E392D32ADDF262000ADF15 /* Extension+RunBlocking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+RunBlocking.swift"; sourceTree = ""; }; + 040FA3EB0673B72D60CC7145 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 0736954E2B1FEB3E007249BE /* mobile_scanner.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = mobile_scanner.xcframework; path = ../build/ios/framework/Release/mobile_scanner.xcframework; sourceTree = ""; }; 0736958A2B3AC96D007249BE /* Bundle+Properties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Properties.swift"; sourceTree = ""; }; 0736958C2B3B79E0007249BE /* StatsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsEventHandler.swift; sourceTree = ""; }; @@ -138,9 +142,9 @@ 07A63A9F2B1E72FC00CAFA4D /* path_provider_foundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = path_provider_foundation.xcframework; path = ../build/ios/framework/release/path_provider_foundation.xcframework; sourceTree = ""; }; 07A63AA02B1E72FC00CAFA4D /* shared_preferences_foundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = shared_preferences_foundation.xcframework; path = ../build/ios/framework/release/shared_preferences_foundation.xcframework; sourceTree = ""; }; 07A63AA12B1E72FC00CAFA4D /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../build/ios/framework/release/Sentry.xcframework; sourceTree = ""; }; - 0F7E04B7207513677AF77112 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 30560DB3EFDF5E86AAD00AB8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; @@ -151,7 +155,7 @@ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7CA62594950187FCFE36B54C /* Pods-Runner-SingBoxPacketTunnel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SingBoxPacketTunnel.debug.xcconfig"; path = "Target Support Files/Pods-Runner-SingBoxPacketTunnel/Pods-Runner-SingBoxPacketTunnel.debug.xcconfig"; sourceTree = ""; }; - 7E8B7AF73AD416B8FAA5E9B0 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 808A94F72872B54716770416 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 90E93DE403BDFA627F3AA51E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -160,10 +164,10 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9A37A927A1A9458918B3C12A /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9AC67B4DCF829F5B6F63AA7D /* Pods-Runner-SingBoxPacketTunnel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SingBoxPacketTunnel.release.xcconfig"; path = "Target Support Files/Pods-Runner-SingBoxPacketTunnel/Pods-Runner-SingBoxPacketTunnel.release.xcconfig"; sourceTree = ""; }; C20A211B58CE31B2738D133C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - DDA50BDF2E5E5DDA3995F24D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D78B64E82B6EB41900C5D800 /* Extension+Iterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Iterator.swift"; sourceTree = ""; }; + D7A7292F2B6E44F200FFFF41 /* CommandClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandClient.swift; sourceTree = ""; }; F3FFE1D9C2D5629FACC123EE /* Pods-Runner-SingBoxPacketTunnel.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SingBoxPacketTunnel.profile.xcconfig"; path = "Target Support Files/Pods-Runner-SingBoxPacketTunnel/Pods-Runner-SingBoxPacketTunnel.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -181,7 +185,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 54EA599BF9C050F2827533D5 /* Pods_RunnerTests.framework in Frameworks */, + 59E8864FB99B37076B22F32B /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -276,12 +280,12 @@ 574F12C7748958784380337F /* Pods-Runner.debug.xcconfig */, 90E93DE403BDFA627F3AA51E /* Pods-Runner.release.xcconfig */, C20A211B58CE31B2738D133C /* Pods-Runner.profile.xcconfig */, - 7E8B7AF73AD416B8FAA5E9B0 /* Pods-RunnerTests.debug.xcconfig */, - 9A37A927A1A9458918B3C12A /* Pods-RunnerTests.release.xcconfig */, - 0F7E04B7207513677AF77112 /* Pods-RunnerTests.profile.xcconfig */, 7CA62594950187FCFE36B54C /* Pods-Runner-SingBoxPacketTunnel.debug.xcconfig */, 9AC67B4DCF829F5B6F63AA7D /* Pods-Runner-SingBoxPacketTunnel.release.xcconfig */, F3FFE1D9C2D5629FACC123EE /* Pods-Runner-SingBoxPacketTunnel.profile.xcconfig */, + 0337C822BEDF7A95576475B3 /* Pods-RunnerTests.debug.xcconfig */, + 808A94F72872B54716770416 /* Pods-RunnerTests.release.xcconfig */, + 040FA3EB0673B72D60CC7145 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -317,6 +321,7 @@ 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( + D7A7292E2B6E44D500FFFF41 /* Network */, 6836D3FF2B57FECF00A79D75 /* Local Packages */, 03E392CD2ADDE103000ADF15 /* Shared */, 9740EEB11CF90186004384FC /* Flutter */, @@ -388,12 +393,21 @@ 07A63A842B1E72AE00CAFA4D /* App.xcframework */, 07A63A832B1E728C00CAFA4D /* Release */, 60F1D4AAC33ACF5C8307310D /* Pods_Runner.framework */, - DDA50BDF2E5E5DDA3995F24D /* Pods_RunnerTests.framework */, 03E392B72ADDA00E000ADF15 /* NetworkExtension.framework */, + 30560DB3EFDF5E86AAD00AB8 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; + D7A7292E2B6E44D500FFFF41 /* Network */ = { + isa = PBXGroup; + children = ( + D7A7292F2B6E44F200FFFF41 /* CommandClient.swift */, + D78B64E82B6EB41900C5D800 /* Extension+Iterator.swift */, + ); + path = Network; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -424,7 +438,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - F63DAC79B8A3B6681959DBC1 /* [CP] Check Pods Manifest.lock */, + 2058E420D1A8B6F0E5E03873 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 531FE8242BCD501C24C8E9FA /* Frameworks */, @@ -546,6 +560,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2058E420D1A8B6F0E5E03873 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -599,28 +635,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F63DAC79B8A3B6681959DBC1 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; FBEFD3291AEA65EDE2F5AEF6 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -673,12 +687,14 @@ 03B516772AE7634400EA47E2 /* Logger.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 03B516712AE74CCD00EA47E2 /* VPNConfig.swift in Sources */, + D78B64E92B6EB41900C5D800 /* Extension+Iterator.swift in Sources */, 03B5166B2AE7315E00EA47E2 /* AlertsEventHandler.swift in Sources */, 0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */, 0736958D2B3B79E0007249BE /* StatsEventHandler.swift in Sources */, 03B516692AE7306B00EA47E2 /* StatusEventHandler.swift in Sources */, 032158B82ADDF8BF008D943B /* VPNManager.swift in Sources */, 0736958F2B3B8048007249BE /* PlatformMethodHandler.swift in Sources */, + D7A729302B6E44F200FFFF41 /* CommandClient.swift in Sources */, 03B516672AE6B93A00EA47E2 /* MethodHandler.swift in Sources */, 03B5166D2AE7325500EA47E2 /* LogsEventHandler.swift in Sources */, 03E392D02ADDF1BD000ADF15 /* FilePath.swift in Sources */, @@ -800,6 +816,8 @@ INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)", "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", @@ -847,6 +865,8 @@ INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)", "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", @@ -942,49 +962,18 @@ DEVELOPMENT_TEAM = 3JFTY5BP58; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleToolboxForMac\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilitiesComponents\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/Sentry\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/SentryPrivate\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cupertino_http\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_native_splash\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/mobile_scanner\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/nanopb\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/package_info_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/protocol_handler\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/sentry_flutter\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/share_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/shared_preferences_foundation\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/sqlite3\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/sqlite3_flutter_libs\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios\"", - "\"${PODS_ROOT}/MLImage/Frameworks\"", - "\"${PODS_ROOT}/MLKitBarcodeScanning/Frameworks\"", - "\"${PODS_ROOT}/MLKitCommon/Frameworks\"", - "\"${PODS_ROOT}/MLKitVision/Frameworks\"", - "$(PROJECT_DIR)/Flutter", - "$(PROJECT_DIR)/Frameworks/**", - "$(PROJECT_DIR)/..", - ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", "$(PROJECT_DIR)/build/ios/framework/$(CONFIGURATION)", "$(PROJECT_DIR)/../build/ios/framework/$(CONFIGURATION)", "$(SDKROOT)/usr/lib/swift", "$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)", + "$(inherited)", "@executable_path/Frameworks", + "@executable_path/../../Frameworks", "@executable_path/../libcore/", - "@executable_path/libcore", + "@executable_path/libcore/", ); ONLY_ACTIVE_ARCH = NO; OTHER_CPLUSPLUSFLAGS = ( @@ -1010,7 +999,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7E8B7AF73AD416B8FAA5E9B0 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 0337C822BEDF7A95576475B3 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -1028,7 +1017,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9A37A927A1A9458918B3C12A /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 808A94F72872B54716770416 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -1044,7 +1033,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0F7E04B7207513677AF77112 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 040FA3EB0673B72D60CC7145 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -1194,49 +1183,18 @@ ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleToolboxForMac\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilitiesComponents\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/Sentry\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/SentryPrivate\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cupertino_http\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_native_splash\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/mobile_scanner\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/nanopb\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/package_info_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/protocol_handler\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/sentry_flutter\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/share_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/shared_preferences_foundation\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/sqlite3\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/sqlite3_flutter_libs\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios\"", - "\"${PODS_ROOT}/MLImage/Frameworks\"", - "\"${PODS_ROOT}/MLKitBarcodeScanning/Frameworks\"", - "\"${PODS_ROOT}/MLKitCommon/Frameworks\"", - "\"${PODS_ROOT}/MLKitVision/Frameworks\"", - "$(PROJECT_DIR)/Flutter", - "$(PROJECT_DIR)/Frameworks/**", - "$(PROJECT_DIR)/..", - ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", "$(PROJECT_DIR)/build/ios/framework/$(CONFIGURATION)", "$(PROJECT_DIR)/../build/ios/framework/$(CONFIGURATION)", "$(SDKROOT)/usr/lib/swift", "$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)", + "$(inherited)", "@executable_path/Frameworks", + "@executable_path/../../Frameworks", "@executable_path/../libcore/", - "@executable_path/libcore", + "@executable_path/libcore/", ); OTHER_CPLUSPLUSFLAGS = ( "-fcxx-modules", @@ -1273,49 +1231,18 @@ DEVELOPMENT_TEAM = 3JFTY5BP58; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleToolboxForMac\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilitiesComponents\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/Sentry\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/SentryPrivate\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/cupertino_http\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_native_splash\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/mobile_scanner\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/nanopb\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/package_info_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/protocol_handler\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/sentry_flutter\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/share_plus\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/shared_preferences_foundation\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/sqlite3\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/sqlite3_flutter_libs\"", - "\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_ios\"", - "\"${PODS_ROOT}/MLImage/Frameworks\"", - "\"${PODS_ROOT}/MLKitBarcodeScanning/Frameworks\"", - "\"${PODS_ROOT}/MLKitCommon/Frameworks\"", - "\"${PODS_ROOT}/MLKitVision/Frameworks\"", - "$(PROJECT_DIR)/Flutter", - "$(PROJECT_DIR)/Frameworks/**", - "$(PROJECT_DIR)/..", - ); INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", "$(PROJECT_DIR)/build/ios/framework/$(CONFIGURATION)", "$(PROJECT_DIR)/../build/ios/framework/$(CONFIGURATION)", "$(SDKROOT)/usr/lib/swift", "$(TOOLCHAIN_DIR)/usr/lib/swift-5.5/$(PLATFORM_NAME)", + "$(inherited)", "@executable_path/Frameworks", + "@executable_path/../../Frameworks", "@executable_path/../libcore/", - "@executable_path/libcore", + "@executable_path/libcore/", ); ONLY_ACTIVE_ARCH = NO; OTHER_CPLUSPLUSFLAGS = ( diff --git a/ios/Runner/Handlers/LogsEventHandler.swift b/ios/Runner/Handlers/LogsEventHandler.swift index 11ee6396..368f6e66 100644 --- a/ios/Runner/Handlers/LogsEventHandler.swift +++ b/ios/Runner/Handlers/LogsEventHandler.swift @@ -1,16 +1,11 @@ -// -// LogsEventHandler.swift -// Runner -// -// Created by GFWFighter on 10/24/23. -// - import Foundation +import Combine public class LogsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler { static let name = "\(Bundle.main.serviceIdentifier)/service.logs" private var channel: FlutterEventChannel? + private var cancellable: AnyCancellable? public static func register(with registrar: FlutterPluginRegistrar) { let instance = LogsEventHandler() @@ -19,13 +14,15 @@ public class LogsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler { } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { - if VPNManager.shared.logCallback { - events(VPNManager.shared.logList) + VPNManager.shared.logClient?.connect() + cancellable = VPNManager.shared.logClient?.$logList.sink { [events] logsList in + events(logsList) } return nil } public func onCancel(withArguments arguments: Any?) -> FlutterError? { + cancellable?.cancel() return nil } } diff --git a/ios/Runner/VPN/VPNManager.swift b/ios/Runner/VPN/VPNManager.swift index 1ade3291..0bb1d9c3 100644 --- a/ios/Runner/VPN/VPNManager.swift +++ b/ios/Runner/VPN/VPNManager.swift @@ -41,6 +41,7 @@ class VPNManager: ObservableObject { @Published private(set) var elapsedTime: TimeInterval = 0 @Published private(set) var logList: [String] = [] @Published private(set) var logCallback = false + private(set) var logClient: CommandClient? private var _connectTime: Date? private var connectTime: Date? { @@ -73,6 +74,8 @@ class VPNManager: ObservableObject { updateStats() elapsedTime = -1 * (connectTime?.timeIntervalSinceNow ?? 0) } + + logClient = CommandClient(.log) } deinit { From a0da2e52c286e2e4edc0587b2677534f4962e44a Mon Sep 17 00:00:00 2001 From: realWLF Date: Sun, 4 Feb 2024 02:50:54 +0330 Subject: [PATCH 8/8] Log for iOS by @ Akuma --- ios/Network/CommandClient.swift | 160 --------------------- ios/Network/Extension+Iterator.swift | 12 -- ios/Runner.xcodeproj/project.pbxproj | 16 --- ios/Runner/Handlers/LogsEventHandler.swift | 68 +++++++-- ios/Runner/VPN/VPNManager.swift | 30 +--- 5 files changed, 62 insertions(+), 224 deletions(-) delete mode 100644 ios/Network/CommandClient.swift delete mode 100644 ios/Network/Extension+Iterator.swift diff --git a/ios/Network/CommandClient.swift b/ios/Network/CommandClient.swift deleted file mode 100644 index 4b2cac05..00000000 --- a/ios/Network/CommandClient.swift +++ /dev/null @@ -1,160 +0,0 @@ -import Foundation -import Libcore - -public class CommandClient: ObservableObject { - public enum ConnectionType { - case status - case groups - case log - case clashMode - } - - private let connectionType: ConnectionType - private let logMaxLines: Int - private var commandClient: LibboxCommandClient? - private var connectTask: Task? - - @Published public var isConnected: Bool - @Published public var status: LibboxStatusMessage? - @Published public var groups: [LibboxOutboundGroup]? - @Published public var logList: [String] - @Published public var clashModeList: [String] - @Published public var clashMode: String - - public init(_ connectionType: ConnectionType, logMaxLines: Int = 300) { - self.connectionType = connectionType - self.logMaxLines = logMaxLines - logList = [] - clashModeList = [] - clashMode = "" - isConnected = false - } - - public func connect() { - if isConnected { - return - } - if let connectTask { - connectTask.cancel() - } - connectTask = Task { - await connect0() - } - } - - public func disconnect() { - if let connectTask { - connectTask.cancel() - self.connectTask = nil - } - if let commandClient { - try? commandClient.disconnect() - self.commandClient = nil - } - } - - private nonisolated func connect0() async { - let clientOptions = LibboxCommandClientOptions() - switch connectionType { - case .status: - clientOptions.command = LibboxCommandStatus - case .groups: - clientOptions.command = LibboxCommandGroup - case .log: - clientOptions.command = LibboxCommandLog - case .clashMode: - clientOptions.command = LibboxCommandClashMode - } - clientOptions.statusInterval = Int64(2 * NSEC_PER_SEC) - let client = LibboxNewCommandClient(clientHandler(self), clientOptions)! - do { - for i in 0 ..< 10 { - try await Task.sleep(nanoseconds: UInt64(Double(100 + (i * 50)) * Double(NSEC_PER_MSEC))) - try Task.checkCancellation() - do { - try client.connect() - await MainActor.run { - commandClient = client - } - return - } catch {} - try Task.checkCancellation() - } - } catch { - try? client.disconnect() - } - } - - private class clientHandler: NSObject, LibboxCommandClientHandlerProtocol { - private let commandClient: CommandClient - - init(_ commandClient: CommandClient) { - self.commandClient = commandClient - } - - func connected() { - DispatchQueue.main.async { [self] in - if commandClient.connectionType == .log { - commandClient.logList = [] - } - commandClient.isConnected = true - } - } - - func disconnected(_: String?) { - DispatchQueue.main.async { [self] in - commandClient.isConnected = false - } - } - - func clearLog() { - DispatchQueue.main.async { [self] in - commandClient.logList.removeAll() - } - } - - func writeLog(_ message: String?) { - guard let message else { - return - } - DispatchQueue.main.async { [self] in - if commandClient.logList.count > commandClient.logMaxLines { - commandClient.logList.removeFirst() - } - commandClient.logList.append(message) - } - } - - func writeStatus(_ message: LibboxStatusMessage?) { - DispatchQueue.main.async { [self] in - commandClient.status = message - } - } - - func writeGroups(_ groups: LibboxOutboundGroupIteratorProtocol?) { - guard let groups else { - return - } - var newGroups: [LibboxOutboundGroup] = [] - while groups.hasNext() { - newGroups.append(groups.next()!) - } - DispatchQueue.main.async { [self] in - commandClient.groups = newGroups - } - } - - func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) { - DispatchQueue.main.async { [self] in - commandClient.clashModeList = modeList!.toArray() - commandClient.clashMode = currentMode! - } - } - - func updateClashMode(_ newMode: String?) { - DispatchQueue.main.async { [self] in - commandClient.clashMode = newMode! - } - } - } -} \ No newline at end of file diff --git a/ios/Network/Extension+Iterator.swift b/ios/Network/Extension+Iterator.swift deleted file mode 100644 index b54611d0..00000000 --- a/ios/Network/Extension+Iterator.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation -import Libcore - -extension LibboxStringIteratorProtocol { - func toArray() -> [String] { - var array: [String] = [] - while hasNext() { - array.append(next()) - } - return array - } -} diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 22c5f1fb..225a73c1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -42,8 +42,6 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - D78B64E92B6EB41900C5D800 /* Extension+Iterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B64E82B6EB41900C5D800 /* Extension+Iterator.swift */; }; - D7A729302B6E44F200FFFF41 /* CommandClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A7292F2B6E44F200FFFF41 /* CommandClient.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -166,8 +164,6 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9AC67B4DCF829F5B6F63AA7D /* Pods-Runner-SingBoxPacketTunnel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SingBoxPacketTunnel.release.xcconfig"; path = "Target Support Files/Pods-Runner-SingBoxPacketTunnel/Pods-Runner-SingBoxPacketTunnel.release.xcconfig"; sourceTree = ""; }; C20A211B58CE31B2738D133C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - D78B64E82B6EB41900C5D800 /* Extension+Iterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Iterator.swift"; sourceTree = ""; }; - D7A7292F2B6E44F200FFFF41 /* CommandClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandClient.swift; sourceTree = ""; }; F3FFE1D9C2D5629FACC123EE /* Pods-Runner-SingBoxPacketTunnel.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SingBoxPacketTunnel.profile.xcconfig"; path = "Target Support Files/Pods-Runner-SingBoxPacketTunnel/Pods-Runner-SingBoxPacketTunnel.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -321,7 +317,6 @@ 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( - D7A7292E2B6E44D500FFFF41 /* Network */, 6836D3FF2B57FECF00A79D75 /* Local Packages */, 03E392CD2ADDE103000ADF15 /* Shared */, 9740EEB11CF90186004384FC /* Flutter */, @@ -399,15 +394,6 @@ name = Frameworks; sourceTree = ""; }; - D7A7292E2B6E44D500FFFF41 /* Network */ = { - isa = PBXGroup; - children = ( - D7A7292F2B6E44F200FFFF41 /* CommandClient.swift */, - D78B64E82B6EB41900C5D800 /* Extension+Iterator.swift */, - ); - path = Network; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -687,14 +673,12 @@ 03B516772AE7634400EA47E2 /* Logger.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 03B516712AE74CCD00EA47E2 /* VPNConfig.swift in Sources */, - D78B64E92B6EB41900C5D800 /* Extension+Iterator.swift in Sources */, 03B5166B2AE7315E00EA47E2 /* AlertsEventHandler.swift in Sources */, 0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */, 0736958D2B3B79E0007249BE /* StatsEventHandler.swift in Sources */, 03B516692AE7306B00EA47E2 /* StatusEventHandler.swift in Sources */, 032158B82ADDF8BF008D943B /* VPNManager.swift in Sources */, 0736958F2B3B8048007249BE /* PlatformMethodHandler.swift in Sources */, - D7A729302B6E44F200FFFF41 /* CommandClient.swift in Sources */, 03B516672AE6B93A00EA47E2 /* MethodHandler.swift in Sources */, 03B5166D2AE7325500EA47E2 /* LogsEventHandler.swift in Sources */, 03E392D02ADDF1BD000ADF15 /* FilePath.swift in Sources */, diff --git a/ios/Runner/Handlers/LogsEventHandler.swift b/ios/Runner/Handlers/LogsEventHandler.swift index 368f6e66..c1b86208 100644 --- a/ios/Runner/Handlers/LogsEventHandler.swift +++ b/ios/Runner/Handlers/LogsEventHandler.swift @@ -1,28 +1,72 @@ import Foundation import Combine +import Libcore -public class LogsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler { +class LogsEventHandler: NSObject, FlutterPlugin, FlutterStreamHandler, LibboxCommandClientHandlerProtocol { static let name = "\(Bundle.main.serviceIdentifier)/service.logs" private var channel: FlutterEventChannel? - private var cancellable: AnyCancellable? - + + private var commandClient: LibboxCommandClient? + private var events: FlutterEventSink? + private var maxLines: Int + private var logList: [String] = [] + + private var lock: NSLock = NSLock() + public static func register(with registrar: FlutterPluginRegistrar) { - let instance = LogsEventHandler() - instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger()) - instance.channel?.setStreamHandler(instance) + let instance = LogsEventHandler() + instance.channel = FlutterEventChannel(name: Self.name, binaryMessenger: registrar.messenger()) + instance.channel?.setStreamHandler(instance) + } + + init(maxLines: Int = 32) { + self.maxLines = maxLines + super.init() + let opts = LibboxCommandClientOptions() + opts.command = LibboxCommandLog + opts.statusInterval = Int64(2 * NSEC_PER_SEC) + commandClient = LibboxCommandClient(self, options: opts) + try? commandClient?.connect() } + deinit { + try? commandClient?.disconnect() + } + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { - VPNManager.shared.logClient?.connect() - cancellable = VPNManager.shared.logClient?.$logList.sink { [events] logsList in - events(logsList) - } + events(logList) + self.events = events return nil } - + public func onCancel(withArguments arguments: Any?) -> FlutterError? { - cancellable?.cancel() + events = nil return nil } + + func writeLog(_ message: String?) { + guard let message else { + return + } + lock.withLock { [self] in + if logList.count > maxLines { + logList.removeFirst() + } + logList.append(message) + DispatchQueue.main.async { [self] () in + events?(logList) + } + } + } +} + +extension LogsEventHandler { + public func clearLog() {} + public func connected() {} + public func disconnected(_ message: String?) {} + public func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) {} + public func updateClashMode(_ newMode: String?) {} + public func writeGroups(_ message: LibboxOutboundGroupIteratorProtocol?) {} + public func writeStatus(_ message: LibboxStatusMessage?) {} } diff --git a/ios/Runner/VPN/VPNManager.swift b/ios/Runner/VPN/VPNManager.swift index 0bb1d9c3..8bdff45b 100644 --- a/ios/Runner/VPN/VPNManager.swift +++ b/ios/Runner/VPN/VPNManager.swift @@ -39,9 +39,6 @@ class VPNManager: ObservableObject { @Published private(set) var upload: Int64 = 0 @Published private(set) var download: Int64 = 0 @Published private(set) var elapsedTime: TimeInterval = 0 - @Published private(set) var logList: [String] = [] - @Published private(set) var logCallback = false - private(set) var logClient: CommandClient? private var _connectTime: Date? private var connectTime: Date? { @@ -74,8 +71,6 @@ class VPNManager: ObservableObject { updateStats() elapsedTime = -1 * (connectTime?.timeIntervalSinceNow ?? 0) } - - logClient = CommandClient(.log) } deinit { @@ -91,7 +86,7 @@ class VPNManager: ObservableObject { do { try await loadVPNPreference() } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } @@ -112,7 +107,7 @@ class VPNManager: ObservableObject { try await newManager.loadFromPreferences() self.manager = newManager } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } @@ -122,7 +117,7 @@ class VPNManager: ObservableObject { try await manager.saveToPreferences() try await manager.loadFromPreferences() } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } @@ -162,7 +157,7 @@ class VPNManager: ObservableObject { } try await self?.loadVPNPreference() } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } }.store(in: &cancelBag) @@ -193,7 +188,7 @@ class VPNManager: ObservableObject { } } } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } } @@ -207,7 +202,7 @@ class VPNManager: ObservableObject { "DisableMemoryLimit": (disableMemoryLimit ? "YES" : "NO") as NSString, ]) } catch { - onServiceWriteLog(message: error.localizedDescription) + LogsEventHandler().writeLog(error.localizedDescription) } connectTime = .now } @@ -216,17 +211,4 @@ class VPNManager: ObservableObject { guard state == .connected else { return } manager.connection.stopVPNTunnel() } - - func onServiceWriteLog(message: String) { - logCallback = true - if logList.count > 300 { - logList.removeFirst() - } - logList.append(message) - } - - func onServiceResetLogs() { - logCallback = false - logList.removeAll() - } }