tor push
This commit is contained in:
@@ -5,6 +5,7 @@ gradle-wrapper.jar
|
|||||||
/gradlew.bat
|
/gradlew.bat
|
||||||
/local.properties
|
/local.properties
|
||||||
GeneratedPluginRegistrant.java
|
GeneratedPluginRegistrant.java
|
||||||
|
.cxx
|
||||||
|
|
||||||
# Remember to never publicly share your keystore.
|
# Remember to never publicly share your keystore.
|
||||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
@@ -176,6 +179,10 @@
|
|||||||
android:foregroundServiceType="mediaPlayback"
|
android:foregroundServiceType="mediaPlayback"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="id.flutter.flutter_background_service.BackgroundService"
|
||||||
|
android:foregroundServiceType="specialUse|remoteMessaging" />
|
||||||
|
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.3.1" apply false
|
id "com.android.application" version "8.7.0" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.9.22" apply false
|
id "org.jetbrains.kotlin.android" version "1.9.22" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 150.5 90.7" enable-background="new 0 0 150.5 90.7" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path fill="#FFFFFF" d="M85.7,71.3c0.1-1.8-0.7-4.6-1.5-7.3c-0.5-2-2.4-4-2.9-5.5c-0.9-2.5-0.5-4.3-0.6-7.8
|
||||||
|
c0.4,3.2,0.4,6,1.2,7.3c0.7,1.3,1.9,3.1,2.6,4.9c1.3,3.3,2.1,6.9,2.2,8.6c0.1,2.9-0.3,7.5-1.3,11c-0.4,1.3-0.9,2.5-1.5,3.6
|
||||||
|
c1.3-1.9,2.2-4,2.6-5.7c1.3-6.1,2.4-12.6,1.5-16.4c-0.2-0.9-0.5-3.8-1.7-7.1c-1.8-4.6-4.2-8.5-4.5-9.4c-0.9-2.6-1.2-9.1-1.3-12.6
|
||||||
|
c0.1,4.2,0.9,10.3,1.5,11.7c0.3,0.9,3,5.2,4.9,9.7c1.3,3,1.6,5.7,1.8,6.5c1,8.4,0.1,11.9-2,19.4c-0.3,1.1-0.8,2.1-1.3,3.1
|
||||||
|
c1.5-1.8,2.6-3.7,3-5.3c2.2-9.4,2.9-21.4-0.1-30.5c-1.5-4.8-5.2-9-6.1-11.9c-1-3.4-0.3-8.9-0.3-9l-8.1-3.6
|
||||||
|
c2.1,5.4,2.5,9.6,0.3,11.4c-8.5,7-22.7,14.9-22.7,26.7c0,12.5,7.4,26,26.5,27c-2.3-0.6-4.5-1.3-6.4-2.1c-1.9-0.8-3.6-1.9-4.9-3.4
|
||||||
|
c0-0.1-0.5-0.5-0.5-0.5c-2.8-3.2-6.3-8.6-7.5-13.7c-0.5-2-0.9-3.6-0.3-5.7c2.3-8.2,7.3-11.5,12.3-14.9c1.1-0.7,2.5-1.4,3.6-2.3
|
||||||
|
c2.1-1.5,3.2-6.3,4.4-10.1c-0.6,3.1-1.3,8.7-4,10.8c-1.1,0.8-2.3,1.6-3.5,2.4c-4.8,3.3-9.7,6.4-11.9,14.3
|
||||||
|
c-0.5,1.7-0.1,3.5,0.3,5.2c1.2,4.9,4.6,10.1,7.3,13.2c0,0.1,0.5,0.5,0.5,0.6c1.8,2.1,2.6,3,7.3,4.3c-1.1-0.6-2-1.3-2.8-1.8
|
||||||
|
c-4.8-3.3-7.2-7.4-7.6-13.5c-0.3-4.7,2.6-9.7,8.3-12.7c3.9-2,5-5.2,6.3-9c-0.9,4.3-1.8,7-5.9,9.4c-4.8,2.8-8.2,8.2-7.9,11.9
|
||||||
|
c0.4,4.5,3.6,9.7,7.1,12.8c1.5,1.3,3.8,2.1,6,2.7c-0.6-0.5-1.2-1.1-1.8-1.9c-0.7-1-1.2-2.4-1.7-3.7c-0.5-1.3-1-2.6-1-3.8
|
||||||
|
c0-2.9,0.3-6.5,2.4-8.9c1.9-2.2,2.5-2.5,3.3-5.1c-0.6,3-1,3.2-2.8,5.7c-2.3,3.2-2.4,6.4-2.1,8.4c0.2,1.3,0.7,2.5,1.1,3.5
|
||||||
|
c0.5,1.3,1.1,2.5,1.9,3.6c0.7,0.8,1,1.4,2.1,1.8c1.1-2,1.8-6.2,1.9-8.6c0.1-2,0.2-4.3-0.1-6.6c-0.4-3.1-1.4-6.1-1.4-8.5
|
||||||
|
c0.5,2.2,1.6,5.2,2.3,8.3c0.5,2.4,0.6,4.8,0.4,6.5c-0.2,2.1-0.3,3.8-0.6,5.6c-0.2,1.2-0.8,2.4-1.7,3.5c2.2-1.7,3.6-4.3,4.2-7.4
|
||||||
|
C85.6,77,85.7,73.3,85.7,71.3z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#FFFFFF" d="M106,53.4c-3.1-5.7-11-11.3-19.3-16.5c-1.9-1.1-2.3-5.3-2-7.1l-1.9-0.9c0,0-0.7,5,0.6,9
|
||||||
|
c0.9,2.8,3.9,6,5.7,10.5c1.3,3,2.6,10.2,2.7,11c1,8.4,0.5,16.3-2,23c-1.1,3.1-3.5,5.9-5.4,7.2c-0.3,0.2-0.6,0.4-0.9,0.6
|
||||||
|
C99.3,90.7,116.7,72.8,106,53.4z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#FFFFFF" d="M102.7,4.3C96.2,6.5,90.4,9.9,86.1,16c2.6-5.3,6.7-10.6,11.3-16c-6.3,4.5-11.7,9.6-15.2,16.4l2.4-9.6
|
||||||
|
c-3.7,6.6-6.2,13.4-7,20.1l4.2,1.8C87.2,20.4,94.1,12.1,102.7,4.3z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon fill="#FFFFFF" points="80.1,64.5 80.1,64.5 80.1,64.5 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#FFFFFF" d="M61.6,28.6v-16c0-2.1-1.8-3.8-3.8-3.8H3.8C1.7,8.8,0,10.5,0,12.6v16c0,2.1,1.7,3.8,3.8,3.8H16
|
||||||
|
c1.1,0,2.6,0.9,2.6,2.6v52.7c0,1.8,1.3,3.1,3,3.1h18.4c1.7,0,3.1-1.3,3.1-3.1V35.4c0-1.6,1.1-3,3.6-3h11.1
|
||||||
|
C59.9,32.3,61.6,30.6,61.6,28.6z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#FFFFFF" d="M150.5,50.2V33.1c0-1.7-1.4-3.1-3.2-3.1h-4.8c-0.6,0-2.1,0.1-2.5,0.1c-12.1,1.2-21.7,11-22.6,23.2
|
||||||
|
c-0.1,0.5-0.1,1.3-0.1,1.9v32.9c0,1.3,1.3,2.5,2.8,2.5h17.2c1.5,0,2.8-1.1,2.8-2.5V59.2c0-3.1,2.4-5.9,7.7-5.9
|
||||||
|
C150.6,53.3,150.5,52.4,150.5,50.2z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -1,3 +1,4 @@
|
|||||||
description: This file stores settings for Dart & Flutter DevTools.
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
extensions:
|
extensions:
|
||||||
|
- drift: true
|
||||||
@@ -21,6 +21,7 @@ import 'package:lensai/features/settings/presentation/screens/settings.dart';
|
|||||||
import 'package:lensai/features/settings/presentation/screens/web_engine_hardening.dart';
|
import 'package:lensai/features/settings/presentation/screens/web_engine_hardening.dart';
|
||||||
import 'package:lensai/features/settings/presentation/screens/web_engine_hardening_group.dart';
|
import 'package:lensai/features/settings/presentation/screens/web_engine_hardening_group.dart';
|
||||||
import 'package:lensai/features/settings/presentation/screens/web_engine_settings.dart';
|
import 'package:lensai/features/settings/presentation/screens/web_engine_settings.dart';
|
||||||
|
import 'package:lensai/features/tor/presentation/screens/tor_proxy.dart';
|
||||||
import 'package:lensai/features/user/presentation/screens/auth.dart';
|
import 'package:lensai/features/user/presentation/screens/auth.dart';
|
||||||
|
|
||||||
part 'routes.g.dart';
|
part 'routes.g.dart';
|
||||||
@@ -326,3 +327,14 @@ class ChatArchiveDetailRoute extends GoRouteData {
|
|||||||
return ChatArchiveDetailScreen(fileName);
|
return ChatArchiveDetailScreen(fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TypedGoRoute<TorProxyRoute>(
|
||||||
|
name: 'TorProxyRoute',
|
||||||
|
path: '/tor_proxy',
|
||||||
|
)
|
||||||
|
class TorProxyRoute extends GoRouteData {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, GoRouterState state) {
|
||||||
|
return const TorProxyScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ List<RouteBase> get $appRoutes => [
|
|||||||
$browserRoute,
|
$browserRoute,
|
||||||
$settingsRoute,
|
$settingsRoute,
|
||||||
$chatArchiveListRoute,
|
$chatArchiveListRoute,
|
||||||
|
$torProxyRoute,
|
||||||
];
|
];
|
||||||
|
|
||||||
RouteBase get $browserRoute => GoRouteData.$route(
|
RouteBase get $browserRoute => GoRouteData.$route(
|
||||||
@@ -523,3 +524,26 @@ extension $ChatArchiveDetailRouteExtension on ChatArchiveDetailRoute {
|
|||||||
|
|
||||||
void replace(BuildContext context) => context.replace(location);
|
void replace(BuildContext context) => context.replace(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RouteBase get $torProxyRoute => GoRouteData.$route(
|
||||||
|
path: '/tor_proxy',
|
||||||
|
name: 'TorProxyRoute',
|
||||||
|
factory: $TorProxyRouteExtension._fromState,
|
||||||
|
);
|
||||||
|
|
||||||
|
extension $TorProxyRouteExtension on TorProxyRoute {
|
||||||
|
static TorProxyRoute _fromState(GoRouterState state) => TorProxyRoute();
|
||||||
|
|
||||||
|
String get location => GoRouteData.$location(
|
||||||
|
'/tor_proxy',
|
||||||
|
);
|
||||||
|
|
||||||
|
void go(BuildContext context) => context.go(location);
|
||||||
|
|
||||||
|
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||||
|
|
||||||
|
void pushReplacement(BuildContext context) =>
|
||||||
|
context.pushReplacement(location);
|
||||||
|
|
||||||
|
void replace(BuildContext context) => context.replace(location);
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class ColorJsonConverter implements JsonConverter<Color, int> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
int toJson(Color value) {
|
int toJson(Color value) {
|
||||||
return value.value;
|
return value.toARGB32();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +27,6 @@ class ColorConverter extends TypeConverter<Color, int> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
int toSql(Color value) {
|
int toSql(Color value) {
|
||||||
return value.value;
|
return value.toARGB32();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1553,7 +1553,8 @@ class $BangTableTableManager extends RootTableManager<
|
|||||||
getPrefetchedDataCallback: (items) async {
|
getPrefetchedDataCallback: (items) async {
|
||||||
return [
|
return [
|
||||||
if (bangFrequencyRefs)
|
if (bangFrequencyRefs)
|
||||||
await $_getPrefetchedData(
|
await $_getPrefetchedData<Bang, BangTable,
|
||||||
|
BangFrequencyData>(
|
||||||
currentTable: table,
|
currentTable: table,
|
||||||
referencedTable:
|
referencedTable:
|
||||||
$BangTableReferences._bangFrequencyRefsTable(db),
|
$BangTableReferences._bangFrequencyRefsTable(db),
|
||||||
@@ -1565,7 +1566,7 @@ class $BangTableTableManager extends RootTableManager<
|
|||||||
.where((e) => e.trigger == item.trigger),
|
.where((e) => e.trigger == item.trigger),
|
||||||
typedResults: items),
|
typedResults: items),
|
||||||
if (bangHistoryRefs)
|
if (bangHistoryRefs)
|
||||||
await $_getPrefetchedData(
|
await $_getPrefetchedData<Bang, BangTable, BangHistoryData>(
|
||||||
currentTable: table,
|
currentTable: table,
|
||||||
referencedTable:
|
referencedTable:
|
||||||
$BangTableReferences._bangHistoryRefsTable(db),
|
$BangTableReferences._bangHistoryRefsTable(db),
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import 'package:lensai/features/user/domain/repositories/engine_settings.dart';
|
|||||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'engine_settings.g.dart';
|
part 'engine_settings_replication.g.dart';
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class EngineSettingsReplicationService
|
class EngineSettingsReplicationService
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'engine_settings.dart';
|
part of 'engine_settings_replication.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||||
|
import 'package:lensai/data/models/equatable_iterable.dart';
|
||||||
|
import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
||||||
|
import 'package:lensai/features/tor/domain/services/tor_proxy.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'proxy_settings_replucation.g.dart';
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class ProxySettingsReplucation extends _$ProxySettingsReplucation {
|
||||||
|
final _service = GeckoContainerProxyService();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build() {
|
||||||
|
ref.listen(
|
||||||
|
torProxyServiceProvider.select((data) => data.valueOrNull),
|
||||||
|
(previous, next) async {
|
||||||
|
if (next != null) {
|
||||||
|
await _service.setProxyPort(next);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.listen(
|
||||||
|
containersWithCountProvider.select(
|
||||||
|
(value) => EquatableCollection(
|
||||||
|
value.valueOrNull
|
||||||
|
?.where((container) => container.metadata.useProxy)
|
||||||
|
.map((container) => container.metadata.contextualIdentity)
|
||||||
|
.nonNulls
|
||||||
|
.toList(),
|
||||||
|
immutable: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(previous, next) async {
|
||||||
|
if (next.collection != null) {
|
||||||
|
for (final contextId in next.collection!) {
|
||||||
|
await _service.addContainerProxy(contextId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'proxy_settings_replucation.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$proxySettingsReplucationHash() =>
|
||||||
|
r'08cb19e895056d4ddbf0399b23a9d159a0c86915';
|
||||||
|
|
||||||
|
/// See also [ProxySettingsReplucation].
|
||||||
|
@ProviderFor(ProxySettingsReplucation)
|
||||||
|
final proxySettingsReplucationProvider =
|
||||||
|
NotifierProvider<ProxySettingsReplucation, void>.internal(
|
||||||
|
ProxySettingsReplucation.new,
|
||||||
|
name: r'proxySettingsReplucationProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$proxySettingsReplucationHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$ProxySettingsReplucation = Notifier<void>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@@ -33,6 +33,7 @@ import 'package:lensai/features/kagi/data/entities/modes.dart';
|
|||||||
import 'package:lensai/presentation/hooks/draggable_scrollable_controller.dart';
|
import 'package:lensai/presentation/hooks/draggable_scrollable_controller.dart';
|
||||||
import 'package:lensai/presentation/hooks/menu_controller.dart';
|
import 'package:lensai/presentation/hooks/menu_controller.dart';
|
||||||
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
|
import 'package:lensai/presentation/hooks/overlay_portal_controller.dart';
|
||||||
|
import 'package:lensai/presentation/icons/tor_icons.dart';
|
||||||
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
import 'package:lensai/utils/ui_helper.dart' as ui_helper;
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
@@ -418,6 +419,13 @@ class BrowserScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
MenuItemButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await context.push(TorProxyRoute().location);
|
||||||
|
},
|
||||||
|
leadingIcon: const Icon(TorIcons.onionAlt),
|
||||||
|
child: const Text('Tor'),
|
||||||
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
|
|||||||
import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart';
|
import 'package:lensai/features/geckoview/domain/providers/web_extensions_state.dart';
|
||||||
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
|
||||||
import 'package:lensai/features/geckoview/features/browser/domain/services/delete_browser_data.dart';
|
import 'package:lensai/features/geckoview/features/browser/domain/services/delete_browser_data.dart';
|
||||||
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings.dart';
|
import 'package:lensai/features/geckoview/features/browser/domain/services/engine_settings_replication.dart';
|
||||||
|
import 'package:lensai/features/geckoview/features/browser/domain/services/proxy_settings_replucation.dart';
|
||||||
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
|
import 'package:lensai/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
|
||||||
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
import 'package:lensai/features/user/domain/repositories/cache.dart';
|
||||||
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
import 'package:lensai/features/user/domain/repositories/general_settings.dart';
|
||||||
@@ -146,6 +147,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
|||||||
engineSettingsReplicationServiceProvider,
|
engineSettingsReplicationServiceProvider,
|
||||||
(previous, next) {},
|
(previous, next) {},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ref.listenManual(
|
||||||
|
proxySettingsReplucationProvider,
|
||||||
|
(previous, next) {},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
+9
-1
@@ -21,6 +21,7 @@ import 'package:lensai/features/geckoview/features/tabs/domain/repositories/cont
|
|||||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab.dart';
|
||||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
|
||||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
|
||||||
|
import 'package:lensai/features/tor/presentation/controllers/start_tor_proxy.dart';
|
||||||
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
import 'package:lensai/presentation/hooks/listenable_callback.dart';
|
||||||
import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
|
import 'package:lensai/presentation/widgets/speech_to_text_button.dart';
|
||||||
|
|
||||||
@@ -181,9 +182,16 @@ class _Tab extends HookConsumerWidget {
|
|||||||
return ContainerChips(
|
return ContainerChips(
|
||||||
selectedContainer: selectedContainer,
|
selectedContainer: selectedContainer,
|
||||||
onSelected: (container) async {
|
onSelected: (container) async {
|
||||||
await ref
|
final result = await ref
|
||||||
.read(selectedContainerProvider.notifier)
|
.read(selectedContainerProvider.notifier)
|
||||||
.setContainerId(container.id);
|
.setContainerId(container.id);
|
||||||
|
|
||||||
|
if (context.mounted &&
|
||||||
|
result == SetContainerResult.successHasProxy) {
|
||||||
|
await ref
|
||||||
|
.read(startProxyControllerProvider.notifier)
|
||||||
|
.maybeStartProxy(context);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onDeleted: (container) async {
|
onDeleted: (container) async {
|
||||||
ref
|
ref
|
||||||
|
|||||||
@@ -2005,7 +2005,8 @@ class $ContainerTableManager extends RootTableManager<
|
|||||||
getPrefetchedDataCallback: (items) async {
|
getPrefetchedDataCallback: (items) async {
|
||||||
return [
|
return [
|
||||||
if (tabRefs)
|
if (tabRefs)
|
||||||
await $_getPrefetchedData(
|
await $_getPrefetchedData<ContainerData, Container,
|
||||||
|
TabData>(
|
||||||
currentTable: table,
|
currentTable: table,
|
||||||
referencedTable: $ContainerReferences._tabRefsTable(db),
|
referencedTable: $ContainerReferences._tabRefsTable(db),
|
||||||
managerFromTypedResult: (p0) =>
|
managerFromTypedResult: (p0) =>
|
||||||
|
|||||||
@@ -53,20 +53,26 @@ class ContainerMetadata with FastEquatable {
|
|||||||
final String? contextualIdentity;
|
final String? contextualIdentity;
|
||||||
final ContainerAuthSettings authSettings;
|
final ContainerAuthSettings authSettings;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
final bool useProxy;
|
||||||
|
|
||||||
ContainerMetadata({
|
ContainerMetadata({
|
||||||
required this.iconData,
|
required this.iconData,
|
||||||
required this.contextualIdentity,
|
required this.contextualIdentity,
|
||||||
required this.authSettings,
|
required this.authSettings,
|
||||||
|
required this.useProxy,
|
||||||
});
|
});
|
||||||
|
|
||||||
ContainerMetadata.withDefaults({
|
ContainerMetadata.withDefaults({
|
||||||
IconData? iconData,
|
IconData? iconData,
|
||||||
String? contextualIdentity,
|
String? contextualIdentity,
|
||||||
ContainerAuthSettings? authSettings,
|
ContainerAuthSettings? authSettings,
|
||||||
|
bool? useProxy,
|
||||||
}) : this(
|
}) : this(
|
||||||
iconData: iconData,
|
iconData: iconData,
|
||||||
contextualIdentity: contextualIdentity,
|
contextualIdentity: contextualIdentity,
|
||||||
authSettings: authSettings ?? ContainerAuthSettings.withDefaults(),
|
authSettings: authSettings ?? ContainerAuthSettings.withDefaults(),
|
||||||
|
useProxy: useProxy ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
factory ContainerMetadata.fromJson(Map<String, dynamic> json) =>
|
factory ContainerMetadata.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -79,6 +85,7 @@ class ContainerMetadata with FastEquatable {
|
|||||||
iconData,
|
iconData,
|
||||||
contextualIdentity,
|
contextualIdentity,
|
||||||
authSettings,
|
authSettings,
|
||||||
|
useProxy,
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ abstract class _$ContainerMetadataCWProxy {
|
|||||||
|
|
||||||
ContainerMetadata authSettings(ContainerAuthSettings authSettings);
|
ContainerMetadata authSettings(ContainerAuthSettings authSettings);
|
||||||
|
|
||||||
|
ContainerMetadata useProxy(bool useProxy);
|
||||||
|
|
||||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||||
///
|
///
|
||||||
/// Usage
|
/// Usage
|
||||||
@@ -100,6 +102,7 @@ abstract class _$ContainerMetadataCWProxy {
|
|||||||
IconData? iconData,
|
IconData? iconData,
|
||||||
String? contextualIdentity,
|
String? contextualIdentity,
|
||||||
ContainerAuthSettings authSettings,
|
ContainerAuthSettings authSettings,
|
||||||
|
bool useProxy,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +123,9 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
|||||||
ContainerMetadata authSettings(ContainerAuthSettings authSettings) =>
|
ContainerMetadata authSettings(ContainerAuthSettings authSettings) =>
|
||||||
this(authSettings: authSettings);
|
this(authSettings: authSettings);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ContainerMetadata useProxy(bool useProxy) => this(useProxy: useProxy);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
||||||
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `ContainerMetadata(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
|
||||||
@@ -132,6 +138,7 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
|||||||
Object? iconData = const $CopyWithPlaceholder(),
|
Object? iconData = const $CopyWithPlaceholder(),
|
||||||
Object? contextualIdentity = const $CopyWithPlaceholder(),
|
Object? contextualIdentity = const $CopyWithPlaceholder(),
|
||||||
Object? authSettings = const $CopyWithPlaceholder(),
|
Object? authSettings = const $CopyWithPlaceholder(),
|
||||||
|
Object? useProxy = const $CopyWithPlaceholder(),
|
||||||
}) {
|
}) {
|
||||||
return ContainerMetadata(
|
return ContainerMetadata(
|
||||||
iconData: iconData == const $CopyWithPlaceholder()
|
iconData: iconData == const $CopyWithPlaceholder()
|
||||||
@@ -146,6 +153,10 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
|
|||||||
? _value.authSettings
|
? _value.authSettings
|
||||||
// ignore: cast_nullable_to_non_nullable
|
// ignore: cast_nullable_to_non_nullable
|
||||||
: authSettings as ContainerAuthSettings,
|
: authSettings as ContainerAuthSettings,
|
||||||
|
useProxy: useProxy == const $CopyWithPlaceholder()
|
||||||
|
? _value.useProxy
|
||||||
|
// ignore: cast_nullable_to_non_nullable
|
||||||
|
: useProxy as bool,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,6 +282,7 @@ ContainerMetadata _$ContainerMetadataFromJson(Map<String, dynamic> json) =>
|
|||||||
? null
|
? null
|
||||||
: ContainerAuthSettings.fromJson(
|
: ContainerAuthSettings.fromJson(
|
||||||
json['authSettings'] as Map<String, dynamic>),
|
json['authSettings'] as Map<String, dynamic>),
|
||||||
|
useProxy: json['useProxy'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$ContainerMetadataToJson(ContainerMetadata instance) =>
|
Map<String, dynamic> _$ContainerMetadataToJson(ContainerMetadata instance) =>
|
||||||
@@ -279,6 +291,7 @@ Map<String, dynamic> _$ContainerMetadataToJson(ContainerMetadata instance) =>
|
|||||||
instance.iconData, const IconDataJsonConverter().toJson),
|
instance.iconData, const IconDataJsonConverter().toJson),
|
||||||
'contextualIdentity': instance.contextualIdentity,
|
'contextualIdentity': instance.contextualIdentity,
|
||||||
'authSettings': instance.authSettings,
|
'authSettings': instance.authSettings,
|
||||||
|
'useProxy': instance.useProxy,
|
||||||
};
|
};
|
||||||
|
|
||||||
Value? _$JsonConverterFromJson<Json, Value>(
|
Value? _$JsonConverterFromJson<Json, Value>(
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|||||||
|
|
||||||
part 'selected_container.g.dart';
|
part 'selected_container.g.dart';
|
||||||
|
|
||||||
|
enum SetContainerResult {
|
||||||
|
failed,
|
||||||
|
success,
|
||||||
|
successHasProxy,
|
||||||
|
}
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
class SelectedContainer extends _$SelectedContainer {
|
class SelectedContainer extends _$SelectedContainer {
|
||||||
Future<ContainerData?> fetchData() async {
|
Future<ContainerData?> fetchData() async {
|
||||||
@@ -20,12 +26,13 @@ class SelectedContainer extends _$SelectedContainer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setContainerId(String id) async {
|
Future<SetContainerResult> setContainerId(String id) async {
|
||||||
final container = await ref
|
final container = await ref
|
||||||
.read(containerRepositoryProvider.notifier)
|
.read(containerRepositoryProvider.notifier)
|
||||||
.getContainerData(id);
|
.getContainerData(id);
|
||||||
|
|
||||||
if (container != null) {
|
if (container != null) {
|
||||||
|
var passAuth = false;
|
||||||
if (container.metadata.authSettings.authenticationRequired) {
|
if (container.metadata.authSettings.authenticationRequired) {
|
||||||
final authResult = await ref
|
final authResult = await ref
|
||||||
.read(localAuthenticationServiceProvider.notifier)
|
.read(localAuthenticationServiceProvider.notifier)
|
||||||
@@ -38,20 +45,24 @@ class SelectedContainer extends _$SelectedContainer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (authResult) {
|
if (authResult) {
|
||||||
state = id;
|
passAuth = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
passAuth = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passAuth) {
|
||||||
state = id;
|
state = id;
|
||||||
|
|
||||||
|
if (container.metadata.useProxy) {
|
||||||
|
return SetContainerResult.successHasProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SetContainerResult.success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> toggleContainer(String id) async {
|
return SetContainerResult.failed;
|
||||||
if (state == id) {
|
|
||||||
clearContainer();
|
|
||||||
} else {
|
|
||||||
await setContainerId(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearContainer() {
|
void clearContainer() {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ final selectedContainerDataProvider =
|
|||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
typedef SelectedContainerDataRef = AutoDisposeStreamProviderRef<ContainerData?>;
|
typedef SelectedContainerDataRef = AutoDisposeStreamProviderRef<ContainerData?>;
|
||||||
String _$selectedContainerHash() => r'753f30646c4992981ec538dcfd33fa23aa1f673d';
|
String _$selectedContainerHash() => r'f3dab491337396b8d56daed37a0d85c85aa8d64a';
|
||||||
|
|
||||||
/// See also [SelectedContainer].
|
/// See also [SelectedContainer].
|
||||||
@ProviderFor(SelectedContainer)
|
@ProviderFor(SelectedContainer)
|
||||||
|
|||||||
+2
-3
@@ -1,5 +1,4 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cross_cache/cross_cache.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
||||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||||
@@ -23,7 +22,7 @@ class TabQaChat extends HookConsumerWidget {
|
|||||||
final chatBackend = ref.watch(chatBackendProvider(chatId).notifier);
|
final chatBackend = ref.watch(chatBackendProvider(chatId).notifier);
|
||||||
final chatController = ref.watch(chatControllerProvider(chatId));
|
final chatController = ref.watch(chatControllerProvider(chatId));
|
||||||
|
|
||||||
final crossCache = useMemoized(() => CrossCache());
|
// final crossCache = useMemoized(() => CrossCache());
|
||||||
final chatScrollController = scrollController ?? useScrollController();
|
final chatScrollController = scrollController ?? useScrollController();
|
||||||
|
|
||||||
useOnInitialization(() async {
|
useOnInitialization(() async {
|
||||||
@@ -59,7 +58,7 @@ class TabQaChat extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
chatController: chatController,
|
chatController: chatController,
|
||||||
crossCache: crossCache,
|
// crossCache: crossCache,
|
||||||
scrollController: chatScrollController,
|
scrollController: chatScrollController,
|
||||||
onMessageSend: (text) async {
|
onMessageSend: (text) async {
|
||||||
await chatBackend.processQAMessage(text);
|
await chatBackend.processQAMessage(text);
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:lensai/core/uuid.dart';
|
import 'package:lensai/core/uuid.dart';
|
||||||
import 'package:lensai/extensions/nullable.dart';
|
|
||||||
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
import 'package:lensai/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/color_picker_dialog.dart';
|
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/color_picker_dialog.dart';
|
||||||
import 'package:lensai/features/user/domain/services/local_authentication.dart';
|
import 'package:lensai/features/user/domain/services/local_authentication.dart';
|
||||||
|
import 'package:lensai/presentation/icons/tor_icons.dart';
|
||||||
|
|
||||||
enum _DialogMode { create, edit }
|
enum _DialogMode { create, edit }
|
||||||
|
|
||||||
@@ -66,6 +66,7 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
final contextualIdentity =
|
final contextualIdentity =
|
||||||
useState(initialContainer.metadata.contextualIdentity);
|
useState(initialContainer.metadata.contextualIdentity);
|
||||||
final authSettings = useState(initialContainer.metadata.authSettings);
|
final authSettings = useState(initialContainer.metadata.authSettings);
|
||||||
|
final useProxy = useState(initialContainer.metadata.useProxy);
|
||||||
|
|
||||||
final textController =
|
final textController =
|
||||||
useTextEditingController(text: initialContainer.name);
|
useTextEditingController(text: initialContainer.name);
|
||||||
@@ -85,17 +86,11 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
final container = initialContainer.copyWith(
|
final container = initialContainer.copyWith(
|
||||||
name: name.isNotEmpty ? name : null,
|
name: name.isNotEmpty ? name : null,
|
||||||
color: selectedColor.value,
|
color: selectedColor.value,
|
||||||
metadata: initialContainer.metadata.mapNotNull(
|
metadata: initialContainer.metadata.copyWith(
|
||||||
(metadata) => metadata.copyWith(
|
contextualIdentity: contextualIdentity.value,
|
||||||
contextualIdentity: contextualIdentity.value,
|
authSettings: authSettings.value,
|
||||||
authSettings: authSettings.value,
|
useProxy: useProxy.value && contextualIdentity.value != null,
|
||||||
),
|
),
|
||||||
) ??
|
|
||||||
ContainerMetadata(
|
|
||||||
iconData: null,
|
|
||||||
contextualIdentity: contextualIdentity.value,
|
|
||||||
authSettings: authSettings.value,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
//Check for permissions, when auth is set or getting set
|
//Check for permissions, when auth is set or getting set
|
||||||
@@ -187,9 +182,35 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
.metadata.contextualIdentity ??
|
.metadata.contextualIdentity ??
|
||||||
uuid.v4()
|
uuid.v4()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
if (!value && useProxy.value) {
|
||||||
|
useProxy.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
SwitchListTile.adaptive(
|
||||||
|
value: useProxy.value,
|
||||||
|
title: const Text('Use Tor Proxy'),
|
||||||
|
secondary: const Icon(TorIcons.onionAlt),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
onChanged: switch (_mode) {
|
||||||
|
_DialogMode.create => (value) {
|
||||||
|
if (value && contextualIdentity.value == null) {
|
||||||
|
contextualIdentity.value = initialContainer
|
||||||
|
.metadata.contextualIdentity ??
|
||||||
|
uuid.v4();
|
||||||
|
}
|
||||||
|
|
||||||
|
useProxy.value = value;
|
||||||
|
},
|
||||||
|
_DialogMode.edit => (contextualIdentity.value != null)
|
||||||
|
? (value) {
|
||||||
|
useProxy.value = value;
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
),
|
||||||
SwitchListTile.adaptive(
|
SwitchListTile.adaptive(
|
||||||
value: authSettings.value.authenticationRequired,
|
value: authSettings.value.authenticationRequired,
|
||||||
title: const Text('Require Authentication'),
|
title: const Text('Require Authentication'),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:lensai/features/geckoview/features/tabs/domain/providers.dart';
|
|||||||
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
import 'package:lensai/features/geckoview/features/tabs/domain/providers/selected_container.dart';
|
||||||
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
import 'package:lensai/features/geckoview/features/tabs/domain/repositories/container.dart';
|
||||||
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_list_tile.dart';
|
import 'package:lensai/features/geckoview/features/tabs/presentation/widgets/container_list_tile.dart';
|
||||||
|
import 'package:lensai/features/tor/presentation/controllers/start_tor_proxy.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
class ContainerListScreen extends HookConsumerWidget {
|
class ContainerListScreen extends HookConsumerWidget {
|
||||||
@@ -62,9 +63,23 @@ class ContainerListScreen extends HookConsumerWidget {
|
|||||||
container,
|
container,
|
||||||
isSelected: container.id == selectedContainer,
|
isSelected: container.id == selectedContainer,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await ref
|
if (container.id == selectedContainer) {
|
||||||
.read(selectedContainerProvider.notifier)
|
ref
|
||||||
.toggleContainer(container.id);
|
.read(selectedContainerProvider.notifier)
|
||||||
|
.clearContainer();
|
||||||
|
} else {
|
||||||
|
final result = await ref
|
||||||
|
.read(selectedContainerProvider.notifier)
|
||||||
|
.setContainerId(container.id);
|
||||||
|
|
||||||
|
if (context.mounted &&
|
||||||
|
result ==
|
||||||
|
SetContainerResult.successHasProxy) {
|
||||||
|
await ref
|
||||||
|
.read(startProxyControllerProvider.notifier)
|
||||||
|
.maybeStartProxy(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
for (final colors in colorTypes) {
|
for (final colors in colorTypes) {
|
||||||
shadingTypes(colors).forEach((Map<Color, String> color) {
|
shadingTypes(colors).forEach((Map<Color, String> color) {
|
||||||
if (widget.pickerColor.value == color.keys.first.value) {
|
if (widget.pickerColor.toARGB32() == color.keys.first.toARGB32()) {
|
||||||
return setState(() {
|
return setState(() {
|
||||||
_currentColorType = colors;
|
_currentColorType = colors;
|
||||||
_currentShading = color.keys.first;
|
_currentShading = color.keys.first;
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||||
|
import 'package:lensai/features/tor/utils/tor_entrypoint.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
part 'tor_proxy.g.dart';
|
||||||
|
|
||||||
|
class _TorService {
|
||||||
|
final service = FlutterBackgroundService();
|
||||||
|
|
||||||
|
final _portSubject = BehaviorSubject<int?>.seeded(null);
|
||||||
|
|
||||||
|
ValueStream<int?> get portStream => _portSubject.stream;
|
||||||
|
|
||||||
|
Future<void> start() async {
|
||||||
|
await service.startService();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> stop() async {
|
||||||
|
service.invoke("stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initializeService() async {
|
||||||
|
await service.configure(
|
||||||
|
iosConfiguration: IosConfiguration(
|
||||||
|
autoStart: false,
|
||||||
|
),
|
||||||
|
androidConfiguration: AndroidConfiguration(
|
||||||
|
autoStart: false,
|
||||||
|
onStart: onStart,
|
||||||
|
isForegroundMode: true,
|
||||||
|
autoStartOnBoot: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
service.on('portUpdate').listen((data) {
|
||||||
|
final port = data!['port'] as int;
|
||||||
|
_portSubject.add((port > -1) ? port : null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
unawaited(stop());
|
||||||
|
unawaited(_portSubject.close());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class TorProxyService extends _$TorProxyService {
|
||||||
|
final _tor = _TorService();
|
||||||
|
|
||||||
|
Future<void> connect({bool forceReconnect = false}) async {
|
||||||
|
final currentPort = _tor.portStream.valueOrNull;
|
||||||
|
|
||||||
|
if (currentPort == null || forceReconnect) {
|
||||||
|
state = const AsyncLoading();
|
||||||
|
await _tor.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> disconnect() async {
|
||||||
|
state = const AsyncLoading();
|
||||||
|
await _tor.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int?> build() async {
|
||||||
|
final portSub = _tor.portStream.distinct().listen(
|
||||||
|
(port) {
|
||||||
|
state = AsyncData(port);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await _tor.initializeService();
|
||||||
|
|
||||||
|
ref.onDispose(() {
|
||||||
|
unawaited(portSub.cancel());
|
||||||
|
_tor.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
return _tor.portStream.valueOrNull;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'tor_proxy.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$torProxyServiceHash() => r'85ed8e3b49007aa56e6b82ff802a3e0ce567a0c8';
|
||||||
|
|
||||||
|
/// See also [TorProxyService].
|
||||||
|
@ProviderFor(TorProxyService)
|
||||||
|
final torProxyServiceProvider =
|
||||||
|
AsyncNotifierProvider<TorProxyService, int?>.internal(
|
||||||
|
TorProxyService.new,
|
||||||
|
name: r'torProxyServiceProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$torProxyServiceHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$TorProxyService = AsyncNotifier<int?>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.dart';
|
||||||
|
import 'package:lensai/features/tor/domain/services/tor_proxy.dart';
|
||||||
|
import 'package:lensai/features/tor/presentation/widgets/tor_dialog.dart';
|
||||||
|
import 'package:lensai/features/tor/presentation/widgets/tor_notification.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'start_tor_proxy.g.dart';
|
||||||
|
|
||||||
|
@Riverpod()
|
||||||
|
class StartProxyController extends _$StartProxyController {
|
||||||
|
// ignore: avoid_build_context_in_providers
|
||||||
|
Future<void> maybeStartProxy(BuildContext context) async {
|
||||||
|
final torProxyRunning = ref.read(torProxyServiceProvider);
|
||||||
|
|
||||||
|
if (torProxyRunning.valueOrNull == null) {
|
||||||
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return TorDialog();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == true) {
|
||||||
|
final connection = ref.read(torProxyServiceProvider.notifier).connect();
|
||||||
|
|
||||||
|
ref.read(overlayDialogControllerProvider.notifier).show(
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
child: TorNotification(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'start_tor_proxy.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$startProxyControllerHash() =>
|
||||||
|
r'c49d4f75bb499b3e695f1c4d08dcc095cfee7143';
|
||||||
|
|
||||||
|
/// See also [StartProxyController].
|
||||||
|
@ProviderFor(StartProxyController)
|
||||||
|
final startProxyControllerProvider =
|
||||||
|
AutoDisposeNotifierProvider<StartProxyController, void>.internal(
|
||||||
|
StartProxyController.new,
|
||||||
|
name: r'startProxyControllerProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$startProxyControllerHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$StartProxyController = AutoDisposeNotifier<void>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
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:lensai/features/tor/domain/services/tor_proxy.dart';
|
||||||
|
|
||||||
|
class TorProxyScreen extends HookConsumerWidget {
|
||||||
|
const TorProxyScreen();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final torProxyPort = ref.watch(torProxyServiceProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Tor Proxy'),
|
||||||
|
),
|
||||||
|
body: ColoredBox(
|
||||||
|
color: const Color(0xFF7D4698),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
'assets/images/tor_white.svg',
|
||||||
|
width: 200,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
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.axisArrowLock);
|
||||||
|
}
|
||||||
|
return null; // Use the default color.
|
||||||
|
}),
|
||||||
|
value: torProxyPort.valueOrNull != null,
|
||||||
|
title: const Text('Tor Proxy'),
|
||||||
|
secondary: const Icon(MdiIcons.power),
|
||||||
|
onChanged: torProxyPort.isLoading
|
||||||
|
? null
|
||||||
|
: (value) async {
|
||||||
|
if (value) {
|
||||||
|
await ref
|
||||||
|
.read(torProxyServiceProvider.notifier)
|
||||||
|
.connect();
|
||||||
|
} else {
|
||||||
|
await ref
|
||||||
|
.read(torProxyServiceProvider.notifier)
|
||||||
|
.disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (torProxyPort.isLoading)
|
||||||
|
const Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
LinearProgressIndicator(
|
||||||
|
backgroundColor: Color(0xFF333A41),
|
||||||
|
color: Color(0xFF68B030),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Establishing connection...',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (torProxyPort.valueOrNull != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 24.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
MdiIcons.shieldLockOutline,
|
||||||
|
color: Color(0xFF68B030),
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Connected to the Tor Network',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium
|
||||||
|
?.copyWith(color: const Color(0xFF68B030)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:lensai/presentation/icons/tor_icons.dart';
|
||||||
|
|
||||||
|
class TorDialog extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
icon: const Icon(
|
||||||
|
TorIcons.onionAlt,
|
||||||
|
color: Color(0xFF7D4698),
|
||||||
|
),
|
||||||
|
title: const Text('Tor Proxy'),
|
||||||
|
content: const Text(
|
||||||
|
'This container requires a Tor proxy for secure connections, which is not currently running.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pop(false);
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pop(true);
|
||||||
|
},
|
||||||
|
child: const Text('Enable'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:lensai/features/geckoview/domain/controllers/overlay_dialog.dart';
|
||||||
|
import 'package:lensai/features/tor/domain/services/tor_proxy.dart';
|
||||||
|
import 'package:lensai/presentation/icons/tor_icons.dart';
|
||||||
|
import 'package:lensai/presentation/widgets/animate_gradient_shader.dart';
|
||||||
|
|
||||||
|
class TorNotification extends HookConsumerWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
ref.listen(
|
||||||
|
torProxyServiceProvider,
|
||||||
|
(previous, next) {
|
||||||
|
if (!next.isLoading) {
|
||||||
|
ref.read(overlayDialogControllerProvider.notifier).dismiss();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return SafeArea(
|
||||||
|
child: ColoredBox(
|
||||||
|
color: const Color(0xFF7D4698),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 56,
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
AnimateGradientShader(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
primaryEnd: Alignment.bottomLeft,
|
||||||
|
secondaryEnd: Alignment.topRight,
|
||||||
|
primaryColors: const [Color(0xFF68B030), Color(0xFF68B030)],
|
||||||
|
secondaryColors: const [Colors.white, Colors.white],
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Icon(TorIcons.onionAlt),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Tor Proxy is connecting...',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
ref
|
||||||
|
.read(overlayDialogControllerProvider.notifier)
|
||||||
|
.dismiss();
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||||
|
import 'package:tor/tor.dart';
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void onStart(ServiceInstance service) async {
|
||||||
|
await Tor.init();
|
||||||
|
|
||||||
|
final portSub = Tor.instance.events.stream.listen((port) {
|
||||||
|
service.invoke('portUpdate', {'port': port});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Tor.instance.start();
|
||||||
|
|
||||||
|
service.on("stop").listen((event) async {
|
||||||
|
Tor.instance.stop();
|
||||||
|
service.invoke('portUpdate', {'port': -1});
|
||||||
|
|
||||||
|
await portSub.cancel();
|
||||||
|
|
||||||
|
await service.stopSelf();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
enum TorIcons implements IconData {
|
||||||
|
authority(0xf101),
|
||||||
|
badexit(0xf102),
|
||||||
|
bridge(0xf103),
|
||||||
|
country(0xf104),
|
||||||
|
directory(0xf105),
|
||||||
|
exit(0xf106),
|
||||||
|
experimental(0xf107),
|
||||||
|
fallbackdir(0xf108),
|
||||||
|
fast(0xf109),
|
||||||
|
fingerprint(0xf10a),
|
||||||
|
guard(0xf10b),
|
||||||
|
hibernating(0xf10c),
|
||||||
|
hsdir(0xf10d),
|
||||||
|
ipv4(0xf10e),
|
||||||
|
ipv6exit(0xf10f),
|
||||||
|
ipv6(0xf110),
|
||||||
|
noedconsensus(0xf111),
|
||||||
|
notrecommended(0xf112),
|
||||||
|
onionAlt(0xf113),
|
||||||
|
onion(0xf114),
|
||||||
|
outdated(0xf115),
|
||||||
|
reachableipv4(0xf116),
|
||||||
|
reachableipv6(0xf117),
|
||||||
|
relay(0xf118),
|
||||||
|
running(0xf119),
|
||||||
|
stable(0xf11a),
|
||||||
|
tshirt(0xf11b),
|
||||||
|
unmeasured(0xf11c),
|
||||||
|
unreachableipv4(0xf11d),
|
||||||
|
unreachableipv6(0xf11e),
|
||||||
|
v2dir(0xf11f),
|
||||||
|
valid(0xf120);
|
||||||
|
|
||||||
|
const TorIcons(this.codePoint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int codePoint;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get fontFamily => 'TorIcons';
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String>? get fontFamilyFallback => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get fontPackage => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get matchTextDirection => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'TorIcons.$name';
|
||||||
|
}
|
||||||
+29
-17
@@ -7,34 +7,35 @@ environment:
|
|||||||
sdk: '>=3.4.1 <4.0.0'
|
sdk: '>=3.4.1 <4.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
collection: ^1.19.0
|
collection: ^1.19.1
|
||||||
copy_with_extension: ^6.0.1
|
copy_with_extension: ^6.0.1
|
||||||
cross_cache: ^0.0.5
|
|
||||||
crypto: ^3.0.6
|
crypto: ^3.0.6
|
||||||
drift: ^2.24.0
|
drift: ^2.25.1
|
||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
exceptions: ^0.6.1
|
exceptions: ^0.6.1
|
||||||
expandable_page_view: ^1.0.17
|
expandable_page_view: ^1.0.17
|
||||||
fading_scroll: ^0.9.1
|
fading_scroll: ^0.9.1
|
||||||
fast_equatable: ^1.1.0
|
fast_equatable: ^1.1.0
|
||||||
file_picker: ^8.3.2
|
file_picker: ^8.3.7
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_background_service: ^5.1.0
|
||||||
flutter_chat_core:
|
flutter_chat_core:
|
||||||
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
||||||
flutter_chat_ui:
|
flutter_chat_ui:
|
||||||
path: ../../flutter_chat_ui/packages/flutter_chat_ui
|
path: ../../flutter_chat_ui/packages/flutter_chat_ui
|
||||||
flutter_hooks: ^0.20.5
|
flutter_hooks: ^0.20.5
|
||||||
flutter_markdown: ^0.7.6+1
|
flutter_markdown: ^0.7.6+2
|
||||||
flutter_material_design_icons: ^1.1.7447
|
flutter_material_design_icons: ^1.1.7447
|
||||||
flutter_mozilla_components:
|
flutter_mozilla_components:
|
||||||
path: ../packages/flutter_mozilla_components
|
path: ../packages/flutter_mozilla_components
|
||||||
flutter_pdf_text: ^0.8.1
|
flutter_pdf_text: ^0.9.0
|
||||||
flutter_reorderable_grid_view:
|
flutter_reorderable_grid_view:
|
||||||
path: ../../flutter-reorderable-grid-view
|
path: ../../flutter-reorderable-grid-view
|
||||||
flutter_secure_storage: ^10.0.0-beta.4
|
flutter_secure_storage: ^10.0.0-beta.4
|
||||||
flutter_sharing_intent: ^1.1.1
|
flutter_sharing_intent: ^1.1.1
|
||||||
flutter_slidable: ^4.0.0
|
flutter_slidable: ^4.0.0
|
||||||
|
flutter_svg: ^2.0.17
|
||||||
go_router: ^14.8.0
|
go_router: ^14.8.0
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
home_widget: ^0.7.0+1
|
home_widget: ^0.7.0+1
|
||||||
@@ -54,9 +55,9 @@ dependencies:
|
|||||||
markdown: ^7.3.0
|
markdown: ^7.3.0
|
||||||
mime: ^2.0.0
|
mime: ^2.0.0
|
||||||
package_info_plus: ^8.2.1
|
package_info_plus: ^8.2.1
|
||||||
path: ^1.9.0
|
path: ^1.9.1
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
pocketbase: ^0.21.0
|
pocketbase: ^0.22.0
|
||||||
provider: ^6.1.2
|
provider: ^6.1.2
|
||||||
riverpod: ^2.6.1
|
riverpod: ^2.6.1
|
||||||
riverpod_annotation: ^2.6.1
|
riverpod_annotation: ^2.6.1
|
||||||
@@ -67,13 +68,17 @@ dependencies:
|
|||||||
speech_to_text_google_dialog:
|
speech_to_text_google_dialog:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/FaFre/speech_to_text_google_dialog.git
|
url: https://github.com/FaFre/speech_to_text_google_dialog.git
|
||||||
sqlite3: ^2.7.2
|
sqlite3: ^2.7.3
|
||||||
sqlite3_flutter_libs: ^0.5.29+1
|
sqlite3_flutter_libs: ^0.5.30
|
||||||
sqlite3_vec:
|
sqlite3_vec:
|
||||||
path: ../packages/sqlite3_vec
|
path: ../packages/sqlite3_vec
|
||||||
synchronized: ^3.3.0+3
|
synchronized: ^3.3.1
|
||||||
text_scroll: ^0.2.0
|
text_scroll: ^0.2.0
|
||||||
timeago: ^3.7.0
|
timeago: ^3.7.0
|
||||||
|
tor:
|
||||||
|
git:
|
||||||
|
url: https://github.com/FaFre/tor.git
|
||||||
|
ref: update
|
||||||
universal_io: ^2.2.2
|
universal_io: ^2.2.2
|
||||||
uri_to_file:
|
uri_to_file:
|
||||||
git:
|
git:
|
||||||
@@ -84,30 +89,37 @@ dependencies:
|
|||||||
xxh3: ^1.2.0
|
xxh3: ^1.2.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
|
cross_cache:
|
||||||
|
path: ../../flutter_chat_ui/packages/cross_cache
|
||||||
flutter_chat_core:
|
flutter_chat_core:
|
||||||
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.14
|
build_runner: ^2.4.15
|
||||||
copy_with_extension_gen: ^6.0.0
|
copy_with_extension_gen: ^6.0.0
|
||||||
custom_lint: ^0.6.10
|
custom_lint: ^0.6.10
|
||||||
dependency_validator: ^4.1.2
|
dependency_validator: ^4.1.2
|
||||||
drift_dev: ^2.24.0
|
drift_dev: ^2.25.2
|
||||||
fast_equatable_lint: ^0.3.0
|
fast_equatable_lint: ^0.3.0
|
||||||
flutter_launcher_icons: ^0.14.3
|
flutter_launcher_icons: ^0.14.3
|
||||||
go_router_builder: ^2.7.5
|
go_router_builder: ^2.8.0
|
||||||
json_serializable: ^6.9.0
|
json_serializable: ^6.9.0
|
||||||
lint: ^2.3.0
|
lint: ^2.4.0
|
||||||
riverpod_generator: ^2.6.1
|
riverpod_generator: ^2.6.1
|
||||||
riverpod_lint: ^2.6.1
|
riverpod_lint: ^2.6.1
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- assets/icon/
|
|
||||||
- assets/landing/
|
|
||||||
- assets/fonts/
|
- assets/fonts/
|
||||||
|
- assets/icon/
|
||||||
|
- assets/images/
|
||||||
|
- assets/landing/
|
||||||
- assets/preferences/
|
- assets/preferences/
|
||||||
|
fonts:
|
||||||
|
- family: TorIcons
|
||||||
|
fonts:
|
||||||
|
- asset: assets/fonts/tor-icons.ttf
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: "launcher_icon"
|
android: "launcher_icon"
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# melos_managed_dependency_overrides: flutter_mozilla_components,sqlite3_vec,flutter_chat_core
|
# melos_managed_dependency_overrides: flutter_mozilla_components,sqlite3_vec,cross_cache,flutter_chat_core
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
flutter_mozilla_components:
|
flutter_mozilla_components:
|
||||||
path: ../packages/flutter_mozilla_components
|
path: ../packages/flutter_mozilla_components
|
||||||
sqlite3_vec:
|
sqlite3_vec:
|
||||||
path: ../packages/sqlite3_vec
|
path: ../packages/sqlite3_vec
|
||||||
|
cross_cache:
|
||||||
|
path: ../../flutter_chat_ui/packages/cross_cache
|
||||||
flutter_chat_core:
|
flutter_chat_core:
|
||||||
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.6.1")
|
classpath("com.android.tools.build:gradle:8.7.0")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
../../../../../javascript/container_proxy/dist/
|
||||||
+2
@@ -5,6 +5,7 @@
|
|||||||
package eu.lensai.flutter_mozilla_components
|
package eu.lensai.flutter_mozilla_components
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import eu.lensai.flutter_mozilla_components.feature.ContainerProxyFeature
|
||||||
import eu.lensai.flutter_mozilla_components.feature.CookieManagerFeature
|
import eu.lensai.flutter_mozilla_components.feature.CookieManagerFeature
|
||||||
import eu.lensai.flutter_mozilla_components.feature.PrefManagerFeature
|
import eu.lensai.flutter_mozilla_components.feature.PrefManagerFeature
|
||||||
import mozilla.components.browser.engine.gecko.GeckoEngine
|
import mozilla.components.browser.engine.gecko.GeckoEngine
|
||||||
@@ -50,6 +51,7 @@ object EngineProvider {
|
|||||||
WebCompatFeature.install(it)
|
WebCompatFeature.install(it)
|
||||||
CookieManagerFeature.install(it)
|
CookieManagerFeature.install(it)
|
||||||
PrefManagerFeature.install(it)
|
PrefManagerFeature.install(it)
|
||||||
|
ContainerProxyFeature.install(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-7
@@ -6,6 +6,7 @@ import androidx.fragment.app.FragmentActivity
|
|||||||
import eu.lensai.flutter_mozilla_components.activities.NotificationActivity
|
import eu.lensai.flutter_mozilla_components.activities.NotificationActivity
|
||||||
import eu.lensai.flutter_mozilla_components.api.GeckoAddonsApiImpl
|
import eu.lensai.flutter_mozilla_components.api.GeckoAddonsApiImpl
|
||||||
import eu.lensai.flutter_mozilla_components.api.GeckoBrowserApiImpl
|
import eu.lensai.flutter_mozilla_components.api.GeckoBrowserApiImpl
|
||||||
|
import eu.lensai.flutter_mozilla_components.api.GeckoContainerProxyApiImpl
|
||||||
import eu.lensai.flutter_mozilla_components.api.GeckoCookieApiImpl
|
import eu.lensai.flutter_mozilla_components.api.GeckoCookieApiImpl
|
||||||
import eu.lensai.flutter_mozilla_components.api.GeckoDeleteBrowsingDataControllerImpl
|
import eu.lensai.flutter_mozilla_components.api.GeckoDeleteBrowsingDataControllerImpl
|
||||||
import eu.lensai.flutter_mozilla_components.api.GeckoEngineSettingsApiImpl
|
import eu.lensai.flutter_mozilla_components.api.GeckoEngineSettingsApiImpl
|
||||||
@@ -20,6 +21,7 @@ import eu.lensai.flutter_mozilla_components.feature.DefaultSelectionActionDelega
|
|||||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoAddonEvents
|
import eu.lensai.flutter_mozilla_components.pigeons.GeckoAddonEvents
|
||||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoAddonsApi
|
import eu.lensai.flutter_mozilla_components.pigeons.GeckoAddonsApi
|
||||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoBrowserApi
|
import eu.lensai.flutter_mozilla_components.pigeons.GeckoBrowserApi
|
||||||
|
import eu.lensai.flutter_mozilla_components.pigeons.GeckoContainerProxyApi
|
||||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoCookieApi
|
import eu.lensai.flutter_mozilla_components.pigeons.GeckoCookieApi
|
||||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoDeleteBrowsingDataController
|
import eu.lensai.flutter_mozilla_components.pigeons.GeckoDeleteBrowsingDataController
|
||||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoEngineSettingsApi
|
import eu.lensai.flutter_mozilla_components.pigeons.GeckoEngineSettingsApi
|
||||||
@@ -46,6 +48,12 @@ import mozilla.components.support.base.log.sink.AndroidLogSink
|
|||||||
|
|
||||||
/** FlutterMozillaComponentsPlugin */
|
/** FlutterMozillaComponentsPlugin */
|
||||||
class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
|
class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
|
||||||
|
companion object {
|
||||||
|
private const val FRAGMENT_CONTAINER_ID = 0xBEEF
|
||||||
|
|
||||||
|
private var isGeckoInitialized = false
|
||||||
|
}
|
||||||
|
|
||||||
private val components by lazy {
|
private val components by lazy {
|
||||||
requireNotNull(GlobalComponents.components) { "Components not initialized" }
|
requireNotNull(GlobalComponents.components) { "Components not initialized" }
|
||||||
}
|
}
|
||||||
@@ -58,11 +66,18 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
|
|||||||
private var isPlatformViewRegistered = false
|
private var isPlatformViewRegistered = false
|
||||||
private var pendingFragmentShow = false
|
private var pendingFragmentShow = false
|
||||||
|
|
||||||
init {
|
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
Log.addSink(AndroidLogSink())
|
synchronized(this) {
|
||||||
|
if(!isGeckoInitialized) {
|
||||||
|
Log.addSink(AndroidLogSink())
|
||||||
|
|
||||||
|
setupGeckoEngine(flutterPluginBinding)
|
||||||
|
isGeckoInitialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
private fun setupGeckoEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
_flutterPluginBinding = flutterPluginBinding
|
_flutterPluginBinding = flutterPluginBinding
|
||||||
|
|
||||||
_flutterEvents = GeckoStateEvents(_flutterPluginBinding.binaryMessenger)
|
_flutterEvents = GeckoStateEvents(_flutterPluginBinding.binaryMessenger)
|
||||||
@@ -99,6 +114,7 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
|
|||||||
GeckoIconsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoIconsApiImpl())
|
GeckoIconsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoIconsApiImpl())
|
||||||
GeckoCookieApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoCookieApiImpl())
|
GeckoCookieApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoCookieApiImpl())
|
||||||
GeckoPrefApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoPrefApiImpl())
|
GeckoPrefApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoPrefApiImpl())
|
||||||
|
GeckoContainerProxyApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoContainerProxyApiImpl())
|
||||||
GeckoFindApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoFindApiImpl())
|
GeckoFindApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoFindApiImpl())
|
||||||
GeckoSelectionActionController.setUp(_flutterPluginBinding.binaryMessenger, GeckoSelectionActionControllerImpl(
|
GeckoSelectionActionController.setUp(_flutterPluginBinding.binaryMessenger, GeckoSelectionActionControllerImpl(
|
||||||
selectionActionDelegate
|
selectionActionDelegate
|
||||||
@@ -169,8 +185,4 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
|
|||||||
isPlatformViewRegistered = false
|
isPlatformViewRegistered = false
|
||||||
pendingFragmentShow = false
|
pendingFragmentShow = false
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val FRAGMENT_CONTAINER_ID = 0xBEEF
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package eu.lensai.flutter_mozilla_components.api
|
||||||
|
|
||||||
|
import eu.lensai.flutter_mozilla_components.feature.ContainerProxyFeature
|
||||||
|
import eu.lensai.flutter_mozilla_components.pigeons.GeckoContainerProxyApi
|
||||||
|
|
||||||
|
class GeckoContainerProxyApiImpl : GeckoContainerProxyApi {
|
||||||
|
override fun setProxyPort(port: Long) {
|
||||||
|
ContainerProxyFeature.scheduleRequest("setProxyPort", port.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addContainerProxy(contextId: String) {
|
||||||
|
ContainerProxyFeature.scheduleRequest("addContainerProxy", contextId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeContainerProxy(contextId: String) {
|
||||||
|
ContainerProxyFeature.scheduleRequest("removeContainerProxy", contextId)
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
package eu.lensai.flutter_mozilla_components.feature
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import mozilla.components.concept.engine.webextension.MessageHandler
|
||||||
|
import mozilla.components.concept.engine.webextension.Port
|
||||||
|
import mozilla.components.concept.engine.webextension.WebExtensionRuntime
|
||||||
|
import mozilla.components.support.base.log.logger.Logger
|
||||||
|
import mozilla.components.support.webextensions.WebExtensionController
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
object ContainerProxyFeature {
|
||||||
|
private val logger = Logger("container_proxy")
|
||||||
|
|
||||||
|
private const val CONTAINER_PROXY_REPORTER_EXTENSION_ID = "container-proxy@lensai.eu"
|
||||||
|
private const val CONTAINER_PROXY_REPORTER_EXTENSION_URL = "resource://android/assets/extensions/container_proxy/"
|
||||||
|
private const val CONTAINER_PROXY_REPORTER_MESSAGING_ID = "containerProxy"
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
// This is an internal var to make it mutable for unit testing purposes only
|
||||||
|
internal var extensionController = WebExtensionController(
|
||||||
|
CONTAINER_PROXY_REPORTER_EXTENSION_ID,
|
||||||
|
CONTAINER_PROXY_REPORTER_EXTENSION_URL,
|
||||||
|
CONTAINER_PROXY_REPORTER_MESSAGING_ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun scheduleRequest(command: String, args: Any) {
|
||||||
|
val message = JSONObject()
|
||||||
|
message.put("action", command);
|
||||||
|
message.put("args", args)
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
extensionController.sendBackgroundMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ContainerProxyBackgroundMessageHandler() : MessageHandler {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the web extension in the runtime through the WebExtensionRuntime install method
|
||||||
|
*
|
||||||
|
* @param runtime a WebExtensionRuntime.
|
||||||
|
* @param productName a custom product name used to automatically label reports. Defaults to
|
||||||
|
* "android-components".
|
||||||
|
*/
|
||||||
|
fun install(runtime: WebExtensionRuntime) {
|
||||||
|
extensionController.registerBackgroundMessageHandler(
|
||||||
|
ContainerProxyBackgroundMessageHandler()
|
||||||
|
)
|
||||||
|
extensionController.install(
|
||||||
|
runtime,
|
||||||
|
onSuccess = {
|
||||||
|
logger.debug("Installed ContainerProxy webextension: ${it.id}")
|
||||||
|
},
|
||||||
|
onError = { throwable ->
|
||||||
|
logger.error("Failed to install ContainerProxy webextension: ", throwable)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
-1
@@ -1,4 +1,4 @@
|
|||||||
// Autogenerated from Pigeon (v22.7.4), do not edit directly.
|
// Autogenerated from Pigeon (v24.1.1), do not edit directly.
|
||||||
// See also: https://pub.dev/packages/pigeon
|
// See also: https://pub.dev/packages/pigeon
|
||||||
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||||
|
|
||||||
@@ -2567,6 +2567,78 @@ interface GeckoPrefApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
|
interface GeckoContainerProxyApi {
|
||||||
|
fun setProxyPort(port: Long)
|
||||||
|
fun addContainerProxy(contextId: String)
|
||||||
|
fun removeContainerProxy(contextId: String)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by GeckoContainerProxyApi. */
|
||||||
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
|
GeckoPigeonCodec()
|
||||||
|
}
|
||||||
|
/** Sets up an instance of `GeckoContainerProxyApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
@JvmOverloads
|
||||||
|
fun setUp(binaryMessenger: BinaryMessenger, api: GeckoContainerProxyApi?, messageChannelSuffix: String = "") {
|
||||||
|
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setProxyPort$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val portArg = args[0] as Long
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
api.setProxyPort(portArg)
|
||||||
|
listOf(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.addContainerProxy$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val contextIdArg = args[0] as String
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
api.addContainerProxy(contextIdArg)
|
||||||
|
listOf(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.removeContainerProxy$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val contextIdArg = args[0] as String
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
api.removeContainerProxy(contextIdArg)
|
||||||
|
listOf(null)
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
interface GeckoCookieApi {
|
interface GeckoCookieApi {
|
||||||
fun getCookie(firstPartyDomain: String?, name: String, partitionKey: CookiePartitionKey?, storeId: String?, url: String, callback: (Result<Cookie>) -> Unit)
|
fun getCookie(firstPartyDomain: String?, name: String, partitionKey: CookiePartitionKey?, storeId: String?, url: String, callback: (Result<Cookie>) -> Unit)
|
||||||
fun getAllCookies(domain: String?, firstPartyDomain: String?, name: String?, partitionKey: CookiePartitionKey?, storeId: String?, url: String, callback: (Result<List<Cookie>>) -> Unit)
|
fun getAllCookies(domain: String?, firstPartyDomain: String?, name: String?, partitionKey: CookiePartitionKey?, storeId: String?, url: String, callback: (Result<List<Cookie>>) -> Unit)
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ android {
|
|||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8
|
jvmTarget = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.3.1" apply false
|
id "com.android.application" version "8.7.0" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.9.22" apply false
|
id "org.jetbrains.kotlin.android" version "1.9.22" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
dist
|
||||||
|
web-ext-artifacts
|
||||||
|
source-artifacts
|
||||||
|
node_modules
|
||||||
|
tmpProfile
|
||||||
|
*.module.scss.d.ts
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
BSD 2-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2019, Bekh-Ivanov Aleksey
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Notes to Reviewer
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[](https://gitter.im/firefox-container-proxy/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
[](https://circleci.com/gh/bekh6ex/firefox-container-proxy/tree/master)
|
||||||
|
[](https://hosted.weblate.org/engage/firefox-container-proxy/)
|
||||||
|
|
||||||
|
|
||||||
|
[Extension page](https://addons.mozilla.org/en-US/firefox/addon/container-proxy/)
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
* **cookies**: needed to identify to which container request belongs
|
||||||
|
* **webRequest** and **webRequestBlocking**: to supply credentials for proxy authorization (but not for normal web authorization)
|
||||||
|
|
||||||
|
## Good to know
|
||||||
|
|
||||||
|
There is a known issue with DNS leak happening in non-default containers when uBlock is installed. The issue is not resolvable by this extension, but can be resolved adjusting in uBlock settings. See [comment](https://github.com/bekh6ex/firefox-container-proxy/issues/23#issuecomment-773249909)
|
||||||
|
|
||||||
|
## Translation
|
||||||
|
[](https://hosted.weblate.org/engage/firefox-container-proxy/)
|
||||||
|
|
||||||
|
[Translate to your language](https://hosted.weblate.org/engage/firefox-container-proxy/)
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
// We need to tell TypeScript that when we write "import styles from './styles.scss' we mean to load a module (to look for a './styles.scss.d.ts').
|
||||||
|
declare module '*.module.scss';
|
||||||
+17856
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"name": "container-proxy",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "[Firefox extension] Assign a proxy to a Firefox container [Extension page](https://addons.mozilla.org/en-US/firefox/addon/container-proxy/)",
|
||||||
|
"scripts": {
|
||||||
|
"test": "pnpm run test:unit",
|
||||||
|
"bundle": "web-ext -s dist build --overwrite-dest",
|
||||||
|
"bundle:src": "V=$(cat src/manifest.json | jq -r .version) && zip -r -X source-artifacts/container-proxy-v${V}.zip src babel.config.js LICENSE package.json package-lock.json README.md tsconfig.json webpack.config.js",
|
||||||
|
"test:func": "pnpm run build && mocha test/functional/",
|
||||||
|
"test:unit": "ts-mocha --require ts-node/register test/unit/**/*.test.ts",
|
||||||
|
"codeStyle:check": "standardx && ts-standard",
|
||||||
|
"codeStyle:fix": "standardx --fix && ts-standard --fix",
|
||||||
|
"lint": "addons-linter dist",
|
||||||
|
"debug": "web-ext run -s dist --verbose --browser-console --keep-profile-changes -p ./tmpProfile --start-url about:debugging --start-url about:addons --pref=intl.locale.requested=en #--pref=intl.locale.matchOS=false",
|
||||||
|
"ci:test-unit": "pnpm run test:unit",
|
||||||
|
"ci:test-func": "HEADLESS=true pnpm run test:func",
|
||||||
|
"ci:style-check": "pnpm run codeStyle:check",
|
||||||
|
"ci:lint": "pnpm run build && pnpm run lint",
|
||||||
|
"build": "webpack",
|
||||||
|
"build:watch": "webpack --watch",
|
||||||
|
"clean": "rm -rf ./dist"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/bekh6ex/firefox-container-proxy.git"
|
||||||
|
},
|
||||||
|
"author": "Aleksei Bekh-Ivanov",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/bekh6ex/firefox-container-proxy/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://addons.mozilla.org/en-US/firefox/addon/container-proxy/",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/chai": "^5.0.1",
|
||||||
|
"@types/firefox-webext-browser": "^120.0.4",
|
||||||
|
"@types/mocha": "^10.0.10",
|
||||||
|
"addons-linter": "^7.8.0",
|
||||||
|
"chai": "^5.1.2",
|
||||||
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"css-modules-typescript-loader": "^4.0.1",
|
||||||
|
"esm": "^3.2.25",
|
||||||
|
"mocha": "^10.8.2",
|
||||||
|
"sass-loader": "^16.0.4",
|
||||||
|
"sinon": "^8.1.1",
|
||||||
|
"standardx": "^7.0.0",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
|
"ts-loader": "^9.2.6",
|
||||||
|
"ts-mocha": "^10.0.0",
|
||||||
|
"ts-standard": "^12.0.2",
|
||||||
|
"tsconfig-paths-webpack-plugin": "^4.2.0",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"web-ext": "^8.4.0",
|
||||||
|
"webextensions-api-fake": "^1.3.0",
|
||||||
|
"webextensions-geckodriver": "^0.7.0",
|
||||||
|
"webpack": "^5.59.1",
|
||||||
|
"webpack-cli": "^6.0.1"
|
||||||
|
},
|
||||||
|
"standardx": {
|
||||||
|
"cache": false,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"globals": [
|
||||||
|
"browser",
|
||||||
|
"expect",
|
||||||
|
"store"
|
||||||
|
],
|
||||||
|
"ignore": [
|
||||||
|
"dist",
|
||||||
|
"/src/lib/*",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"test/**/*.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ts-standard": {
|
||||||
|
"ignore": [
|
||||||
|
"dist",
|
||||||
|
"src/**/*.js",
|
||||||
|
"test/**/*.js",
|
||||||
|
"src/options/import/FoxyProxyConverter.ts",
|
||||||
|
"test/unit/options/import/FoxyProxyConverter.test.ts"
|
||||||
|
],
|
||||||
|
"cache": false,
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"globals": [
|
||||||
|
"browser",
|
||||||
|
"expect",
|
||||||
|
"store"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"rules": {
|
||||||
|
"no-unused-expressions": "error"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.test.js"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"no-unused-expressions": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
import { Store } from '../store/Store'
|
||||||
|
import { HttpProxySettings, HttpsProxySettings, ProxySettings } from '../domain/ProxySettings'
|
||||||
|
import { ProxyInfo, Socks5ProxyInfo } from '../domain/ProxyInfo'
|
||||||
|
import { ProxyType } from '../domain/ProxyType'
|
||||||
|
import BlockingResponse = browser.webRequest.BlockingResponse
|
||||||
|
import _OnAuthRequiredDetails = browser.webRequest._OnAuthRequiredDetails
|
||||||
|
import _OnRequestDetails = browser.proxy._OnRequestDetails
|
||||||
|
|
||||||
|
const localhosts = new Set(['localhost', '127.0.0.1', '[::1]'])
|
||||||
|
|
||||||
|
const containerIdentifier = 'firefox-container-'
|
||||||
|
|
||||||
|
type DoNotProxy = never[]
|
||||||
|
export const doNotProxy: DoNotProxy = []
|
||||||
|
|
||||||
|
const emergencyBreak: Socks5ProxyInfo = {
|
||||||
|
type: ProxyType.Socks5,
|
||||||
|
host: 'emergency-break-proxy.localhost',
|
||||||
|
port: 1,
|
||||||
|
failoverTimeout: 1,
|
||||||
|
username: 'nonexistent user',
|
||||||
|
password: 'dummy password',
|
||||||
|
proxyDNS: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class BackgroundMain {
|
||||||
|
store: Store
|
||||||
|
|
||||||
|
constructor({ store }: { store: Store }) {
|
||||||
|
this.store = store
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeAuthListener(cookieStoreId: string, proxy: HttpProxySettings | HttpsProxySettings): void {
|
||||||
|
const listener: (details: _OnAuthRequiredDetails) => BlockingResponse = (details) => {
|
||||||
|
if (!details.isProxy) return {}
|
||||||
|
|
||||||
|
if (details.cookieStoreId !== cookieStoreId) return {}
|
||||||
|
|
||||||
|
// TODO: Fix in @types/firefox-webext-browser
|
||||||
|
// @ts-expect-error
|
||||||
|
const info = details.proxyInfo
|
||||||
|
if (info.host !== proxy.host || info.port !== proxy.port || info.type !== proxy.type) return {}
|
||||||
|
|
||||||
|
const result = { authCredentials: { username: proxy.username, password: proxy.password } }
|
||||||
|
|
||||||
|
browser.webRequest.onAuthRequired.removeListener(listener)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.webRequest.onAuthRequired.addListener(
|
||||||
|
listener,
|
||||||
|
{ urls: ['<all_urls>'] },
|
||||||
|
['blocking']
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Fix in @types/firefox-webext-browser
|
||||||
|
async onRequest(requestDetails: Pick<_OnRequestDetails, 'cookieStoreId' | 'url' | 'tabId'>): Promise<DoNotProxy | ProxyInfo[]> {
|
||||||
|
const tab = (await browser.tabs.get(requestDetails.tabId))
|
||||||
|
|
||||||
|
if (tab.cookieStoreId?.startsWith(containerIdentifier) === true) {
|
||||||
|
try {
|
||||||
|
const cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length)
|
||||||
|
|
||||||
|
const proxies = await this.store.getProxiesForContainer(cookieStoreId)
|
||||||
|
|
||||||
|
if (proxies.length > 0) {
|
||||||
|
proxies.forEach(p => {
|
||||||
|
if (p.type === ProxyType.Http || p.type === ProxyType.Https) {
|
||||||
|
this.initializeAuthListener(cookieStoreId, p)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const result: ProxyInfo[] = proxies.filter((p: ProxySettings) => {
|
||||||
|
try {
|
||||||
|
const documentUrl = new URL(requestDetails.url)
|
||||||
|
const isLocalhost = localhosts.has(documentUrl.hostname)
|
||||||
|
if (isLocalhost && p.doNotProxyLocal) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}).map(p => p.asProxyInfo())
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
return [emergencyBreak]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return [emergencyBreak]
|
||||||
|
} catch (e: unknown) {
|
||||||
|
console.error(`Error in onRequest listener: ${e as string}`)
|
||||||
|
return [emergencyBreak]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return doNotProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
run(browser: { proxy: any }): void {
|
||||||
|
const filter = { urls: ['<all_urls>'] }
|
||||||
|
|
||||||
|
browser.proxy.onRequest.addListener(this.onRequest.bind(this), filter)
|
||||||
|
|
||||||
|
browser.proxy.onError.addListener((e: Error) => {
|
||||||
|
console.error('Proxy error', e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
import { Socks5ProxySettings } from 'src/domain/ProxySettings';
|
||||||
|
import { Store } from '../store/Store'
|
||||||
|
import BackgroundMain from './BackgroundMain'
|
||||||
|
|
||||||
|
console.log('Background script started')
|
||||||
|
|
||||||
|
const store = new Store()
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
action: 'setProxyPort' | 'addContainerProxy' | 'removeContainerProxy';
|
||||||
|
args: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = browser.runtime.connectNative("containerProxy");
|
||||||
|
port.onMessage.addListener((raw: unknown): void => {
|
||||||
|
const message = raw as Message;
|
||||||
|
switch (message.action) {
|
||||||
|
case "setProxyPort":
|
||||||
|
store.putProxy(new Socks5ProxySettings({
|
||||||
|
id: 'tor',
|
||||||
|
type: 'socks',
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: message.args,
|
||||||
|
doNotProxyLocal: true,
|
||||||
|
title: 'Tor',
|
||||||
|
proxyDNS: true,
|
||||||
|
}))
|
||||||
|
console.log('put tor port ' + message.args)
|
||||||
|
|
||||||
|
break
|
||||||
|
case "addContainerProxy":
|
||||||
|
store.setContainerProxyRelation(message.args, "tor")
|
||||||
|
console.log('added container relation ' + message.args)
|
||||||
|
break
|
||||||
|
case "removeContainerProxy":
|
||||||
|
store.removeContainerProxyRelation(message.args, "tor")
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const backgroundListener = new BackgroundMain({ store })
|
||||||
|
backgroundListener.run(browser)
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
import { ProxyType } from './ProxyType'
|
||||||
|
|
||||||
|
export type ProxyInfo = Socks4ProxyInfo | Socks5ProxyInfo | HttpProxyInfo | HttpsProxyInfo
|
||||||
|
|
||||||
|
interface ProxyInfoBase<TY extends ProxyType> {
|
||||||
|
type: TY
|
||||||
|
host: string
|
||||||
|
port: number
|
||||||
|
failoverTimeout: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Socks5ProxyInfo extends ProxyInfoBase<ProxyType.Socks5> {
|
||||||
|
type: ProxyType.Socks5
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
proxyDNS: boolean
|
||||||
|
proxyAuthorizationHeader?: undefined // MUST not present, otherwise failure that cannot be prevented/recovered from
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Socks4ProxyInfo extends ProxyInfoBase<ProxyType.Socks4> {
|
||||||
|
type: ProxyType.Socks4
|
||||||
|
proxyDNS: boolean
|
||||||
|
proxyAuthorizationHeader?: undefined // MUST not present, otherwise failure that cannot be prevented/recovered from
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpsProxyInfo extends ProxyInfoBase<ProxyType.Https> {
|
||||||
|
type: ProxyType.Https
|
||||||
|
proxyDNS?: undefined
|
||||||
|
proxyAuthorizationHeader: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpProxyInfo extends ProxyInfoBase<ProxyType.Http> {
|
||||||
|
type: ProxyType.Http
|
||||||
|
proxyDNS?: undefined
|
||||||
|
proxyAuthorizationHeader?: undefined // MUST not present, otherwise failure that cannot be prevented/recovered from
|
||||||
|
}
|
||||||
+170
@@ -0,0 +1,170 @@
|
|||||||
|
import { ProxyType } from './ProxyType'
|
||||||
|
import { ProxyDao } from '../store/Store'
|
||||||
|
import { HttpProxyInfo, HttpsProxyInfo, ProxyInfo, Socks4ProxyInfo, Socks5ProxyInfo } from './ProxyInfo'
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-namespace,@typescript-eslint/no-redeclare */
|
||||||
|
|
||||||
|
export type ProxySettings = Socks4ProxySettings | Socks5ProxySettings | HttpProxySettings | HttpsProxySettings
|
||||||
|
|
||||||
|
const failoverTimeout = 5
|
||||||
|
|
||||||
|
export namespace ProxySettings {
|
||||||
|
|
||||||
|
export function tryFromDao(dao: ProxyDao): ProxySettings | undefined {
|
||||||
|
switch (dao.type) {
|
||||||
|
case 'socks':
|
||||||
|
return new Socks5ProxySettings(dao)
|
||||||
|
case 'socks4':
|
||||||
|
return new Socks4ProxySettings(dao)
|
||||||
|
case 'http':
|
||||||
|
return new HttpProxySettings(dao)
|
||||||
|
case 'https':
|
||||||
|
return new HttpsProxySettings(dao)
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ProxySettingsBase<PI extends ProxyInfo> {
|
||||||
|
readonly id: string
|
||||||
|
readonly title: string
|
||||||
|
readonly host: string
|
||||||
|
readonly port: number
|
||||||
|
readonly doNotProxyLocal: boolean
|
||||||
|
|
||||||
|
protected constructor(dao: ProxyDao) {
|
||||||
|
this.id = dao.id
|
||||||
|
this.title = dao.title
|
||||||
|
this.host = dao.host
|
||||||
|
this.port = dao.port
|
||||||
|
this.doNotProxyLocal = dao.doNotProxyLocal
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract get type(): PI['type']
|
||||||
|
|
||||||
|
abstract asProxyInfo(): PI
|
||||||
|
|
||||||
|
abstract asDao(): ProxyDao
|
||||||
|
|
||||||
|
get url(): string {
|
||||||
|
return `${this.type}://${this.host}:${this.port}`
|
||||||
|
}
|
||||||
|
|
||||||
|
protected baseDao(): ProxyDao {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
title: this.title,
|
||||||
|
type: this.type,
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
doNotProxyLocal: this.doNotProxyLocal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Socks5ProxySettings extends ProxySettingsBase<Socks5ProxyInfo> {
|
||||||
|
readonly username?: string
|
||||||
|
readonly password?: string
|
||||||
|
readonly proxyDNS: boolean
|
||||||
|
|
||||||
|
constructor(dao: ProxyDao) {
|
||||||
|
super(dao)
|
||||||
|
this.username = dao.username
|
||||||
|
this.password = dao.password
|
||||||
|
this.proxyDNS = dao.proxyDNS ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
get type(): ProxyType.Socks5 {
|
||||||
|
return ProxyType.Socks5
|
||||||
|
}
|
||||||
|
|
||||||
|
asProxyInfo(): Socks5ProxyInfo {
|
||||||
|
return {
|
||||||
|
type: this.type,
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
username: this.username ?? '',
|
||||||
|
password: this.password ?? '',
|
||||||
|
proxyDNS: this.proxyDNS,
|
||||||
|
failoverTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asDao(): ProxyDao {
|
||||||
|
return { ...super.baseDao(), username: this.username, password: this.password, proxyDNS: this.proxyDNS }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Socks4ProxySettings extends ProxySettingsBase<Socks4ProxyInfo> {
|
||||||
|
readonly proxyDNS: boolean
|
||||||
|
|
||||||
|
constructor(dao: ProxyDao) {
|
||||||
|
super(dao)
|
||||||
|
this.proxyDNS = dao.proxyDNS ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
get type(): ProxyType.Socks4 {
|
||||||
|
return ProxyType.Socks4
|
||||||
|
}
|
||||||
|
|
||||||
|
asProxyInfo(): Socks4ProxyInfo {
|
||||||
|
return {
|
||||||
|
type: this.type,
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
proxyDNS: this.proxyDNS,
|
||||||
|
failoverTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asDao(): ProxyDao {
|
||||||
|
return { ...super.baseDao(), proxyDNS: this.proxyDNS }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class HttpBasedProxySettings<PI extends (HttpsProxyInfo | HttpProxyInfo)> extends ProxySettingsBase<PI> {
|
||||||
|
readonly username?: string
|
||||||
|
readonly password?: string
|
||||||
|
|
||||||
|
constructor(dao: ProxyDao) {
|
||||||
|
super(dao)
|
||||||
|
this.username = dao.username
|
||||||
|
this.password = dao.password
|
||||||
|
}
|
||||||
|
|
||||||
|
asDao(): ProxyDao {
|
||||||
|
return { ...super.baseDao(), username: this.username, password: this.password }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpsProxySettings extends HttpBasedProxySettings<HttpsProxyInfo> {
|
||||||
|
get type(): ProxyType.Https {
|
||||||
|
return ProxyType.Https
|
||||||
|
}
|
||||||
|
|
||||||
|
asProxyInfo(): HttpsProxyInfo {
|
||||||
|
return {
|
||||||
|
type: this.type,
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
proxyAuthorizationHeader: '', //generateAuthorizationHeader(this.username ?? '', this.password ?? ''),
|
||||||
|
failoverTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpProxySettings extends HttpBasedProxySettings<HttpProxyInfo> {
|
||||||
|
get type(): ProxyType.Http {
|
||||||
|
return ProxyType.Http
|
||||||
|
}
|
||||||
|
|
||||||
|
asProxyInfo(): HttpProxyInfo {
|
||||||
|
return {
|
||||||
|
type: this.type,
|
||||||
|
host: this.host,
|
||||||
|
port: this.port,
|
||||||
|
failoverTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-namespace,no-redeclare,import/export */
|
||||||
|
|
||||||
|
export enum ProxyType {
|
||||||
|
Socks5 = 'socks',
|
||||||
|
Socks4 = 'socks4',
|
||||||
|
Http = 'http',
|
||||||
|
Https = 'https'
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ProxyType {
|
||||||
|
export function tryFromString(s: string): ProxyType | undefined {
|
||||||
|
switch (s) {
|
||||||
|
case 'socks':
|
||||||
|
return ProxyType.Socks5
|
||||||
|
case 'socks4':
|
||||||
|
return ProxyType.Socks4
|
||||||
|
case 'http':
|
||||||
|
return ProxyType.Http
|
||||||
|
case 'https':
|
||||||
|
return ProxyType.Https
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Container proxy",
|
||||||
|
"version": "0.1.22",
|
||||||
|
"description": "Container Proxy",
|
||||||
|
"browser_specific_settings": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "container-proxy@lensai.eu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"<all_urls>",
|
||||||
|
"geckoViewAddons",
|
||||||
|
"nativeMessaging",
|
||||||
|
"nativeMessagingFromContent",
|
||||||
|
"webRequest",
|
||||||
|
"webRequestBlocking",
|
||||||
|
"storage",
|
||||||
|
"contextualIdentities",
|
||||||
|
"cookies",
|
||||||
|
"proxy"
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background/index.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import { ProxyInfo } from '../domain/ProxyInfo'
|
||||||
|
import { ProxyType } from '../domain/ProxyType'
|
||||||
|
import { ProxySettings } from '../domain/ProxySettings'
|
||||||
|
const tryFromDao = ProxySettings.tryFromDao
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-namespace,no-redeclare,import/export */
|
||||||
|
|
||||||
|
export interface ProxyDao {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
type: string
|
||||||
|
host: string
|
||||||
|
port: number
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
proxyDNS?: boolean
|
||||||
|
doNotProxyLocal: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ProxyDao {
|
||||||
|
export function toProxyInfo(proxy: Pick<ProxyDao, 'type' | 'host' | 'port' | 'username' | 'password' | 'proxyDNS'>): ProxyInfo | undefined {
|
||||||
|
const type = ProxyType.tryFromString(proxy.type)
|
||||||
|
if (type === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = {
|
||||||
|
host: proxy.host,
|
||||||
|
port: proxy.port,
|
||||||
|
failoverTimeout: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ProxyType.Socks5:
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
...base,
|
||||||
|
username: proxy.username ?? '',
|
||||||
|
password: proxy.password ?? '',
|
||||||
|
proxyDNS: proxy.proxyDNS ?? true
|
||||||
|
}
|
||||||
|
case ProxyType.Socks4:
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
...base,
|
||||||
|
proxyDNS: proxy.proxyDNS ?? true
|
||||||
|
}
|
||||||
|
case ProxyType.Http:
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
...base
|
||||||
|
}
|
||||||
|
case ProxyType.Https:
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
...base,
|
||||||
|
proxyAuthorizationHeader: '' //generateAuthorizationHeader(proxy.username ?? '', proxy.password ?? '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Store {
|
||||||
|
private proxies: ProxyDao[] = []
|
||||||
|
private relations: { [key: string]: string[] } = {}
|
||||||
|
|
||||||
|
async getAllProxies(): Promise<ProxySettings[]> {
|
||||||
|
const proxyDaos = await this.getAllProxyDaos()
|
||||||
|
const result: ProxySettings[] = proxyDaos.map(tryFromDao).filter(p => p !== undefined) as ProxySettings[]
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProxyById(id: string): Promise<ProxySettings | null> {
|
||||||
|
const proxies = await this.getAllProxies()
|
||||||
|
const index = proxies.findIndex(p => p.id === id)
|
||||||
|
if (index === -1) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return proxies[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async putProxy(proxy: ProxySettings): Promise<void> {
|
||||||
|
const proxies = await this.getAllProxyDaos()
|
||||||
|
const index = proxies.findIndex(p => p.id === proxy.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
proxies[index] = proxy.asDao()
|
||||||
|
} else {
|
||||||
|
proxies.push(proxy.asDao())
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.saveProxyDaos(proxies)
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteProxyById(id: string): Promise<void> {
|
||||||
|
const proxies = await this.getAllProxyDaos()
|
||||||
|
const index = proxies.findIndex(p => p.id === id)
|
||||||
|
if (index !== -1) {
|
||||||
|
proxies.splice(index, 1)
|
||||||
|
await this.saveProxyDaos(proxies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRelations(): Promise<{ [key: string]: string[] }> {
|
||||||
|
return this.relations
|
||||||
|
}
|
||||||
|
|
||||||
|
async setContainerProxyRelation(cookieStoreId: string, proxyId: string): Promise<void> {
|
||||||
|
this.relations[cookieStoreId] = [proxyId]
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeContainerProxyRelation(cookieStoreId: string, proxyId: string): Promise<void> {
|
||||||
|
const currentRelations = this.relations[cookieStoreId] ?? []
|
||||||
|
this.relations[cookieStoreId] = currentRelations.filter(id => id !== proxyId)
|
||||||
|
|
||||||
|
// If no relations left for this container, clean up by removing the key
|
||||||
|
if (this.relations[cookieStoreId].length === 0) {
|
||||||
|
delete this.relations[cookieStoreId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProxiesForContainer(cookieStoreId: string): Promise<ProxySettings[]> {
|
||||||
|
const relations = await this.getRelations()
|
||||||
|
|
||||||
|
const proxyIds: string[] = relations[cookieStoreId] ?? []
|
||||||
|
|
||||||
|
if (proxyIds.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxies = await this.getAllProxies()
|
||||||
|
const proxyById: { [key: string]: ProxySettings } = {}
|
||||||
|
proxies.forEach(function (p) { proxyById[p.id] = p })
|
||||||
|
|
||||||
|
return proxyIds.map(pId => proxyById[pId])
|
||||||
|
.filter(p => p !== undefined)
|
||||||
|
.map(fillInDefaults)
|
||||||
|
.map(tryFromDao)
|
||||||
|
.filter(p => p !== undefined) as ProxySettings[]
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveProxyDaos(p: ProxyDao[]): Promise<void> {
|
||||||
|
this.proxies = p
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAllProxyDaos(): Promise<ProxyDao[]> {
|
||||||
|
return this.proxies.map(fillInDefaults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillInDefaults(proxy: Partial<ProxyDao>): ProxyDao {
|
||||||
|
if (proxy.title === undefined) {
|
||||||
|
proxy.title = ''
|
||||||
|
}
|
||||||
|
if (typeof proxy.doNotProxyLocal === 'undefined') {
|
||||||
|
proxy.doNotProxyLocal = true
|
||||||
|
}
|
||||||
|
if (typeof proxy.proxyDNS === 'undefined') {
|
||||||
|
if (proxy.type === 'socks' || proxy.type === 'socks4') {
|
||||||
|
proxy.proxyDNS = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return proxy as ProxyDao
|
||||||
|
}
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
const PageObject = require('./page-objects/PageObject.js')
|
||||||
|
const OptionsPageObject = require('./page-objects/OptionsPageObject.js')
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
const assert = require('chai')
|
||||||
|
const expect = assert.expect
|
||||||
|
|
||||||
|
const webExtensionsGeckoDriver = require('webextensions-geckodriver')
|
||||||
|
const { webdriver, firefox } = webExtensionsGeckoDriver
|
||||||
|
const { until, By } = webdriver
|
||||||
|
|
||||||
|
const manifestPath = path.resolve(path.join(__dirname, '../../dist/manifest.json'))
|
||||||
|
|
||||||
|
describe('Container Proxy extension', function () {
|
||||||
|
let geckodriver
|
||||||
|
this.timeout(30000)
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
const fxOptions = new firefox.Options()
|
||||||
|
if (process.env.HEADLESS) {
|
||||||
|
fxOptions.headless()
|
||||||
|
.windowSize({ height: 1080, width: 1920 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const webExtension = await webExtensionsGeckoDriver(manifestPath, { fxOptions })
|
||||||
|
geckodriver = webExtension.geckodriver
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add a proxy', async () => {
|
||||||
|
const helper = new Helper(geckodriver)
|
||||||
|
|
||||||
|
const options = await helper.openOptionsPage()
|
||||||
|
|
||||||
|
let proxyList = await options.openProxyList()
|
||||||
|
|
||||||
|
const proxyForm = await proxyList.openAddProxyForm()
|
||||||
|
|
||||||
|
await proxyForm.selectProtocol('socks')
|
||||||
|
await proxyForm.typeInServer('localhost')
|
||||||
|
await proxyForm.typeInPort(1080)
|
||||||
|
await proxyForm.typeInUsername('user')
|
||||||
|
await proxyForm.typeInPassword('password')
|
||||||
|
|
||||||
|
await proxyForm.testSettings()
|
||||||
|
|
||||||
|
proxyList = await proxyForm.saveSettings()
|
||||||
|
|
||||||
|
const proxyLabel = 'socks://localhost:1080'
|
||||||
|
await geckodriver.wait(async () => {
|
||||||
|
const row = await geckodriver.wait(until.elementLocated(
|
||||||
|
By.css('.proxy-list-item:first-of-type')
|
||||||
|
), 2000)
|
||||||
|
|
||||||
|
const label = row.findElement(By.css('.proxy-name'))
|
||||||
|
|
||||||
|
const text = await label.getText()
|
||||||
|
return text === proxyLabel
|
||||||
|
}, 1000, 'Should show proxy in the list')
|
||||||
|
|
||||||
|
const assign = await options.openAssignProxy()
|
||||||
|
const defaultContainerSelect = await assign.defaultContainerSelect()
|
||||||
|
await defaultContainerSelect.selectByLabel(proxyLabel)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip('should contain IP address text', async () => {
|
||||||
|
await geckodriver.setContext(firefox.Context.CONTENT)
|
||||||
|
await geckodriver.get('https://api.duckduckgo.com/?q=ip&no_html=1&format=json&t=firefox-container-proxy-extension')
|
||||||
|
const text = await geckodriver.getPageSource()
|
||||||
|
|
||||||
|
expect(text).to.include('Your IP address is')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should successfully use SOCKS5 proxy for default container', async () => {
|
||||||
|
const helper = new Helper(geckodriver)
|
||||||
|
|
||||||
|
const optionsPage = await helper.openOptionsPage()
|
||||||
|
const proxyList = await optionsPage.openProxyList()
|
||||||
|
const addProxyForm = await proxyList.openAddProxyForm()
|
||||||
|
const title = 'Valid SOCKS5 proxy'
|
||||||
|
await addProxyForm.addProxy({
|
||||||
|
title: title,
|
||||||
|
type: 'socks',
|
||||||
|
server: 'localhost',
|
||||||
|
port: 1080,
|
||||||
|
username: 'user',
|
||||||
|
password: 'password'
|
||||||
|
})
|
||||||
|
// TODO: Check if username and password are actually verified by "dante"
|
||||||
|
|
||||||
|
const assignProxy = await optionsPage.openAssignProxy()
|
||||||
|
await assignProxy.selectForDefaultContainer(title)
|
||||||
|
await helper.assertCanGetTheIpAddress()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail with incorrect SOCKS5 proxy settings', async () => {
|
||||||
|
const helper = new Helper(geckodriver)
|
||||||
|
|
||||||
|
const optionsPage = await helper.openOptionsPage()
|
||||||
|
const proxyList = await optionsPage.openProxyList()
|
||||||
|
const addProxyForm = await proxyList.openAddProxyForm()
|
||||||
|
const title = 'Incorrectly setup SOCKS5 proxy'
|
||||||
|
await addProxyForm.addProxy({
|
||||||
|
title: title,
|
||||||
|
type: 'socks',
|
||||||
|
server: 'localhost',
|
||||||
|
port: 999,
|
||||||
|
username: 'user',
|
||||||
|
password: 'password'
|
||||||
|
})
|
||||||
|
|
||||||
|
const assignProxy = await optionsPage.openAssignProxy()
|
||||||
|
await assignProxy.selectForDefaultContainer(title)
|
||||||
|
await helper.assertProxyFailure()
|
||||||
|
})
|
||||||
|
|
||||||
|
after(function () {
|
||||||
|
geckodriver.quit()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
class Helper extends PageObject {
|
||||||
|
toolbarButton = By.id('container-proxy_bekh-ivanov_me-browser-action')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Promise<OptionsPageObject>}
|
||||||
|
*/
|
||||||
|
async openOptionsPage() {
|
||||||
|
await this._driver.setContext(firefox.Context.CHROME)
|
||||||
|
await this.click(this.toolbarButton)
|
||||||
|
await this._driver.setContext(firefox.Context.CONTENT)
|
||||||
|
|
||||||
|
let windowHandle
|
||||||
|
await this._driver.wait(async () => {
|
||||||
|
const windowHandles = await this._driver.getAllWindowHandles()
|
||||||
|
windowHandle = windowHandles[windowHandles.length - 1]
|
||||||
|
try {
|
||||||
|
await this._driver.switchTo().window(windowHandle)
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const title = await this._driver.getTitle()
|
||||||
|
return title === 'Container Proxy extension settings'
|
||||||
|
}, 2000, 'Should have opened Container Proxy extension settings')
|
||||||
|
|
||||||
|
return this.createPageObject(OptionsPageObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
async assertCanGetTheIpAddress() {
|
||||||
|
await this._driver.setContext(firefox.Context.CONTENT)
|
||||||
|
await this._driver.get('https://duckduckgo.com/?q=ip&ia=answer&atb=v150-1')
|
||||||
|
const text = await this._driver.getPageSource()
|
||||||
|
|
||||||
|
expect(text).to.include('Your IP address is')
|
||||||
|
}
|
||||||
|
|
||||||
|
async assertProxyFailure() {
|
||||||
|
await this._driver.setContext(firefox.Context.CONTENT)
|
||||||
|
try {
|
||||||
|
await this._driver.get('https://api.duckduckgo.com/?q=ip&no_html=1&format=json&t=firefox-container-proxy-extension')
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
const text = await this._driver.getPageSource()
|
||||||
|
|
||||||
|
expect(text).to.include('Firefox is configured to use a proxy server that is refusing connections.')
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
socks:
|
||||||
|
build: ./socks
|
||||||
|
ports:
|
||||||
|
- "1080:1080"
|
||||||
|
http:
|
||||||
|
build: ./http
|
||||||
|
ports:
|
||||||
|
- "3128:3128"
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
FROM datadog/squid
|
||||||
|
|
||||||
|
RUN apt update && apt install -y apache2-utils
|
||||||
|
|
||||||
|
RUN htpasswd -b -c /etc/squid/passwords userhttp passwordhttp
|
||||||
|
|
||||||
|
ADD squid.conf /etc/squid/squid.conf
|
||||||
|
|
||||||
|
RUN chmod o+rw /var/run
|
||||||
|
|
||||||
|
USER proxy
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
auth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid/passwords
|
||||||
|
#auth_param basic children 5 startup = 5 idle = 1
|
||||||
|
auth_param basic realm proxy
|
||||||
|
auth_param basic credentialsttl 1 second
|
||||||
|
connect_timeout 1 second
|
||||||
|
acl authenticated proxy_auth REQUIRED
|
||||||
|
http_access allow authenticated
|
||||||
|
|
||||||
|
logformat splunk_recommended_squid %ts.%03tu logformat=splunk_recommended_squid duration=%tr src_ip=%>a src_port=%>p dest_ip=%<a dest_port=%<p user_ident="%[ui" user="%[un" local_time=[%tl] http_method=%rm request_method_from_client=%<rm request_method_to_server=%>rm url="%ru" http_referrer="%{Referer}>h" http_user_agent="%{User-Agent}>h" status=%>Hs vendor_action=%Ss dest_status=%Sh total_time_milliseconds=%<tt http_content_type="%mt" bytes=%st bytes_in=%>st bytes_out=%<st sni=ssl::>sni
|
||||||
|
|
||||||
|
logfile_rotate 0
|
||||||
|
cache_log stdio:/proc/self/fd/1 splunk_recommended_squid
|
||||||
|
access_log stdio:/proc/self/fd/1 splunk_recommended_squid
|
||||||
|
cache_store_log stdio:/proc/self/fd/1 splunk_recommended_squid
|
||||||
|
|
||||||
|
# Choose the port you want. Below we set it to default 3128.
|
||||||
|
http_port 3128
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
user:$apr1$L24dGKxY$xqj/npbeiBkMZEH7s9Iae/
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
FROM vimagick/dante
|
||||||
|
|
||||||
|
RUN useradd user && echo user:password | chpasswd
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
import BackgroundMain, { doNotProxy } from '../../src/background/BackgroundMain'
|
||||||
|
import { Store } from '../../src/store/Store'
|
||||||
|
import webExtensionsApiFake from 'webextensions-api-fake'
|
||||||
|
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { ProxySettings } from '../../src/domain/ProxySettings'
|
||||||
|
const tryFromDao = ProxySettings.tryFromDao
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||||
|
|
||||||
|
const store = new Store()
|
||||||
|
|
||||||
|
describe('BackgroundMain', function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
// @ts-expect-error
|
||||||
|
global.browser = webExtensionsApiFake()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// @ts-expect-error
|
||||||
|
delete global.browser
|
||||||
|
})
|
||||||
|
|
||||||
|
const backgroundMain = new BackgroundMain({ store: store })
|
||||||
|
|
||||||
|
// TODO: Add test for proxyDNS property
|
||||||
|
|
||||||
|
describe('onRequest', function () {
|
||||||
|
it('should return empty array if no proxy is set up', async () => {
|
||||||
|
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: 0 })
|
||||||
|
|
||||||
|
expect(result).to.be.deep.equal(doNotProxy)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return proxy if proxy is set up', async () => {
|
||||||
|
await givenSomeProxyIsSetUpForContainer({ containerId: 'firefox-default', host: undefined, doNotProxyLocal: undefined })
|
||||||
|
|
||||||
|
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: 0 })
|
||||||
|
|
||||||
|
expect(result).to.be.an('array')
|
||||||
|
expect(result).to.be.not.empty
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove doNotProxyLocal flag from proxy settings if proxy is set up', async () => {
|
||||||
|
await givenSomeProxyIsSetUpForContainer({ containerId: 'firefox-default', host: undefined, doNotProxyLocal: undefined })
|
||||||
|
|
||||||
|
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'https://google.com', tabId: 0 })
|
||||||
|
|
||||||
|
expect((result[0] as any).doNotProxyLocal).to.be.undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return proxy for the container if url is invalid', async () => {
|
||||||
|
// To be more on a safe side
|
||||||
|
await givenSomeProxyIsSetUpForContainer({ containerId: 'firefox-default', host: undefined, doNotProxyLocal: undefined })
|
||||||
|
|
||||||
|
const result = await backgroundMain.onRequest({ cookieStoreId: 'firefox-default', url: 'np-protocol-url.com', tabId: 0 })
|
||||||
|
|
||||||
|
expect(result).to.be.an('array')
|
||||||
|
expect(result).to.be.not.empty
|
||||||
|
})
|
||||||
|
|
||||||
|
// Connections to localhost, 127.0.0.1, and ::1 are never proxied. (From FF settings)
|
||||||
|
const localAddresses = [
|
||||||
|
'http://localhost/index.html',
|
||||||
|
'https://localhost/index.html',
|
||||||
|
'http://127.0.0.1/',
|
||||||
|
'https://127.0.0.1/',
|
||||||
|
'http://[::1]/test',
|
||||||
|
'https://[::1]/test',
|
||||||
|
'http://[0:0:0:0:0:0:0:1]/test',
|
||||||
|
'https://[0:0:0:0:0:0:0:1]/test',
|
||||||
|
'https://user:password@127.0.0.1:123/',
|
||||||
|
'http://[::1]:123/test'
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('proxying of local addresses is disabled', () => {
|
||||||
|
localAddresses.forEach(url => {
|
||||||
|
it(`should return empty array if the address is local: ${url}`, async () => {
|
||||||
|
await givenSomeProxyIsSetUpForContainer({ containerId: 'container1', host: undefined, doNotProxyLocal: true })
|
||||||
|
|
||||||
|
const result = await backgroundMain.onRequest({ cookieStoreId: 'container1', url, tabId: 0 })
|
||||||
|
|
||||||
|
expect(result).to.be.deep.equal(doNotProxy)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('proxying of local addresses is enabled', () => {
|
||||||
|
localAddresses.forEach(url => {
|
||||||
|
it(`should return array with proxy: ${url}`, async () => {
|
||||||
|
const host = 'proxyX.example.com'
|
||||||
|
await givenSomeProxyIsSetUpForContainer({ host, containerId: 'container1', doNotProxyLocal: false })
|
||||||
|
|
||||||
|
const result = await backgroundMain.onRequest({ cookieStoreId: 'container1', url, tabId: 0 })
|
||||||
|
|
||||||
|
expect(result[0].host).to.be.equal(host)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function givenSomeProxyIsSetUpForContainer({ host, containerId, doNotProxyLocal }: any): Promise<void> {
|
||||||
|
const proxyId = 'proxy1'
|
||||||
|
const proxy: any = {
|
||||||
|
id: proxyId,
|
||||||
|
type: 'socks',
|
||||||
|
host: (host as string) ?? 'example.com',
|
||||||
|
port: 1080
|
||||||
|
}
|
||||||
|
if (typeof doNotProxyLocal !== 'undefined') {
|
||||||
|
proxy.doNotProxyLocal = doNotProxyLocal
|
||||||
|
}
|
||||||
|
await store.putProxy(tryFromDao(proxy) as ProxySettings)
|
||||||
|
|
||||||
|
await store.setContainerProxyRelation(containerId, proxyId)
|
||||||
|
}
|
||||||
+167
@@ -0,0 +1,167 @@
|
|||||||
|
import { ProxyDao, Store } from '../../src/store/Store'
|
||||||
|
import webExtensionsApiFake from 'webextensions-api-fake'
|
||||||
|
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { ProxySettings } from '../../src/domain/ProxySettings'
|
||||||
|
const tryFromDao = ProxySettings.tryFromDao
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||||
|
|
||||||
|
describe('Store', () => {
|
||||||
|
const store = new Store()
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(global as any).browser = webExtensionsApiFake()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete (global as any).browser
|
||||||
|
})
|
||||||
|
|
||||||
|
function someProxyWith(id: string, props: Partial<ProxyDao> = {}): ProxySettings {
|
||||||
|
const dao = {
|
||||||
|
id: id,
|
||||||
|
title: 'some title',
|
||||||
|
type: 'socks',
|
||||||
|
host: 'localhost',
|
||||||
|
port: 1080,
|
||||||
|
username: 'user',
|
||||||
|
password: 'password',
|
||||||
|
proxyDNS: true,
|
||||||
|
failoverTimeout: 5,
|
||||||
|
doNotProxyLocal: true
|
||||||
|
}
|
||||||
|
return tryFromDao({ ...dao, ...props }) as ProxySettings
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('getAllProxies', function () {
|
||||||
|
it('should return empty array if nothing is saved', async () => {
|
||||||
|
const proxies = await store.getAllProxies()
|
||||||
|
|
||||||
|
expect(proxies).to.be.an('array')
|
||||||
|
expect(proxies).to.be.empty
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should put and get the proxy back', async () => {
|
||||||
|
const id = 'someId'
|
||||||
|
const proxy = someProxyWith(id)
|
||||||
|
|
||||||
|
await store.putProxy(proxy)
|
||||||
|
|
||||||
|
const gotProxy = await store.getProxyById(id)
|
||||||
|
|
||||||
|
expect(gotProxy).to.be.deep.equal(proxy)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('when get, returns doNotProxyLocal `true` if was not set when put', async () => {
|
||||||
|
const id = 'someId'
|
||||||
|
const proxy = someProxyWith(id)
|
||||||
|
delete (proxy as any).doNotProxyLocal
|
||||||
|
|
||||||
|
await store.putProxy(proxy)
|
||||||
|
|
||||||
|
const gotProxy = await store.getProxyById(id)
|
||||||
|
|
||||||
|
expect(gotProxy?.doNotProxyLocal).to.be.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stores doNotProxyLocal value', async () => {
|
||||||
|
const id = 'someId'
|
||||||
|
const proxy = someProxyWith(id, { doNotProxyLocal: false })
|
||||||
|
|
||||||
|
await store.putProxy(proxy)
|
||||||
|
|
||||||
|
const gotProxy = await store.getProxyById(id)
|
||||||
|
|
||||||
|
expect(gotProxy?.doNotProxyLocal).to.be.equal(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should be able to delete proxy', async () => {
|
||||||
|
const id = 'someId'
|
||||||
|
await store.putProxy(someProxyWith(id))
|
||||||
|
await store.deleteProxyById(id)
|
||||||
|
|
||||||
|
const result = await store.getProxyById(id)
|
||||||
|
|
||||||
|
expect(result).to.be.null
|
||||||
|
})
|
||||||
|
|
||||||
|
// describe('getProxiesForContainer', () => {
|
||||||
|
// it('should find proxy by container id if one present', async () => {
|
||||||
|
// const cookieStoreId = 'container-1'
|
||||||
|
// const proxyId = 'proxy-1'
|
||||||
|
// // TODO Store should probably take care of this
|
||||||
|
// const relations = {
|
||||||
|
// [cookieStoreId]: [proxyId]
|
||||||
|
// }
|
||||||
|
// const givenProxy = someProxyWith(proxyId)
|
||||||
|
// await store.putProxy(givenProxy)
|
||||||
|
|
||||||
|
// await browser.storage.local.set({ relations: relations })
|
||||||
|
|
||||||
|
// const [gotProxy] = await store.getProxiesForContainer(cookieStoreId)
|
||||||
|
|
||||||
|
// expect(gotProxy).to.be.deep.equal(givenProxy)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// it('doNotProxyLocal should be `true` if not set', async () => {
|
||||||
|
// const cookieStoreId = 'container-1'
|
||||||
|
// const proxyId = 'proxy-1'
|
||||||
|
// // TODO Store should probably take care of this
|
||||||
|
// const relations = {
|
||||||
|
// [cookieStoreId]: [proxyId]
|
||||||
|
// }
|
||||||
|
// const givenProxy = someProxyWith(proxyId)
|
||||||
|
// delete (givenProxy as any).doNotProxyLocal
|
||||||
|
// await store.putProxy(givenProxy)
|
||||||
|
|
||||||
|
// await browser.storage.local.set({ relations: relations })
|
||||||
|
|
||||||
|
// const [gotProxy] = await store.getProxiesForContainer(cookieStoreId)
|
||||||
|
|
||||||
|
// expect(gotProxy.doNotProxyLocal).to.be.equal(true)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// it('should return empty array if proxy not set for the container', async () => {
|
||||||
|
// const cookieStoreId = 'container-1'
|
||||||
|
|
||||||
|
// const result = await store.getProxiesForContainer(cookieStoreId)
|
||||||
|
|
||||||
|
// expect(result).to.be.empty
|
||||||
|
// })
|
||||||
|
|
||||||
|
// it('should return empty array if proxy is set for the container but does not exist', async () => {
|
||||||
|
// const cookieStoreId = 'container-1'
|
||||||
|
// const proxyId = 'something-absent'
|
||||||
|
// const relations = {
|
||||||
|
// [cookieStoreId]: [proxyId]
|
||||||
|
// }
|
||||||
|
// await browser.storage.local.set({ relations: relations })
|
||||||
|
|
||||||
|
// const result = await store.getProxiesForContainer(cookieStoreId)
|
||||||
|
|
||||||
|
// expect(result).to.be.empty
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
|
||||||
|
describe('setContainerProxyRelation', function () {
|
||||||
|
it('should set the relation', async () => {
|
||||||
|
await store.setContainerProxyRelation('container1', 'proxy1')
|
||||||
|
|
||||||
|
const relations = await store.getRelations()
|
||||||
|
|
||||||
|
expect(relations.container1).to.be.deep.equal(['proxy1'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not remove existing relations', async () => {
|
||||||
|
await store.setContainerProxyRelation('container1', 'proxy1')
|
||||||
|
await store.setContainerProxyRelation('container2', 'proxy2')
|
||||||
|
|
||||||
|
const relations = await store.getRelations()
|
||||||
|
|
||||||
|
expect(relations.container1).to.be.deep.equal(['proxy1'])
|
||||||
|
expect(relations.container2).to.be.deep.equal(['proxy2'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"module": "ES2020",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"target": "es2019",
|
||||||
|
"sourceMap": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "preact",
|
||||||
|
"plugins": [{ "name": "typescript-plugin-css-modules" }]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
"test/unit/**/*",
|
||||||
|
"declarations.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
const path = require('path')
|
||||||
|
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
|
||||||
|
const CopyPlugin = require('copy-webpack-plugin')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
background: './src/background/index.ts',
|
||||||
|
},
|
||||||
|
devtool: 'source-map',
|
||||||
|
mode: 'development',
|
||||||
|
performance: {
|
||||||
|
hints: false,
|
||||||
|
},
|
||||||
|
node: false,
|
||||||
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
|
moduleIds: 'named',
|
||||||
|
chunkIds: 'named',
|
||||||
|
concatenateModules: false,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.tsx', '.js', '.module.scss'],
|
||||||
|
plugins: [new TsconfigPathsPlugin()],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name]/index.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
loader: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.module\.scss$/i,
|
||||||
|
use: ['style-loader', 'css-modules-typescript-loader', {
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: { modules: { localIdentName: '[name]__[local]--[hash:base64:5]' } }
|
||||||
|
}, 'sass-loader'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new CopyPlugin({
|
||||||
|
patterns: [
|
||||||
|
'README.md',
|
||||||
|
'LICENSE',
|
||||||
|
'src/manifest.json',
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
devServer: {
|
||||||
|
hot: false,
|
||||||
|
inline: false,
|
||||||
|
writeToDisk: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ export 'src/data/models/load_url_flags.dart';
|
|||||||
export 'src/data/models/source.dart';
|
export 'src/data/models/source.dart';
|
||||||
export 'src/domain/entities/default_selection_actions.dart';
|
export 'src/domain/entities/default_selection_actions.dart';
|
||||||
export 'src/domain/services/gecko_addon.dart';
|
export 'src/domain/services/gecko_addon.dart';
|
||||||
|
export 'src/domain/services/gecko_container_proxy.dart';
|
||||||
export 'src/domain/services/gecko_cookie.dart';
|
export 'src/domain/services/gecko_cookie.dart';
|
||||||
export 'src/domain/services/gecko_delete_browser_data.dart';
|
export 'src/domain/services/gecko_delete_browser_data.dart';
|
||||||
export 'src/domain/services/gecko_engine_settings.dart';
|
export 'src/domain/services/gecko_engine_settings.dart';
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
|
||||||
|
|
||||||
|
final _apiInstance = GeckoContainerProxyApi();
|
||||||
|
|
||||||
|
class GeckoContainerProxyService {
|
||||||
|
Future<void> setProxyPort(int port) {
|
||||||
|
return _apiInstance.setProxyPort(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addContainerProxy(String contextId) {
|
||||||
|
return _apiInstance.addContainerProxy(contextId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeContainerProxy(String contextId) {
|
||||||
|
return _apiInstance.removeContainerProxy(contextId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Autogenerated from Pigeon (v22.7.4), do not edit directly.
|
// Autogenerated from Pigeon (v24.1.1), do not edit directly.
|
||||||
// See also: https://pub.dev/packages/pigeon
|
// See also: https://pub.dev/packages/pigeon
|
||||||
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
|
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
|
||||||
|
|
||||||
@@ -2668,6 +2668,86 @@ class GeckoPrefApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GeckoContainerProxyApi {
|
||||||
|
/// Constructor for [GeckoContainerProxyApi]. The [binaryMessenger] named argument is
|
||||||
|
/// available for dependency injection. If it is left null, the default
|
||||||
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
|
GeckoContainerProxyApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
|
|
||||||
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
|
Future<void> setProxyPort(int port) async {
|
||||||
|
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setProxyPort$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final List<Object?>? pigeonVar_replyList =
|
||||||
|
await pigeonVar_channel.send(<Object?>[port]) as List<Object?>?;
|
||||||
|
if (pigeonVar_replyList == null) {
|
||||||
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
} else if (pigeonVar_replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: pigeonVar_replyList[0]! as String,
|
||||||
|
message: pigeonVar_replyList[1] as String?,
|
||||||
|
details: pigeonVar_replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addContainerProxy(String contextId) async {
|
||||||
|
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.addContainerProxy$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final List<Object?>? pigeonVar_replyList =
|
||||||
|
await pigeonVar_channel.send(<Object?>[contextId]) as List<Object?>?;
|
||||||
|
if (pigeonVar_replyList == null) {
|
||||||
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
} else if (pigeonVar_replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: pigeonVar_replyList[0]! as String,
|
||||||
|
message: pigeonVar_replyList[1] as String?,
|
||||||
|
details: pigeonVar_replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeContainerProxy(String contextId) async {
|
||||||
|
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.removeContainerProxy$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final List<Object?>? pigeonVar_replyList =
|
||||||
|
await pigeonVar_channel.send(<Object?>[contextId]) as List<Object?>?;
|
||||||
|
if (pigeonVar_replyList == null) {
|
||||||
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
} else if (pigeonVar_replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: pigeonVar_replyList[0]! as String,
|
||||||
|
message: pigeonVar_replyList[1] as String?,
|
||||||
|
details: pigeonVar_replyList[2],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class GeckoCookieApi {
|
class GeckoCookieApi {
|
||||||
/// Constructor for [GeckoCookieApi]. The [binaryMessenger] named argument is
|
/// Constructor for [GeckoCookieApi]. The [binaryMessenger] named argument is
|
||||||
/// available for dependency injection. If it is left null, the default
|
/// available for dependency injection. If it is left null, the default
|
||||||
|
|||||||
@@ -889,6 +889,13 @@ abstract class GeckoPrefApi {
|
|||||||
void resetPrefs(List<String>? preferenceNames);
|
void resetPrefs(List<String>? preferenceNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostApi()
|
||||||
|
abstract class GeckoContainerProxyApi {
|
||||||
|
void setProxyPort(int port);
|
||||||
|
void addContainerProxy(String contextId);
|
||||||
|
void removeContainerProxy(String contextId);
|
||||||
|
}
|
||||||
|
|
||||||
@HostApi()
|
@HostApi()
|
||||||
abstract class GeckoCookieApi {
|
abstract class GeckoCookieApi {
|
||||||
@async
|
@async
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
lint: ^2.3.0
|
lint: ^2.4.0
|
||||||
pigeon: ^24.1.0
|
pigeon: ^24.1.1
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ group = "me.movenext.sqlite3_vec"
|
|||||||
version = "1.0-SNAPSHOT"
|
version = "1.0-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "1.8.22"
|
ext.kotlin_version = "1.9.22"
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.1.0")
|
classpath("com.android.tools.build:gradle:8.7.0")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,12 +30,12 @@ android {
|
|||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_11
|
jvmTarget = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ android {
|
|||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8
|
jvmTarget = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
|||||||
+1
-1
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.1.0" apply false
|
id "com.android.application" version "8.7.0" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
id "org.jetbrains.kotlin.android" version "1.9.22" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ dependencies:
|
|||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
sqlite3: ^2.7.2
|
sqlite3: ^2.7.3
|
||||||
sqlite3_flutter_libs: ^0.5.29+1
|
sqlite3_flutter_libs: ^0.5.30
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
integration_test:
|
integration_test:
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
sqlite3: ^2.7.2
|
sqlite3: ^2.7.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
lint: ^2.3.0
|
lint: ^2.4.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
Reference in New Issue
Block a user