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