From cf89e2ad809c3c08ba42ad2014068a2e0aec87fa Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Sat, 17 May 2025 19:48:27 +0200 Subject: [PATCH] custom user agent --- .../screens/web_engine_settings.dart | 58 +++++++++++++++++++ .../user/data/models/engine_settings.dart | 3 + .../user/data/models/engine_settings.g.dart | 14 +++++ .../domain/repositories/engine_settings.dart | 4 ++ .../repositories/engine_settings.g.dart | 2 +- .../api/GeckoEngineSettingsApiImpl.kt | 7 +++ .../pigeons/Gecko.g.kt | 7 ++- .../lib/src/pigeons/gecko.g.dart | 5 ++ .../pigeons/gecko.dart | 2 + 9 files changed, 99 insertions(+), 3 deletions(-) diff --git a/app/lib/features/settings/presentation/screens/web_engine_settings.dart b/app/lib/features/settings/presentation/screens/web_engine_settings.dart index 4ae0293e..590dcb00 100644 --- a/app/lib/features/settings/presentation/screens/web_engine_settings.dart +++ b/app/lib/features/settings/presentation/screens/web_engine_settings.dart @@ -1,7 +1,9 @@ import 'package:fading_scroll/fading_scroll.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:lensai/core/routing/routes.dart'; import 'package:lensai/extensions/nullable.dart'; @@ -10,6 +12,7 @@ import 'package:lensai/features/user/data/models/engine_settings.dart'; import 'package:lensai/features/user/data/models/general_settings.dart'; import 'package:lensai/features/user/domain/repositories/engine_settings.dart'; import 'package:lensai/features/user/domain/repositories/general_settings.dart'; +import 'package:universal_io/io.dart'; class WebEngineSettingsScreen extends HookConsumerWidget { const WebEngineSettingsScreen(); @@ -19,6 +22,11 @@ class WebEngineSettingsScreen extends HookConsumerWidget { final generalSettings = ref.watch(generalSettingsRepositoryProvider); final engineSettings = ref.watch(engineSettingsRepositoryProvider); + final userAgentTextController = useTextEditingController( + text: engineSettings.userAgent, + keys: [engineSettings.userAgent], + ); + return Scaffold( appBar: AppBar(title: const Text('Web Engine Settings')), body: FadingScroll( @@ -297,6 +305,56 @@ class WebEngineSettingsScreen extends HookConsumerWidget { ], ), ), + ListTile( + leading: const Icon(MdiIcons.cardAccountDetails), + title: TextField( + controller: userAgentTextController, + decoration: const InputDecoration( + labelText: 'Custom User Agent', + floatingLabelBehavior: FloatingLabelBehavior.always, + hintText: 'Mozilla/5.0 …', + ), + onSubmitted: (value) async { + await ref + .read(saveEngineSettingsControllerProvider.notifier) + .save( + (currentSettings) => + currentSettings.copyWith.userAgent(value), + ); + + if (context.mounted) { + final restart = await showDialog( + context: context, + builder: + (context) => AlertDialog( + title: const Text('User Agent Changed'), + content: const Text( + 'The app needs to restart for the new user agent to take effect', + ), + actions: [ + TextButton( + onPressed: () { + context.pop(false); + }, + child: const Text('Later'), + ), + TextButton( + onPressed: () { + context.pop(true); + }, + child: const Text('Restart Now'), + ), + ], + ), + ); + + if (restart == true) { + exit(0); + } + } + }, + ), + ), ListTile( title: const Text('Web Engine Hardening'), contentPadding: const EdgeInsets.symmetric( diff --git a/app/lib/features/user/data/models/engine_settings.dart b/app/lib/features/user/data/models/engine_settings.dart index c32428f4..c1dbf310 100644 --- a/app/lib/features/user/data/models/engine_settings.dart +++ b/app/lib/features/user/data/models/engine_settings.dart @@ -46,6 +46,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable { required super.cookieBannerHandlingGlobalRules, required super.cookieBannerHandlingGlobalRulesSubFrames, required super.webContentIsolationStrategy, + required super.userAgent, }); EngineSettings.withDefaults({ @@ -59,6 +60,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable { bool? cookieBannerHandlingGlobalRules, bool? cookieBannerHandlingGlobalRulesSubFrames, WebContentIsolationStrategy? webContentIsolationStrategy, + super.userAgent, }) : super( javascriptEnabled: javascriptEnabled ?? true, trackingProtectionPolicy: @@ -97,5 +99,6 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable { super.cookieBannerHandlingGlobalRules, super.cookieBannerHandlingGlobalRulesSubFrames, super.webContentIsolationStrategy, + super.userAgent, ]; } diff --git a/app/lib/features/user/data/models/engine_settings.g.dart b/app/lib/features/user/data/models/engine_settings.g.dart index c83b1be0..24ae1bce 100644 --- a/app/lib/features/user/data/models/engine_settings.g.dart +++ b/app/lib/features/user/data/models/engine_settings.g.dart @@ -39,6 +39,8 @@ abstract class _$EngineSettingsCWProxy { WebContentIsolationStrategy? webContentIsolationStrategy, ); + EngineSettings userAgent(String? userAgent); + /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `EngineSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// /// Usage @@ -56,6 +58,7 @@ abstract class _$EngineSettingsCWProxy { bool? cookieBannerHandlingGlobalRules, bool? cookieBannerHandlingGlobalRulesSubFrames, WebContentIsolationStrategy? webContentIsolationStrategy, + String? userAgent, }); } @@ -118,6 +121,9 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy { WebContentIsolationStrategy? webContentIsolationStrategy, ) => this(webContentIsolationStrategy: webContentIsolationStrategy); + @override + EngineSettings userAgent(String? userAgent) => this(userAgent: userAgent); + @override /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `EngineSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// @@ -138,6 +144,7 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy { Object? cookieBannerHandlingGlobalRulesSubFrames = const $CopyWithPlaceholder(), Object? webContentIsolationStrategy = const $CopyWithPlaceholder(), + Object? userAgent = const $CopyWithPlaceholder(), }) { return EngineSettings( javascriptEnabled: @@ -193,6 +200,11 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy { ? _value.webContentIsolationStrategy // ignore: cast_nullable_to_non_nullable : webContentIsolationStrategy as WebContentIsolationStrategy?, + userAgent: + userAgent == const $CopyWithPlaceholder() + ? _value.userAgent + // ignore: cast_nullable_to_non_nullable + : userAgent as String?, ); } } @@ -239,11 +251,13 @@ EngineSettings _$EngineSettingsFromJson(Map json) => _$WebContentIsolationStrategyEnumMap, json['webContentIsolationStrategy'], ), + userAgent: json['userAgent'] as String?, ); Map _$EngineSettingsToJson( EngineSettings instance, ) => { + 'userAgent': instance.userAgent, 'javascriptEnabled': instance.javascriptEnabled, 'trackingProtectionPolicy': _$TrackingProtectionPolicyEnumMap[instance.trackingProtectionPolicy]!, diff --git a/app/lib/features/user/domain/repositories/engine_settings.dart b/app/lib/features/user/domain/repositories/engine_settings.dart index c1f1b25e..19b2db58 100644 --- a/app/lib/features/user/domain/repositories/engine_settings.dart +++ b/app/lib/features/user/domain/repositories/engine_settings.dart @@ -82,6 +82,10 @@ class EngineSettingsRepository extends _$EngineSettingsRepository { DriftSqlType.string, db.typeMapping, ), + 'userAgent': settings['userAgent']?.readAs( + DriftSqlType.string, + db.typeMapping, + ), }); }); diff --git a/app/lib/features/user/domain/repositories/engine_settings.g.dart b/app/lib/features/user/domain/repositories/engine_settings.g.dart index 23779dfb..ff71782f 100644 --- a/app/lib/features/user/domain/repositories/engine_settings.g.dart +++ b/app/lib/features/user/domain/repositories/engine_settings.g.dart @@ -7,7 +7,7 @@ part of 'engine_settings.dart'; // ************************************************************************** String _$engineSettingsRepositoryHash() => - r'bd255f91f7cee702d9cfcb10bfdf3cd013234901'; + r'87e308c8bd4390d1d5c401532a0c35cd96f6176e'; /// See also [EngineSettingsRepository]. @ProviderFor(EngineSettingsRepository) diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt index d3c4e8f7..983b4d1a 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt @@ -103,6 +103,9 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi { WebContentIsolationStrategy.ISOLATE_HIGH_VALUE -> mozilla.components.concept.engine.fission.WebContentIsolationStrategy.ISOLATE_HIGH_VALUE } } + if(settings.userAgent != null) { + components.core.engineSettings.userAgentString = settings.userAgent; + } } override fun updateRuntimeSettings(settings: GeckoEngineSettings) { @@ -149,6 +152,10 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi { if(settings.webContentIsolationStrategy != null) { components.core.engine.settings.webContentIsolationStrategy = components.core.engineSettings.webContentIsolationStrategy } + if(settings.userAgent != null) { + components.core.engine.settings.userAgentString = components.core.engineSettings.userAgentString + reloadSession = true + } if(reloadSession) { components.useCases.sessionUseCases.reload() diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt index 82574621..053b9ff5 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt @@ -1451,7 +1451,8 @@ data class GeckoEngineSettings ( val cookieBannerHandlingModePrivateBrowsing: CookieBannerHandlingMode? = null, val cookieBannerHandlingGlobalRules: Boolean? = null, val cookieBannerHandlingGlobalRulesSubFrames: Boolean? = null, - val webContentIsolationStrategy: WebContentIsolationStrategy? = null + val webContentIsolationStrategy: WebContentIsolationStrategy? = null, + val userAgent: String? = null ) { companion object { @@ -1466,7 +1467,8 @@ data class GeckoEngineSettings ( val cookieBannerHandlingGlobalRules = pigeonVar_list[7] as Boolean? val cookieBannerHandlingGlobalRulesSubFrames = pigeonVar_list[8] as Boolean? val webContentIsolationStrategy = pigeonVar_list[9] as WebContentIsolationStrategy? - return GeckoEngineSettings(javascriptEnabled, trackingProtectionPolicy, httpsOnlyMode, globalPrivacyControlEnabled, preferredColorScheme, cookieBannerHandlingMode, cookieBannerHandlingModePrivateBrowsing, cookieBannerHandlingGlobalRules, cookieBannerHandlingGlobalRulesSubFrames, webContentIsolationStrategy) + val userAgent = pigeonVar_list[10] as String? + return GeckoEngineSettings(javascriptEnabled, trackingProtectionPolicy, httpsOnlyMode, globalPrivacyControlEnabled, preferredColorScheme, cookieBannerHandlingMode, cookieBannerHandlingModePrivateBrowsing, cookieBannerHandlingGlobalRules, cookieBannerHandlingGlobalRulesSubFrames, webContentIsolationStrategy, userAgent) } } fun toList(): List { @@ -1481,6 +1483,7 @@ data class GeckoEngineSettings ( cookieBannerHandlingGlobalRules, cookieBannerHandlingGlobalRulesSubFrames, webContentIsolationStrategy, + userAgent, ) } override fun equals(other: Any?): Boolean { diff --git a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart index 392cb39d..b90ee4d6 100644 --- a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart +++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart @@ -1790,6 +1790,7 @@ class GeckoEngineSettings { this.cookieBannerHandlingGlobalRules, this.cookieBannerHandlingGlobalRulesSubFrames, this.webContentIsolationStrategy, + this.userAgent, }); bool? javascriptEnabled; @@ -1812,6 +1813,8 @@ class GeckoEngineSettings { WebContentIsolationStrategy? webContentIsolationStrategy; + String? userAgent; + List _toList() { return [ javascriptEnabled, @@ -1824,6 +1827,7 @@ class GeckoEngineSettings { cookieBannerHandlingGlobalRules, cookieBannerHandlingGlobalRulesSubFrames, webContentIsolationStrategy, + userAgent, ]; } @@ -1843,6 +1847,7 @@ class GeckoEngineSettings { cookieBannerHandlingGlobalRules: result[7] as bool?, cookieBannerHandlingGlobalRulesSubFrames: result[8] as bool?, webContentIsolationStrategy: result[9] as WebContentIsolationStrategy?, + userAgent: result[10] as String?, ); } diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart index e8e82aab..84f403d5 100644 --- a/packages/flutter_mozilla_components/pigeons/gecko.dart +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -549,6 +549,7 @@ class GeckoEngineSettings { final bool? cookieBannerHandlingGlobalRules; final bool? cookieBannerHandlingGlobalRulesSubFrames; final WebContentIsolationStrategy? webContentIsolationStrategy; + final String? userAgent; GeckoEngineSettings( this.javascriptEnabled, @@ -561,6 +562,7 @@ class GeckoEngineSettings { this.cookieBannerHandlingGlobalRules, this.cookieBannerHandlingGlobalRulesSubFrames, this.webContentIsolationStrategy, + this.userAgent, ); }