diff --git a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart index 1592eb64..7836f90d 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart @@ -18,21 +18,20 @@ * along with this program. If not, see . */ import 'package:fast_equatable/fast_equatable.dart'; -import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:nullability/nullability.dart'; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:weblibre/core/logger.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; +import 'package:weblibre/features/tor/domain/repositories/tor_proxy.dart'; import 'package:weblibre/features/tor/domain/services/tor_proxy.dart'; +import 'package:weblibre/features/user/data/models/tor_settings.dart'; import 'package:weblibre/features/user/domain/repositories/tor_settings.dart'; part 'proxy_settings_replication.g.dart'; @Riverpod(keepAlive: true) class ProxySettingsReplication extends _$ProxySettingsReplication { - final _service = GeckoContainerProxyService(); - @override void build() { ref.listen( @@ -40,7 +39,9 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { torProxyServiceProvider.select((data) => data.value), (previous, next) async { if (next != null) { - await _service.setProxyPort(next); + await ref + .read(torProxyRepositoryProvider.notifier) + .setProxyPort(next); } }, onError: (error, stackTrace) { @@ -62,13 +63,15 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { for (final container in next.value!) { if (container.metadata.contextualIdentity.isNotEmpty) { if (container.metadata.useProxy) { - await _service.addContainerProxy( - container.metadata.contextualIdentity!, - ); + await ref + .read(torProxyRepositoryProvider.notifier) + .addContainerProxy(container.metadata.contextualIdentity!); } else { - await _service.removeContainerProxy( - container.metadata.contextualIdentity!, - ); + await ref + .read(torProxyRepositoryProvider.notifier) + .removeContainerProxy( + container.metadata.contextualIdentity!, + ); } } } @@ -90,9 +93,39 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { ), (previous, next) async { if (next) { - await _service.addContainerProxy('private'); + await ref + .read(torProxyRepositoryProvider.notifier) + .addContainerProxy('private'); } else { - await _service.removeContainerProxy('private'); + await ref + .read(torProxyRepositoryProvider.notifier) + .removeContainerProxy('private'); + } + }, + onError: (error, stackTrace) { + logger.e( + 'Error listening to generalSettingsRepositoryProvider', + error: error, + stackTrace: stackTrace, + ); + }, + ); + + ref.listen( + fireImmediately: true, + torSettingsWithDefaultsProvider.select( + (value) => value.proxyRegularTabsMode, + ), + (previous, next) async { + switch (next) { + case TorRegularTabProxyMode.container: + await ref + .read(torProxyRepositoryProvider.notifier) + .removeContainerProxy('general'); + case TorRegularTabProxyMode.all: + await ref + .read(torProxyRepositoryProvider.notifier) + .addContainerProxy('general'); } }, onError: (error, stackTrace) { diff --git a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.g.dart b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.g.dart index a81d6a89..09a37e60 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.g.dart @@ -42,7 +42,7 @@ final class ProxySettingsReplicationProvider } String _$proxySettingsReplicationHash() => - r'787545f882b97274dc67007a6404f170d40bdb64'; + r'46063662e3aca4f38a6876f4265ada9a87bde6dc'; abstract class _$ProxySettingsReplication extends $Notifier { void build(); diff --git a/app/lib/features/tor/domain/repositories/tor_proxy.dart b/app/lib/features/tor/domain/repositories/tor_proxy.dart new file mode 100644 index 00000000..28af0dfa --- /dev/null +++ b/app/lib/features/tor/domain/repositories/tor_proxy.dart @@ -0,0 +1,58 @@ +import 'dart:async'; + +import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:synchronized/synchronized.dart'; + +part 'tor_proxy.g.dart'; + +@Riverpod(keepAlive: true) +class TorProxyRepository extends _$TorProxyRepository { + final _service = GeckoContainerProxyService(); + final _serviceLock = Lock(); + + Future setProxyPort(int port) { + return _serviceLock.synchronized(() async { + await _waitHealthcheck().timeout(const Duration(seconds: 10)); + + return _service.setProxyPort(port); + }); + } + + Future addContainerProxy(String contextId) { + return _serviceLock.synchronized(() async { + await _waitHealthcheck().timeout(const Duration(seconds: 10)); + + return _service.addContainerProxy(contextId); + }); + } + + Future removeContainerProxy(String contextId) { + return _serviceLock.synchronized(() async { + await _waitHealthcheck().timeout(const Duration(seconds: 10)); + + return _service.removeContainerProxy(contextId); + }); + } + + Future _waitHealthcheck({ + Duration timeout = const Duration(seconds: 15), + }) async { + final startTime = DateTime.now(); + + var healthy = await _service.healthcheck(); + while (!healthy) { + if (DateTime.now().difference(startTime) > timeout) { + throw TimeoutException('Timed out waiting for proxy service'); + } + + await Future.delayed(const Duration(milliseconds: 25)); + healthy = await _service.healthcheck(); + } + } + + @override + void build() { + return; + } +} diff --git a/app/lib/features/tor/domain/repositories/tor_proxy.g.dart b/app/lib/features/tor/domain/repositories/tor_proxy.g.dart new file mode 100644 index 00000000..adabf52c --- /dev/null +++ b/app/lib/features/tor/domain/repositories/tor_proxy.g.dart @@ -0,0 +1,64 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tor_proxy.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(TorProxyRepository) +const torProxyRepositoryProvider = TorProxyRepositoryProvider._(); + +final class TorProxyRepositoryProvider + extends $NotifierProvider { + const TorProxyRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'torProxyRepositoryProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$torProxyRepositoryHash(); + + @$internal + @override + TorProxyRepository create() => TorProxyRepository(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(void value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$torProxyRepositoryHash() => + r'dd565140620a1f66090e84e541d5ea8e34ab0094'; + +abstract class _$TorProxyRepository extends $Notifier { + void build(); + @$mustCallSuper + @override + void runBuild() { + build(); + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + void, + Object?, + Object? + >; + element.handleValue(ref, null); + } +} diff --git a/app/lib/features/user/data/models/tor_settings.dart b/app/lib/features/user/data/models/tor_settings.dart index 9b9731b7..7bd4c83d 100644 --- a/app/lib/features/user/data/models/tor_settings.dart +++ b/app/lib/features/user/data/models/tor_settings.dart @@ -25,15 +25,19 @@ part 'tor_settings.g.dart'; enum TorConnectionConfig { auto, direct, obfs4, snowflake } +enum TorRegularTabProxyMode { container, all } + @CopyWith() @JsonSerializable(includeIfNull: true, constructor: 'withDefaults') class TorSettings with FastEquatable { + final TorRegularTabProxyMode proxyRegularTabsMode; final bool proxyPrivateTabsTor; final TorConnectionConfig config; final bool requireBridge; final bool fetchRemoteBridges; TorSettings({ + required this.proxyRegularTabsMode, required this.proxyPrivateTabsTor, required this.config, required this.requireBridge, @@ -41,11 +45,14 @@ class TorSettings with FastEquatable { }); TorSettings.withDefaults({ + TorRegularTabProxyMode? proxyRegularTabsMode, bool? proxyPrivateTabsTor, TorConnectionConfig? config, bool? requireBridge, bool? fetchRemoteBridges, - }) : proxyPrivateTabsTor = proxyPrivateTabsTor ?? false, + }) : proxyRegularTabsMode = + proxyRegularTabsMode ?? TorRegularTabProxyMode.container, + proxyPrivateTabsTor = proxyPrivateTabsTor ?? false, config = config ?? TorConnectionConfig.auto, requireBridge = requireBridge ?? false, fetchRemoteBridges = fetchRemoteBridges ?? true; @@ -57,6 +64,7 @@ class TorSettings with FastEquatable { @override List get hashParameters => [ + proxyRegularTabsMode, proxyPrivateTabsTor, config, requireBridge, diff --git a/app/lib/features/user/data/models/tor_settings.g.dart b/app/lib/features/user/data/models/tor_settings.g.dart index 9101086a..22a7a6e2 100644 --- a/app/lib/features/user/data/models/tor_settings.g.dart +++ b/app/lib/features/user/data/models/tor_settings.g.dart @@ -7,6 +7,8 @@ part of 'tor_settings.dart'; // ************************************************************************** abstract class _$TorSettingsCWProxy { + TorSettings proxyRegularTabsMode(TorRegularTabProxyMode proxyRegularTabsMode); + TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor); TorSettings config(TorConnectionConfig config); @@ -23,6 +25,7 @@ abstract class _$TorSettingsCWProxy { /// TorSettings(...).copyWith(id: 12, name: "My name") /// ``` TorSettings call({ + TorRegularTabProxyMode proxyRegularTabsMode, bool proxyPrivateTabsTor, TorConnectionConfig config, bool requireBridge, @@ -37,6 +40,11 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy { final TorSettings _value; + @override + TorSettings proxyRegularTabsMode( + TorRegularTabProxyMode proxyRegularTabsMode, + ) => call(proxyRegularTabsMode: proxyRegularTabsMode); + @override TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor) => call(proxyPrivateTabsTor: proxyPrivateTabsTor); @@ -61,12 +69,19 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy { /// TorSettings(...).copyWith(id: 12, name: "My name") /// ``` TorSettings call({ + Object? proxyRegularTabsMode = const $CopyWithPlaceholder(), Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(), Object? config = const $CopyWithPlaceholder(), Object? requireBridge = const $CopyWithPlaceholder(), Object? fetchRemoteBridges = const $CopyWithPlaceholder(), }) { return TorSettings( + proxyRegularTabsMode: + proxyRegularTabsMode == const $CopyWithPlaceholder() || + proxyRegularTabsMode == null + ? _value.proxyRegularTabsMode + // ignore: cast_nullable_to_non_nullable + : proxyRegularTabsMode as TorRegularTabProxyMode, proxyPrivateTabsTor: proxyPrivateTabsTor == const $CopyWithPlaceholder() || proxyPrivateTabsTor == null @@ -105,6 +120,10 @@ extension $TorSettingsCopyWith on TorSettings { TorSettings _$TorSettingsFromJson(Map json) => TorSettings.withDefaults( + proxyRegularTabsMode: $enumDecodeNullable( + _$TorRegularTabProxyModeEnumMap, + json['proxyRegularTabsMode'], + ), proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?, config: $enumDecodeNullable(_$TorConnectionConfigEnumMap, json['config']), requireBridge: json['requireBridge'] as bool?, @@ -113,12 +132,19 @@ TorSettings _$TorSettingsFromJson(Map json) => Map _$TorSettingsToJson(TorSettings instance) => { + 'proxyRegularTabsMode': + _$TorRegularTabProxyModeEnumMap[instance.proxyRegularTabsMode]!, 'proxyPrivateTabsTor': instance.proxyPrivateTabsTor, 'config': _$TorConnectionConfigEnumMap[instance.config]!, 'requireBridge': instance.requireBridge, 'fetchRemoteBridges': instance.fetchRemoteBridges, }; +const _$TorRegularTabProxyModeEnumMap = { + TorRegularTabProxyMode.container: 'container', + TorRegularTabProxyMode.all: 'all', +}; + const _$TorConnectionConfigEnumMap = { TorConnectionConfig.auto: 'auto', TorConnectionConfig.direct: 'direct',