proxy private tabs

This commit is contained in:
Fabian Freund
2025-06-23 22:05:59 +02:00
parent 9c6c50b0da
commit 9e785b7984
9 changed files with 125 additions and 14 deletions
@@ -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');
}
},
);
}
}
@@ -7,7 +7,7 @@ part of 'proxy_settings_replication.dart';
// **************************************************************************
String _$proxySettingsReplicationHash() =>
r'9571d178534f68ad3104f1168665ffb57711c6cc';
r'cd3a7573abfa0b32562289c02e77f0b44e88a8ae';
/// See also [ProxySettingsReplication].
@ProviderFor(ProxySettingsReplication)
@@ -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<Color?>((
Set<WidgetState> states,
) {
if (states.isEmpty) {
return const Color(0xFF333A41);
}
return null; // Use the default color.
}),
trackOutlineColor:
WidgetStateProperty.resolveWith<Color?>((
Set<WidgetState> states,
) {
if (states.isEmpty) {
return Colors.white;
}
return null; // Use the default color.
}),
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((
Set<WidgetState> 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),
@@ -28,12 +28,15 @@ class GeneralSettings with FastEquatable {
final Set<DeleteBrowsingDataType>? 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<String, dynamic> json) =>
_$GeneralSettingsFromJson(json);
@@ -59,5 +64,6 @@ class GeneralSettings with FastEquatable {
enforceReadability,
deleteBrowsingDataOnQuit,
defaultSearchProvider,
proxyPrivateTabsTor,
];
}
@@ -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<DeleteBrowsingDataType>? 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<String, dynamic> json) =>
?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e))
.toSet(),
defaultSearchProvider: json['defaultSearchProvider'] as String?,
proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?,
);
Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) =>
@@ -132,6 +145,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) =>
?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!)
.toList(),
'defaultSearchProvider': instance.defaultSearchProvider,
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
};
const _$ThemeModeEnumMap = {
@@ -43,6 +43,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
DriftSqlType.string,
db.typeMapping,
),
'proxyPrivateTabsTor': settings['proxyPrivateTabsTor']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
});
}
@@ -7,7 +7,7 @@ part of 'general_settings.dart';
// **************************************************************************
String _$generalSettingsRepositoryHash() =>
r'2dcc600ccfb074fa27fae90acb3d61e10c87e884';
r'e16ce010f5feb692bd9f3a394c9502f959ded589';
/// See also [GeneralSettingsRepository].
@ProviderFor(GeneralSettingsRepository)
@@ -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)
@@ -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({