From 9e785b79847ae84bc6e420465339f284a93108ca Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Mon, 23 Jun 2025 22:05:59 +0200 Subject: [PATCH] proxy private tabs --- .../services/proxy_settings_replication.dart | 38 +++++++++--- .../proxy_settings_replication.g.dart | 2 +- .../tor/presentation/screens/tor_proxy.dart | 58 +++++++++++++++++++ .../user/data/models/general_settings.dart | 8 ++- .../user/data/models/general_settings.g.dart | 14 +++++ .../domain/repositories/general_settings.dart | 4 ++ .../repositories/general_settings.g.dart | 2 +- .../src/background/BackgroundMain.ts | 12 +++- .../container_proxy/src/background/index.ts | 1 + 9 files changed, 125 insertions(+), 14 deletions(-) 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 c115c227..3abff8e0 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 @@ -1,8 +1,10 @@ import 'package:fast_equatable/fast_equatable.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; +import 'package:nullability/nullability.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/tor/domain/services/tor_proxy.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; part 'proxy_settings_replication.g.dart'; @@ -25,21 +27,39 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { ref.listen( fireImmediately: true, containersWithCountProvider.select( - (value) => EquatableValue( - value.valueOrNull - ?.where((container) => container.metadata.useProxy) - .map((container) => container.metadata.contextualIdentity) - .nonNulls - .toList(), - ), + (value) => EquatableValue(value.valueOrNull), ), (previous, next) async { if (next.value != null) { - for (final contextId in next.value!) { - await _service.addContainerProxy(contextId); + for (final container in next.value!) { + if (container.metadata.contextualIdentity.isNotEmpty) { + if (container.metadata.useProxy) { + await _service.addContainerProxy( + container.metadata.contextualIdentity!, + ); + } else { + await _service.removeContainerProxy( + container.metadata.contextualIdentity!, + ); + } + } } } }, ); + + ref.listen( + fireImmediately: true, + generalSettingsRepositoryProvider.select( + (value) => value.proxyPrivateTabsTor, + ), + (previous, next) async { + if (next) { + await _service.addContainerProxy('private'); + } else { + await _service.removeContainerProxy('private'); + } + }, + ); } } 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 361184ef..6d00cceb 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 @@ -7,7 +7,7 @@ part of 'proxy_settings_replication.dart'; // ************************************************************************** String _$proxySettingsReplicationHash() => - r'9571d178534f68ad3104f1168665ffb57711c6cc'; + r'cd3a7573abfa0b32562289c02e77f0b44e88a8ae'; /// See also [ProxySettingsReplication]. @ProviderFor(ProxySettingsReplication) diff --git a/app/lib/features/tor/presentation/screens/tor_proxy.dart b/app/lib/features/tor/presentation/screens/tor_proxy.dart index 120d14d5..97d3c26f 100644 --- a/app/lib/features/tor/presentation/screens/tor_proxy.dart +++ b/app/lib/features/tor/presentation/screens/tor_proxy.dart @@ -2,7 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart'; import 'package:weblibre/features/tor/domain/services/tor_proxy.dart'; +import 'package:weblibre/features/user/data/models/general_settings.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/presentation/hooks/on_initialization.dart'; class TorProxyScreen extends HookConsumerWidget { @@ -11,6 +14,11 @@ class TorProxyScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final torProxyPort = ref.watch(torProxyServiceProvider); + final proxyPrivateTabsTor = ref.watch( + generalSettingsRepositoryProvider.select( + (value) => value.proxyPrivateTabsTor, + ), + ); useOnInitialization(() async { await ref.read(torProxyServiceProvider.notifier).requestSync(); @@ -77,6 +85,55 @@ class TorProxyScreen extends HookConsumerWidget { }, ), ), + ListTileTheme( + iconColor: Colors.white, + textColor: Colors.white, + child: SwitchListTile.adaptive( + inactiveThumbColor: Colors.white, + activeColor: const Color(0xFF68B030), + trackColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + if (states.isEmpty) { + return const Color(0xFF333A41); + } + return null; // Use the default color. + }), + trackOutlineColor: + WidgetStateProperty.resolveWith(( + Set states, + ) { + if (states.isEmpty) { + return Colors.white; + } + return null; // Use the default color. + }), + thumbIcon: WidgetStateProperty.resolveWith(( + Set states, + ) { + if (states.contains(WidgetState.selected)) { + return const Icon(MdiIcons.incognito); + } + return null; // Use the default color. + }), + value: proxyPrivateTabsTor, + title: const Text('Proxy Private Tabs'), + subtitle: const Text( + 'When enabled, all Private Tabs will be tunneled through Tor', + ), + secondary: const Icon(MdiIcons.arrowDecision), + onChanged: (value) async { + await ref + .read( + saveGeneralSettingsControllerProvider.notifier, + ) + .save( + (currentSettings) => currentSettings.copyWith + .proxyPrivateTabsTor(value), + ); + }, + ), + ), ], ), ), @@ -87,6 +144,7 @@ class TorProxyScreen extends HookConsumerWidget { const Column( mainAxisSize: MainAxisSize.min, children: [ + SizedBox(height: 8), LinearProgressIndicator( backgroundColor: Color(0xFF333A41), color: Color(0xFF68B030), diff --git a/app/lib/features/user/data/models/general_settings.dart b/app/lib/features/user/data/models/general_settings.dart index 2ff8a226..8886e5a0 100644 --- a/app/lib/features/user/data/models/general_settings.dart +++ b/app/lib/features/user/data/models/general_settings.dart @@ -28,12 +28,15 @@ class GeneralSettings with FastEquatable { final Set? deleteBrowsingDataOnQuit; final String defaultSearchProvider; + final bool proxyPrivateTabsTor; + GeneralSettings({ required this.themeMode, required this.enableReadability, required this.enforceReadability, required this.deleteBrowsingDataOnQuit, required this.defaultSearchProvider, + required this.proxyPrivateTabsTor, }); GeneralSettings.withDefaults({ @@ -42,10 +45,12 @@ class GeneralSettings with FastEquatable { bool? enforceReadability, this.deleteBrowsingDataOnQuit, String? defaultSearchProvider, + bool? proxyPrivateTabsTor, }) : themeMode = themeMode ?? ThemeMode.dark, enableReadability = enableReadability ?? true, enforceReadability = enforceReadability ?? false, - defaultSearchProvider = defaultSearchProvider ?? 'lais'; + defaultSearchProvider = defaultSearchProvider ?? 'lais', + proxyPrivateTabsTor = proxyPrivateTabsTor ?? false; factory GeneralSettings.fromJson(Map json) => _$GeneralSettingsFromJson(json); @@ -59,5 +64,6 @@ class GeneralSettings with FastEquatable { enforceReadability, deleteBrowsingDataOnQuit, defaultSearchProvider, + proxyPrivateTabsTor, ]; } diff --git a/app/lib/features/user/data/models/general_settings.g.dart b/app/lib/features/user/data/models/general_settings.g.dart index c4158b3e..c0f87819 100644 --- a/app/lib/features/user/data/models/general_settings.g.dart +++ b/app/lib/features/user/data/models/general_settings.g.dart @@ -19,6 +19,8 @@ abstract class _$GeneralSettingsCWProxy { GeneralSettings defaultSearchProvider(String defaultSearchProvider); + GeneralSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor); + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// /// Usage @@ -31,6 +33,7 @@ abstract class _$GeneralSettingsCWProxy { bool enforceReadability, Set? deleteBrowsingDataOnQuit, String defaultSearchProvider, + bool proxyPrivateTabsTor, }); } @@ -60,6 +63,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { GeneralSettings defaultSearchProvider(String defaultSearchProvider) => this(defaultSearchProvider: defaultSearchProvider); + @override + GeneralSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor) => + this(proxyPrivateTabsTor: proxyPrivateTabsTor); + @override /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// @@ -73,6 +80,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { Object? enforceReadability = const $CopyWithPlaceholder(), Object? deleteBrowsingDataOnQuit = const $CopyWithPlaceholder(), Object? defaultSearchProvider = const $CopyWithPlaceholder(), + Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(), }) { return GeneralSettings( themeMode: themeMode == const $CopyWithPlaceholder() @@ -97,6 +105,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { ? _value.defaultSearchProvider // ignore: cast_nullable_to_non_nullable : defaultSearchProvider as String, + proxyPrivateTabsTor: proxyPrivateTabsTor == const $CopyWithPlaceholder() + ? _value.proxyPrivateTabsTor + // ignore: cast_nullable_to_non_nullable + : proxyPrivateTabsTor as bool, ); } } @@ -121,6 +133,7 @@ GeneralSettings _$GeneralSettingsFromJson(Map json) => ?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e)) .toSet(), defaultSearchProvider: json['defaultSearchProvider'] as String?, + proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?, ); Map _$GeneralSettingsToJson(GeneralSettings instance) => @@ -132,6 +145,7 @@ Map _$GeneralSettingsToJson(GeneralSettings instance) => ?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!) .toList(), 'defaultSearchProvider': instance.defaultSearchProvider, + 'proxyPrivateTabsTor': instance.proxyPrivateTabsTor, }; const _$ThemeModeEnumMap = { diff --git a/app/lib/features/user/domain/repositories/general_settings.dart b/app/lib/features/user/domain/repositories/general_settings.dart index 828e1796..96b0e076 100644 --- a/app/lib/features/user/domain/repositories/general_settings.dart +++ b/app/lib/features/user/domain/repositories/general_settings.dart @@ -43,6 +43,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository { DriftSqlType.string, db.typeMapping, ), + 'proxyPrivateTabsTor': settings['proxyPrivateTabsTor']?.readAs( + DriftSqlType.bool, + db.typeMapping, + ), }); } diff --git a/app/lib/features/user/domain/repositories/general_settings.g.dart b/app/lib/features/user/domain/repositories/general_settings.g.dart index 81a77e4f..11ac706f 100644 --- a/app/lib/features/user/domain/repositories/general_settings.g.dart +++ b/app/lib/features/user/domain/repositories/general_settings.g.dart @@ -7,7 +7,7 @@ part of 'general_settings.dart'; // ************************************************************************** String _$generalSettingsRepositoryHash() => - r'2dcc600ccfb074fa27fae90acb3d61e10c87e884'; + r'e16ce010f5feb692bd9f3a394c9502f959ded589'; /// See also [GeneralSettingsRepository]. @ProviderFor(GeneralSettingsRepository) diff --git a/packages/flutter_mozilla_components/javascript/container_proxy/src/background/BackgroundMain.ts b/packages/flutter_mozilla_components/javascript/container_proxy/src/background/BackgroundMain.ts index c23ba77b..06665597 100644 --- a/packages/flutter_mozilla_components/javascript/container_proxy/src/background/BackgroundMain.ts +++ b/packages/flutter_mozilla_components/javascript/container_proxy/src/background/BackgroundMain.ts @@ -9,6 +9,7 @@ import _OnRequestDetails = browser.proxy._OnRequestDetails const localhosts = new Set(['localhost', '127.0.0.1', '[::1]']) const containerIdentifier = 'firefox-container-' +const privateIdentifier = 'firefox-private' type DoNotProxy = never[] export const doNotProxy: DoNotProxy = [] @@ -59,9 +60,16 @@ export default class BackgroundMain { if (requestDetails.tabId > -1) { const tab = (await browser.tabs.get(requestDetails.tabId)) - if (tab.cookieStoreId?.startsWith(containerIdentifier) === true) { + if (tab.cookieStoreId?.startsWith(containerIdentifier) === true || tab.cookieStoreId === privateIdentifier) { try { - const cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length) + let cookieStoreId: string + + if (tab.cookieStoreId.startsWith(containerIdentifier)) { + cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length) + } else { + // Handle private tabs - use 'private' as identifier + cookieStoreId = 'private' + } const proxies = await this.store.getProxiesForContainer(cookieStoreId) diff --git a/packages/flutter_mozilla_components/javascript/container_proxy/src/background/index.ts b/packages/flutter_mozilla_components/javascript/container_proxy/src/background/index.ts index 6fb845cd..1acf1c37 100644 --- a/packages/flutter_mozilla_components/javascript/container_proxy/src/background/index.ts +++ b/packages/flutter_mozilla_components/javascript/container_proxy/src/background/index.ts @@ -35,6 +35,7 @@ port.onMessage.addListener((raw: unknown): void => { break case "removeContainerProxy": store.removeContainerProxyRelation(message.args, "tor") + console.log('removed container relation ' + message.args) break case "healthcheck": port.postMessage({