finished implementation

This commit is contained in:
Fabian Freund
2025-09-14 11:41:01 +02:00
parent ac0f3b5dcc
commit a8334ae684
30 changed files with 848 additions and 83 deletions
@@ -0,0 +1 @@
{"meek":["meek_lite 192.0.2.20:80 url=https://1603026938.rsc.cdn77.org front=www.phpmyadmin.net utls=HelloRandomizedALPN"],"meek-azure":["meek_lite 192.0.2.20:80 url=https://1603026938.rsc.cdn77.org front=www.phpmyadmin.net utls=HelloRandomizedALPN"],"obfs4":["obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0","obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0","obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0","obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1","obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0","obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0","obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0","obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0","obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0","obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0","obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0"],"snowflake":["snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.antisip.com:3478,stun:stun.epygi.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.mixvoip.com:3478,stun:stun.nextcloud.com:3478,stun:stun.bethesda.net:3478,stun:stun.nextcloud.com:443 utls-imitate=hellorandomizedalpn","snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.antisip.com:3478,stun:stun.epygi.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.mixvoip.com:3478,stun:stun.nextcloud.com:3478,stun:stun.bethesda.net:3478,stun:stun.nextcloud.com:443 utls-imitate=hellorandomizedalpn"]}
@@ -17,7 +17,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:developer';
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -163,6 +166,38 @@ class DeveloperSettingsScreen extends HookConsumerWidget {
label: const Text('Copy'),
),
),
if (kDebugMode)
CustomListTile(
title: 'Dart VM',
subtitle: 'Copy Dart VM service URL',
prefix: Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(
Icons.bug_report,
size: 24,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
suffix: FilledButton.icon(
onPressed: () async {
final serviceProtocolInfo = await Service.getInfo();
await Clipboard.setData(
ClipboardData(
text:
serviceProtocolInfo.serverUri?.toString() ??
'Error',
),
);
if (context.mounted) {
showInfoMessage(context, 'Service URL copied');
}
},
icon: const Icon(Icons.copy),
label: const Text('Copy'),
),
),
],
);
},
@@ -0,0 +1,53 @@
import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:pluggable_transports_proxy/pluggable_transports_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:universal_io/io.dart';
part 'builtin_bridges.g.dart';
@Riverpod(keepAlive: true)
class BuiltinBridgesService extends _$BuiltinBridgesService {
late final _bridgeFileFuture = path_provider
.getApplicationSupportDirectory()
.then((dir) => File(p.join(dir.path, 'builtin-bridges.json')));
Future<DateTime?> lastUpdate() async {
final bridgeFile = await _bridgeFileFuture;
if (!await bridgeFile.exists()) {
return null;
}
return bridgeFile.lastModified();
}
Future<void> updateStoredBuiltinBridges(BuiltInBridges bridges) async {
final bridgeFile = await _bridgeFileFuture;
await bridgeFile.writeAsString(jsonEncode(bridges.toJson()), flush: true);
}
Future<BuiltInBridges?> getStoredBuiltinBridges() async {
final bridgeFile = await _bridgeFileFuture;
if (!await bridgeFile.exists()) {
return null;
}
final content = await bridgeFile.readAsString();
return BuiltInBridges.fromJson(jsonDecode(content) as Map<String, dynamic>);
}
Future<BuiltInBridges> getBundledBuiltinBridges() async {
final content = await rootBundle.loadString(
'assets/preferences/builtin-bridges.json',
);
return BuiltInBridges.fromJson(jsonDecode(content) as Map<String, dynamic>);
}
@override
void build() {
return;
}
}
@@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'builtin_bridges.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$builtinBridgesServiceHash() =>
r'beacb3c9d8c5a7179c7c9cad8024d128b09241f5';
/// See also [BuiltinBridgesService].
@ProviderFor(BuiltinBridgesService)
final builtinBridgesServiceProvider =
NotifierProvider<BuiltinBridgesService, void>.internal(
BuiltinBridgesService.new,
name: r'builtinBridgesServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$builtinBridgesServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$BuiltinBridgesService = 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
@@ -0,0 +1,49 @@
import 'package:pluggable_transports_proxy/pluggable_transports_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/tor/data/services/builtin_bridges.dart';
part 'builtin_bridges.g.dart';
@Riverpod()
class BuiltinBridgesRepository extends _$BuiltinBridgesRepository {
Future<void> updateIfNecessary() async {
final lastUpdate = await ref
.read(builtinBridgesServiceProvider.notifier)
.lastUpdate();
if (lastUpdate == null ||
DateTime.now().difference(lastUpdate) > const Duration(days: 2)) {
BuiltInBridges? remoteBridges;
try {
remoteBridges = await service.getBuiltinBridges();
} catch (e, s) {
logger.e('Failed fetching builtin bridges', error: e, stackTrace: s);
}
if (remoteBridges != null) {
await ref
.read(builtinBridgesServiceProvider.notifier)
.updateStoredBuiltinBridges(remoteBridges);
}
}
}
Future<BuiltInBridges> getBridges({bool tryUpdate = true}) async {
if (tryUpdate) {
await updateIfNecessary();
}
final storedBridges = await ref
.read(builtinBridgesServiceProvider.notifier)
.getStoredBuiltinBridges();
return storedBridges ??
await ref
.read(builtinBridgesServiceProvider.notifier)
.getBundledBuiltinBridges();
}
@override
void build(MoatService service) {}
}
@@ -0,0 +1,166 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'builtin_bridges.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$builtinBridgesRepositoryHash() =>
r'05fc40a7742fc266aeb76b4e3f1d9bdfdb8d448f';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$BuiltinBridgesRepository
extends BuildlessAutoDisposeNotifier<void> {
late final MoatService service;
void build(MoatService service);
}
/// See also [BuiltinBridgesRepository].
@ProviderFor(BuiltinBridgesRepository)
const builtinBridgesRepositoryProvider = BuiltinBridgesRepositoryFamily();
/// See also [BuiltinBridgesRepository].
class BuiltinBridgesRepositoryFamily extends Family<void> {
/// See also [BuiltinBridgesRepository].
const BuiltinBridgesRepositoryFamily();
/// See also [BuiltinBridgesRepository].
BuiltinBridgesRepositoryProvider call(MoatService service) {
return BuiltinBridgesRepositoryProvider(service);
}
@override
BuiltinBridgesRepositoryProvider getProviderOverride(
covariant BuiltinBridgesRepositoryProvider provider,
) {
return call(provider.service);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'builtinBridgesRepositoryProvider';
}
/// See also [BuiltinBridgesRepository].
class BuiltinBridgesRepositoryProvider
extends AutoDisposeNotifierProviderImpl<BuiltinBridgesRepository, void> {
/// See also [BuiltinBridgesRepository].
BuiltinBridgesRepositoryProvider(MoatService service)
: this._internal(
() => BuiltinBridgesRepository()..service = service,
from: builtinBridgesRepositoryProvider,
name: r'builtinBridgesRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$builtinBridgesRepositoryHash,
dependencies: BuiltinBridgesRepositoryFamily._dependencies,
allTransitiveDependencies:
BuiltinBridgesRepositoryFamily._allTransitiveDependencies,
service: service,
);
BuiltinBridgesRepositoryProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.service,
}) : super.internal();
final MoatService service;
@override
void runNotifierBuild(covariant BuiltinBridgesRepository notifier) {
return notifier.build(service);
}
@override
Override overrideWith(BuiltinBridgesRepository Function() create) {
return ProviderOverride(
origin: this,
override: BuiltinBridgesRepositoryProvider._internal(
() => create()..service = service,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
service: service,
),
);
}
@override
AutoDisposeNotifierProviderElement<BuiltinBridgesRepository, void>
createElement() {
return _BuiltinBridgesRepositoryProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is BuiltinBridgesRepositoryProvider &&
other.service == service;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, service.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin BuiltinBridgesRepositoryRef on AutoDisposeNotifierProviderRef<void> {
/// The parameter `service` of this provider.
MoatService get service;
}
class _BuiltinBridgesRepositoryProviderElement
extends AutoDisposeNotifierProviderElement<BuiltinBridgesRepository, void>
with BuiltinBridgesRepositoryRef {
_BuiltinBridgesRepositoryProviderElement(super.provider);
@override
MoatService get service =>
(origin as BuiltinBridgesRepositoryProvider).service;
}
// 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
@@ -20,13 +20,17 @@
import 'dart:async';
import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:nullability/nullability.dart';
import 'package:pluggable_transports_proxy/pluggable_transports_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/providers/lifecycle.dart';
import 'package:weblibre/features/tor/domain/repositories/builtin_bridges.dart';
import 'package:weblibre/features/tor/utils/tor_entrypoint.dart';
import 'package:weblibre/features/user/data/models/tor_settings.dart';
import 'package:weblibre/features/user/domain/repositories/tor_settings.dart';
part 'tor_proxy.g.dart';
@@ -38,9 +42,19 @@ class _TorService {
ValueStream<int?> get portStream => _portSubject.stream;
Future<void> start({ProxyType? proxyType, String? bridgeLines}) async {
Future<void> startOrReconfigure({
ProxyType? proxyType,
String? bridgeLines,
}) async {
await service.startService();
service.invoke('start', {
service.invoke('startOrReconfigure', {
if (proxyType != null) 'proxyType': proxyType.name,
if (bridgeLines != null) 'bridgeLines': bridgeLines,
});
}
Future<void> reconfigure({ProxyType? proxyType, String? bridgeLines}) async {
service.invoke('reconfigure', {
if (proxyType != null) 'proxyType': proxyType.name,
if (bridgeLines != null) 'bridgeLines': bridgeLines,
});
@@ -101,7 +115,7 @@ class TorProxyService extends _$TorProxyService {
});
}
Future<void> connect({bool forceReconnect = false}) async {
Future<void> startOrReconfigure({bool forceReconnect = false}) async {
final currentPort = await requestSync();
if (currentPort == null || forceReconnect) {
@@ -111,14 +125,97 @@ class TorProxyService extends _$TorProxyService {
.read(torSettingsRepositoryProvider.notifier)
.fetchSettings();
if (torSettings.autoConfig) {
final moat = MoatService();
final config = await moat.autoConf();
Setting? setting;
if (torSettings.config == TorConnectionConfig.auto) {
List<Setting>? config;
todo
final moat = MoatService();
try {
await moat.initialize();
config = await moat.autoConf(
cannotConnectWithoutPt: torSettings.requireBridge,
);
if (config == null && torSettings.requireBridge) {
config = MoatService.convertBuiltinToSettings(
await ref
.read(builtinBridgesRepositoryProvider(moat).notifier)
.getBridges(),
);
}
} catch (e, s) {
logger.e('Failed auto configure bridges', error: e, stackTrace: s);
} finally {
await moat.dispose();
}
setting = config.mapNotNull(
(config) =>
config.firstWhereOrNull(
(setting) => setting.bridge.type == TransportType.obfs4,
) ??
config.firstWhereOrNull(
(setting) => setting.bridge.type == TransportType.snowflake,
),
);
} else if (torSettings.config != TorConnectionConfig.direct) {
List<Setting>? config;
final moat = MoatService();
try {
await moat.initialize();
if (torSettings.fetchRemoteBridges) {
config = await moat.getDefaultBridges();
}
if (config == null && torSettings.requireBridge) {
config = MoatService.convertBuiltinToSettings(
await ref
.read(builtinBridgesRepositoryProvider(moat).notifier)
.getBridges(tryUpdate: torSettings.fetchRemoteBridges),
);
}
setting = config.mapNotNull(
(config) => config.firstWhereOrNull(
(setting) =>
setting.bridge.type ==
switch (torSettings.config) {
TorConnectionConfig.auto => throw UnimplementedError(),
TorConnectionConfig.direct => throw UnimplementedError(),
TorConnectionConfig.obfs4 => TransportType.obfs4,
TorConnectionConfig.snowflake => TransportType.snowflake,
},
),
);
} catch (e, s) {
logger.e('Failed auto configure bridges', error: e, stackTrace: s);
} finally {
await moat.dispose();
}
}
if (setting != null) {
switch (setting.bridge.type) {
case TransportType.obfs4:
await _tor.startOrReconfigure(
proxyType: ProxyType.obfs4,
bridgeLines: setting.bridge.bridges?.join('\n'),
);
case TransportType.snowflake:
await _tor.startOrReconfigure(
proxyType: ProxyType.snowflake,
bridgeLines: setting.bridge.bridges?.join('\n'),
);
case TransportType.meek:
case TransportType.meekAzure:
case TransportType.webtunnel:
throw UnimplementedError();
}
} else {
await _tor.startOrReconfigure();
}
await _tor.start();
_enableHeartbeatTimer();
}
}
@@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tor_proxy.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$torProxyServiceHash() => r'82b4964ddf14d02893ebbea3b25d91216359ba10';
/// 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
@@ -47,7 +47,7 @@ class StartProxyController extends _$StartProxyController {
if (result == true) {
final connection = ref
.read(torProxyServiceProvider.notifier)
.connect();
.startOrReconfigure();
ref
.read(overlayControllerProvider.notifier)
@@ -59,6 +59,9 @@ class StartProxyController extends _$StartProxyController {
await connection;
}
}
} else {
//Reconfigure
await ref.read(torProxyServiceProvider.notifier).startOrReconfigure();
}
}
@@ -7,7 +7,7 @@ part of 'start_tor_proxy.dart';
// **************************************************************************
String _$startProxyControllerHash() =>
r'3e8b409f069f32b601b1c60acbe0a72694fd1cc2';
r'8840d5ffe9346c3f8b0aa0b3e034605b606c5e34';
/// See also [StartProxyController].
@ProviderFor(StartProxyController)
@@ -39,6 +39,14 @@ class TorProxyScreen extends HookConsumerWidget {
await ref.read(torProxyServiceProvider.notifier).requestSync();
});
ref.listen(torSettingsRepositoryProvider, (previous, next) async {
final torService = ref.read(torProxyServiceProvider.notifier);
if (await torService.requestSync() != null) {
await torService.startOrReconfigure();
}
});
return Scaffold(
appBar: AppBar(title: const Text('Tor™ Proxy')),
body: SafeArea(
@@ -46,9 +54,8 @@ class TorProxyScreen extends HookConsumerWidget {
color: const Color(0xFF7D4698),
child: Column(
children: [
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
Expanded(
child: ListView(
children: [
const Icon(TorIcons.onionAlt, size: 84),
const SizedBox(height: 16),
@@ -92,7 +99,7 @@ class TorProxyScreen extends HookConsumerWidget {
if (value) {
await ref
.read(torProxyServiceProvider.notifier)
.connect();
.startOrReconfigure();
} else {
await ref
.read(torProxyServiceProvider.notifier)
@@ -137,7 +144,7 @@ class TorProxyScreen extends HookConsumerWidget {
subtitle: const Text(
'When enabled, all Private Tabs will be tunneled through Tor',
),
secondary: const Icon(MdiIcons.arrowDecision),
secondary: const Icon(MdiIcons.incognito),
onChanged: (value) async {
await ref
.read(saveTorSettingsControllerProvider.notifier)
@@ -179,27 +186,173 @@ class TorProxyScreen extends HookConsumerWidget {
}
return null; // Use the default color.
}),
value: torSettings.autoConfig,
title: const Text('Configure Transport Automatically'),
value: torSettings.config == TorConnectionConfig.auto,
title: const Text('Auto Configure Transport'),
subtitle: const Text(
'From some locations, it is necessary to use a pluggable transport to connect to the Tor network that is otherwise blocked. Turn off automatic configuration to manually configure how Tor connects.',
'From some locations, it is necessary to use a pluggable transport to connect to Tor',
),
secondary: const Icon(MdiIcons.arrowDecision),
onChanged: (value) async {
await ref
.read(saveTorSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.autoConfig(value),
);
},
secondary: const Icon(MdiIcons.arrowDecisionAuto),
onChanged: torProxyPort.isLoading
? null
: (value) async {
await ref
.read(
saveTorSettingsControllerProvider
.notifier,
)
.save(
(currentSettings) =>
currentSettings.copyWith.config(
value
? TorConnectionConfig.auto
: TorConnectionConfig.direct,
),
);
},
),
),
if (torSettings.config == TorConnectionConfig.auto) ...[
SwitchListTile.adaptive(
value: torSettings.requireBridge,
contentPadding: const EdgeInsets.only(
left: 56,
right: 24,
),
onChanged: torProxyPort.isLoading
? null
: (value) async {
await ref
.read(
saveTorSettingsControllerProvider
.notifier,
)
.save(
(currentSettings) => currentSettings
.copyWith
.requireBridge(value),
);
},
title: const Text(
"I'm sure I cannot connect without a bridge",
),
),
] else ...[
RadioGroup(
groupValue: torSettings.config,
onChanged: (value) async {
if (value != null) {
await ref
.read(
saveTorSettingsControllerProvider.notifier,
)
.save(
(currentSettings) =>
currentSettings.copyWith.config(value),
);
}
},
child: RadioListTile.adaptive(
value: TorConnectionConfig.direct,
enabled: !torProxyPort.isLoading,
contentPadding: const EdgeInsets.only(
left: 56,
right: 24,
),
title: Text('Direct Connection'),
subtitle: Text(
'The best way to connect to Tor if Tor is not blocked',
),
),
),
RadioGroup(
groupValue: torSettings.config,
onChanged: (value) async {
if (value != null) {
await ref
.read(
saveTorSettingsControllerProvider.notifier,
)
.save(
(currentSettings) =>
currentSettings.copyWith.config(value),
);
}
},
child: RadioListTile.adaptive(
value: TorConnectionConfig.obfs4,
enabled: !torProxyPort.isLoading,
contentPadding: const EdgeInsets.only(
left: 56,
right: 24,
),
title: Text('obfs4'),
subtitle: Text(
'Suitable for light censorship and high bandwidth needs',
),
),
),
RadioGroup(
groupValue: torSettings.config,
onChanged: (value) async {
if (value != null) {
await ref
.read(
saveTorSettingsControllerProvider.notifier,
)
.save(
(currentSettings) =>
currentSettings.copyWith.config(value),
);
}
},
child: RadioListTile.adaptive(
value: TorConnectionConfig.snowflake,
enabled: !torProxyPort.isLoading,
contentPadding: const EdgeInsets.only(
left: 56,
right: 24,
),
title: Text('Snowflake'),
subtitle: Text('Suitable for heavy censorship'),
),
),
CheckboxListTile.adaptive(
value: torSettings.fetchRemoteBridges,
controlAffinity: ListTileControlAffinity.leading,
enabled:
torSettings.config != TorConnectionConfig.direct,
contentPadding: const EdgeInsets.only(
left: 56,
right: 24,
),
onChanged: torProxyPort.isLoading
? null
: (value) async {
if (value != null) {
await ref
.read(
saveTorSettingsControllerProvider
.notifier,
)
.save(
(currentSettings) => currentSettings
.copyWith
.fetchRemoteBridges(value),
);
}
},
title: const Text(
"Fetch fresh Bridges before connecting",
),
),
],
],
),
),
Flexible(
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (torProxyPort.isLoading)
const Column(
+55 -7
View File
@@ -35,16 +35,19 @@ Future<void> onStart(ServiceInstance service) async {
service.invoke('portUpdate', {'port': port});
});
Future<void> stopProxies() async {
for (final proxyType in startedProxyType) {
await IPtProxyController().stop(proxyType);
}
startedProxyType.clear();
}
Future<void> stopService() async {
timeout?.cancel();
timeout = null;
Tor.instance.stop();
for (final proxyType in startedProxyType) {
await IPtProxyController().stop(proxyType);
}
startedProxyType.clear();
await stopProxies();
service.invoke('portUpdate', {'port': -1});
@@ -84,12 +87,57 @@ Future<void> onStart(ServiceInstance service) async {
}
}
service.on("start").listen((event) async {
Future<void> reconfigureService(
ProxyType? proxyType,
String? bridgeLines,
) async {
if (Tor.instance.hasClient) {
await stopProxies();
if (proxyType != null) {
final port = await IPtProxyController().start(proxyType, "");
startedProxyType.add(proxyType);
switch (proxyType) {
case ProxyType.obfs4:
await Tor.instance.reconfigure(
obfs4Port: port,
bridgeLines: bridgeLines,
);
case ProxyType.meekLite:
throw UnimplementedError();
case ProxyType.webtunnel:
throw UnimplementedError();
case ProxyType.snowflake:
await Tor.instance.reconfigure(
snowflakePort: port,
bridgeLines: bridgeLines,
);
}
} else {
await Tor.instance.reconfigure();
}
}
}
service.on("startOrReconfigure").listen((event) async {
final proxyType = ProxyType.values.firstWhereOrNull(
(x) => x.name == event?['proxyType'],
);
await startService(proxyType, event?['bridgeLines'] as String?);
await reconfigureService(proxyType, event?['bridgeLines'] as String?);
if (!Tor.instance.hasClient) {
await startService(proxyType, event?['bridgeLines'] as String?);
}
});
service.on("reconfigure").listen((event) async {
final proxyType = ProxyType.values.firstWhereOrNull(
(x) => x.name == event?['proxyType'],
);
await reconfigureService(proxyType, event?['bridgeLines'] as String?);
});
service.on("heartbeat").listen((event) {
@@ -42,7 +42,12 @@ class SettingDao extends DatabaseAccessor<UserDatabase> with _$SettingDaoMixin {
partitionKey: Value(partitionKey),
value: Value(driftvalue),
),
onConflict: DoUpdate((old) => SettingCompanion(value: Value(driftvalue))),
onConflict: DoUpdate(
(old) => SettingCompanion(
partitionKey: Value(partitionKey),
value: Value(driftvalue),
),
),
);
}
@@ -266,7 +266,8 @@ Map<String, dynamic> _$GeneralSettingsToJson(
'showExtensionShortcut': instance.showExtensionShortcut,
'enableLocalAiFeatures': instance.enableLocalAiFeatures,
'defaultCreateTabType': _$TabTypeEnumMap[instance.defaultCreateTabType]!,
'defaultIntentTabType': _$TabTypeEnumMap[instance.defaultIntentTabType]!,
'tabIntentOpenSetting':
_$TabIntentOpenSettingEnumMap[instance.tabIntentOpenSetting]!,
'autoHideTabBar': instance.autoHideTabBar,
'tabBarSwipeAction': _$TabBarSwipeActionEnumMap[instance.tabBarSwipeAction]!,
};
@@ -4,17 +4,32 @@ import 'package:json_annotation/json_annotation.dart';
part 'tor_settings.g.dart';
enum TorConnectionConfig { auto, direct, obfs4, snowflake }
@CopyWith()
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
class TorSettings with FastEquatable {
final bool proxyPrivateTabsTor;
final bool autoConfig;
final TorConnectionConfig config;
final bool requireBridge;
final bool fetchRemoteBridges;
TorSettings({required this.proxyPrivateTabsTor, required this.autoConfig});
TorSettings({
required this.proxyPrivateTabsTor,
required this.config,
required this.requireBridge,
required this.fetchRemoteBridges,
});
TorSettings.withDefaults({bool? proxyPrivateTabsTor, bool? autoConfig})
: proxyPrivateTabsTor = proxyPrivateTabsTor ?? false,
autoConfig = autoConfig ?? true;
TorSettings.withDefaults({
bool? proxyPrivateTabsTor,
TorConnectionConfig? config,
bool? requireBridge,
bool? fetchRemoteBridges,
}) : proxyPrivateTabsTor = proxyPrivateTabsTor ?? false,
config = config ?? TorConnectionConfig.auto,
requireBridge = requireBridge ?? false,
fetchRemoteBridges = fetchRemoteBridges ?? true;
factory TorSettings.fromJson(Map<String, dynamic> json) =>
_$TorSettingsFromJson(json);
@@ -22,5 +37,10 @@ class TorSettings with FastEquatable {
Map<String, dynamic> toJson() => _$TorSettingsToJson(this);
@override
List<Object?> get hashParameters => [proxyPrivateTabsTor, autoConfig];
List<Object?> get hashParameters => [
proxyPrivateTabsTor,
config,
requireBridge,
fetchRemoteBridges,
];
}
@@ -9,7 +9,11 @@ part of 'tor_settings.dart';
abstract class _$TorSettingsCWProxy {
TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor);
TorSettings autoConfig(bool autoConfig);
TorSettings config(TorConnectionConfig config);
TorSettings requireBridge(bool requireBridge);
TorSettings fetchRemoteBridges(bool fetchRemoteBridges);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TorSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
@@ -17,7 +21,12 @@ abstract class _$TorSettingsCWProxy {
/// ```dart
/// TorSettings(...).copyWith(id: 12, name: "My name")
/// ````
TorSettings call({bool proxyPrivateTabsTor, bool autoConfig});
TorSettings call({
bool proxyPrivateTabsTor,
TorConnectionConfig config,
bool requireBridge,
bool fetchRemoteBridges,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfTorSettings.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfTorSettings.copyWith.fieldName(...)`
@@ -31,7 +40,15 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
this(proxyPrivateTabsTor: proxyPrivateTabsTor);
@override
TorSettings autoConfig(bool autoConfig) => this(autoConfig: autoConfig);
TorSettings config(TorConnectionConfig config) => this(config: config);
@override
TorSettings requireBridge(bool requireBridge) =>
this(requireBridge: requireBridge);
@override
TorSettings fetchRemoteBridges(bool fetchRemoteBridges) =>
this(fetchRemoteBridges: fetchRemoteBridges);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TorSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
@@ -42,17 +59,27 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
/// ````
TorSettings call({
Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(),
Object? autoConfig = const $CopyWithPlaceholder(),
Object? config = const $CopyWithPlaceholder(),
Object? requireBridge = const $CopyWithPlaceholder(),
Object? fetchRemoteBridges = const $CopyWithPlaceholder(),
}) {
return TorSettings(
proxyPrivateTabsTor: proxyPrivateTabsTor == const $CopyWithPlaceholder()
? _value.proxyPrivateTabsTor
// ignore: cast_nullable_to_non_nullable
: proxyPrivateTabsTor as bool,
autoConfig: autoConfig == const $CopyWithPlaceholder()
? _value.autoConfig
config: config == const $CopyWithPlaceholder()
? _value.config
// ignore: cast_nullable_to_non_nullable
: autoConfig as bool,
: config as TorConnectionConfig,
requireBridge: requireBridge == const $CopyWithPlaceholder()
? _value.requireBridge
// ignore: cast_nullable_to_non_nullable
: requireBridge as bool,
fetchRemoteBridges: fetchRemoteBridges == const $CopyWithPlaceholder()
? _value.fetchRemoteBridges
// ignore: cast_nullable_to_non_nullable
: fetchRemoteBridges as bool,
);
}
}
@@ -70,11 +97,22 @@ extension $TorSettingsCopyWith on TorSettings {
TorSettings _$TorSettingsFromJson(Map<String, dynamic> json) =>
TorSettings.withDefaults(
proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?,
autoConfig: json['autoConfig'] as bool?,
config: $enumDecodeNullable(_$TorConnectionConfigEnumMap, json['config']),
requireBridge: json['requireBridge'] as bool?,
fetchRemoteBridges: json['fetchRemoteBridges'] as bool?,
);
Map<String, dynamic> _$TorSettingsToJson(TorSettings instance) =>
<String, dynamic>{
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
'autoConfig': instance.autoConfig,
'config': _$TorConnectionConfigEnumMap[instance.config]!,
'requireBridge': instance.requireBridge,
'fetchRemoteBridges': instance.fetchRemoteBridges,
};
const _$TorConnectionConfigEnumMap = {
TorConnectionConfig.auto: 'auto',
TorConnectionConfig.direct: 'direct',
TorConnectionConfig.obfs4: 'obfs4',
TorConnectionConfig.snowflake: 'snowflake',
};
@@ -27,7 +27,7 @@ final generalSettingsWithDefaultsProvider =
typedef GeneralSettingsWithDefaultsRef =
AutoDisposeProviderRef<GeneralSettings>;
String _$generalSettingsRepositoryHash() =>
r'07eeaf811fac4b9c787cce7c29379a7329910ae5';
r'0d207ae4ae9fd94b1257eb8aa4cfd73d4a47caf2';
/// See also [GeneralSettingsRepository].
@ProviderFor(GeneralSettingsRepository)
@@ -23,7 +23,12 @@ class TorSettingsRepository extends _$TorSettingsRepository {
DriftSqlType.bool,
db.typeMapping,
),
'autoConfig': settings['autoConfig']?.readAs(
'config': settings['config']?.readAs(DriftSqlType.string, db.typeMapping),
'requireBridge': settings['requireBridge']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
'fetchRemoteBridges': settings['fetchRemoteBridges']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
@@ -26,7 +26,7 @@ final torSettingsWithDefaultsProvider =
// ignore: unused_element
typedef TorSettingsWithDefaultsRef = AutoDisposeProviderRef<TorSettings>;
String _$torSettingsRepositoryHash() =>
r'686e8576453f33a0d2d0eb2869e29ca88e00bc37';
r'ef80d1cc6b9b72cce371c03c656b10a0924d49d0';
/// See also [TorSettingsRepository].
@ProviderFor(TorSettingsRepository)
+4
View File
@@ -0,0 +1,4 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
- drift: true
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>readability [24 Aug 2025 at 12:45]</title>
<title>readability [12 Sep 2025 at 07:08]</title>
<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABrVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+O1foceMD///+J0/qK1Pr7/v8Xdr/9///W8P4UdL7L7P0Scr2r4Pyj3vwad8D5/f/2/f+55f3E6f34+/2H0/ojfMKpzOd0rNgQcb3F3O/j9f7c8v6g3Pz0/P/w+v/q+P7n9v6T1/uQ1vuE0vqLut/y+v+Z2fvt+f+15Pzv9fuc2/vR7v2V2Pvd6/bg9P7I6/285/2y4/yp3/zp8vk8i8kqgMT7/P31+fyv4vxGkcz6/P6/6P3j7vfS5PNnpNUxhcbO7f7F6v3O4vHK3/DA2u631Ouy0eqXweKJud5wqthfoNMMbLvY8f73+v2dxeR8sNtTmdDx9/zX6PSjyeaCtd1YnNGX2PuQveCGt95Nls42h8dLlM3F4vBtAAAAM3RSTlMAAyOx0/sKBvik8opWGBMOAe3l1snDm2E9LSb06eHcu5JpHbarfHZCN9CBb08zzkdNS0kYaptYAAAFV0lEQVRYw92X51/aYBDHHS2O2qqttVbrqNq9m+TJIAYIShBkWwqIiCgoWvfeq7Z2/s29hyQNyUcR7LveGwVyXy6XH8/9rqxglLfUPLxVduUor3h0rfp2TYvpivk37929TkG037hffoX0+peVtZQc1589rigVUdXS/ABSAyEmGIO/1XfvldSK8vs3OqB6u3m0nxmIrvgB0dj7rr7Y9IbuF68hnfFaiHA/sxqm0wciIG43P60qKv9WXWc1RXGh/mFESFABTSBi0sNAKzqet17eCtOb3kZIDwxEEU0oAIJGYxNBDhBND29e0rtXXbcpuPmED9IhEAAQ/AXEaF8EPmnrrKsv0LvWR3fg5sWDNAFZOgAgaKvZDogHNU9MFwnnYROkc56RD5CjAbQX9Ow4g7upCsvYu55aSI/Nj0H1akgKQEUM94dwK65hYRmFU9MIcH/fqJYOZYcnuJSU/waKDgTOEVaVKhwrTRP5XzgSpAITYzom7UvkhFX5VutmxeNnWDjjswTKTyfgluNDGbUpWissXhF3s7mlSml+czWkg3D0l1nNjGNjz3myOQOa1KM/jOS6ebdbAVTCi4gljHSFrviza7tOgRWcS0MOUX9zdNgag5w7rRqA44Lzw0hr1WqES36dFliSJFlh2rXIae3FFcDDgKdxrUIDePr8jGcSClV1u7A9xeN0ModY/pHMxmR1EzRh8TJiwqsHmKW0l4FCEZI+jHio+JdPPE9qwQtTRxku2D8sIeRL2LnxWSllANCQGOIiqVHAz2ye2JR0DcH+HoxDkaADLjgxjKQ+AwCX/g0+DNgdG0ukYCONAe+dbc2IAc6fwt1ARoDSezNHxV2Cmzwv3O6lDMV55edBGwGK9n1+x2F8EDfAGCxug8MhpsMEcTEAWf3rx2vZhe/LAmtIn/6apE6PN0ULKgywD9mmdxbmFl3OvD5AS5fW5zLbv/YHmcsBTjf/afDz3MaZTVCfAP9z6/Bw6ycv8EUBWJIn9zYcoAWWlW9+OzO3vkTy8H+RANLmdrpOuYWdZYEXpo+TlCJrW5EARb7fF+bWdqf3hhyZI1nWJQHgznErZhbjoEsWqi8dQNoE294aldzFurwSABL2XXMf9+H1VQGke9exw5P/AnA5Pv5ngMul7LOvO922iwACu8WkCwLCafvM4CeWPxfA8lNHcWZSoi8EwMAIciKX2Z4SWCMAa3snCZ/G4EA8D6CMLNFsGQhkkz/gQNEBbPCbWsxGUpYVu3z8IyNAknwJkfPMEhLyrdi5RTyUVACkw4GSFRNWJNEW+fgPGwHD8/JxnRuLabN4CGNRkAE23na2+VmEAUmrYymSGjMAYqH84YUIyzgzs3XC7gNgH36Vcc4zKY9o9fgPBXUAiHHwVboBHGLiX6Zcjp1f2wu4tvzZKo0ecPnDtQYDQvJXaBeNzce45Fp28ZQLrEZVuFqgBwOalArKXnW1UzlnSusQKJqKYNuz4tOnI6sZG4zanpemv+7ySU2jbA9h6uhcgpfy6G2PahirDZ6zvq6zDduMVFTKvzw8wgyEdelwY9in3XkEPs3osJuwRQ4qTkfzifndg9Gfc4pdsu82+tTnHZTBa2EAMrqr2t43pguc8tNm7JQVQ2S0ukj2d22dhXYP0/veWtwKrCkNoNimAN5+Xr/oLrxswKbVJjteWrX7eR63o4j9q0GxnaBdWgGA5VStpanIjQmEhV0/nVt5VOFUvix6awJhPcAaTEShgrG+iGyvb5a0Ndb1YGHFPEwoqAinoaykaID1o1pdPNu7XsnCKQ3R+hwWIIhGvORcJUBYXe3Xa3vq/mF/N9V13ugufMkfXn+KHsRD0B8AAAAASUVORK5CYII=" type="image/x-icon" />
<script>
@@ -1,8 +1,8 @@
{
"hash": "1d5bede379cdf6035685",
"version": "5.97.1",
"time": 548,
"builtAt": 1756032309689,
"time": 557,
"builtAt": 1757653694040,
"publicPath": "auto",
"outputPath": "/home/fafre/development/repos/lensai/packages/flutter_mozilla_components/javascript/readability/dist",
"assetsByChunkName": {
@@ -1,2 +1,3 @@
export 'src/data/models/moat.dart';
export 'src/data/service/moat_service.dart';
export 'src/pigeons/proxy.g.dart' show IPtProxyController, ProxyType;
@@ -83,13 +83,19 @@ class Setting {
@JsonSerializable()
class Bridge {
final String type;
@JsonKey(toJson: _transportToJson, fromJson: _transportFromJson)
final TransportType type;
final String source;
@JsonKey(name: 'bridge_strings')
final List<String>? bridges;
const Bridge({required this.type, required this.source, this.bridges});
static String _transportToJson(TransportType transport) => transport.value;
static TransportType _transportFromJson(dynamic json) =>
TransportType.fromString(json as String)!;
factory Bridge.fromJson(Map<String, dynamic> json) => _$BridgeFromJson(json);
Map<String, dynamic> toJson() => _$BridgeToJson(this);
@@ -50,7 +50,7 @@ Map<String, dynamic> _$SettingToJson(Setting instance) => <String, dynamic>{
};
Bridge _$BridgeFromJson(Map<String, dynamic> json) => Bridge(
type: json['type'] as String,
type: Bridge._transportFromJson(json['type']),
source: json['source'] as String,
bridges: (json['bridge_strings'] as List<dynamic>?)
?.map((e) => e as String)
@@ -58,7 +58,7 @@ Bridge _$BridgeFromJson(Map<String, dynamic> json) => Bridge(
);
Map<String, dynamic> _$BridgeToJson(Bridge instance) => <String, dynamic>{
'type': instance.type,
'type': Bridge._transportToJson(instance.type),
'source': instance.source,
'bridge_strings': instance.bridges,
};
@@ -9,6 +9,8 @@ import 'package:socks5_proxy/socks_client.dart';
const String _meekParameters =
'url=https://1723079976.rsc.cdn77.org;front=www.phpmyadmin.net';
const defaultBridges = [TransportType.obfs4, TransportType.snowflake];
/// Manages MOAT API connections with persistent proxy setup.
class MoatService {
MoatApi? _api;
@@ -66,10 +68,10 @@ class MoatService {
}
/// Converts BuiltInBridges to Settings for requested transports.
List<Setting> _convertBuiltinToSettings(
BuiltInBridges builtinBridges,
List<TransportType> transports,
) {
static List<Setting> convertBuiltinToSettings(
BuiltInBridges builtinBridges, {
List<TransportType> transports = defaultBridges,
}) {
final settings = <Setting>[];
for (final transport in transports) {
@@ -85,7 +87,7 @@ class MoatService {
settings.add(
Setting(
bridge: Bridge(
type: transport.value,
type: transport,
source: 'builtin',
bridges: bridgeStrings,
),
@@ -98,29 +100,20 @@ class MoatService {
}
/// Gets built-in bridges from the MOAT service endpoint.
Future<List<Setting>?> getBuiltinBridges({
List<TransportType> transports = const [
TransportType.obfs4,
TransportType.snowflake,
TransportType.meek,
],
Future<BuiltInBridges> getBuiltinBridges({
List<TransportType> transports = defaultBridges,
}) async {
_ensureInitialized();
final builtinBridges = await _api!.builtin();
final settings = _convertBuiltinToSettings(builtinBridges, transports);
return settings.isEmpty ? null : settings;
return builtinBridges;
}
/// Tries to automatically configure Pluggable Transports.
Future<List<Setting>?> autoConf({
String? country,
bool cannotConnectWithoutPt = false,
List<TransportType> transports = const [
TransportType.obfs4,
TransportType.snowflake,
TransportType.webtunnel,
],
List<TransportType> transports = defaultBridges,
}) async {
_ensureInitialized();
@@ -146,6 +139,18 @@ class MoatService {
return response.settings;
}
Future<List<Setting>?> getDefaultBridges({
List<TransportType> transports = defaultBridges,
}) async {
_ensureInitialized();
final response = await _api!.defaults(
SettingsRequest(transports: transports),
);
return response.settings;
}
/// Gets the list of supported countries from the MOAT service.
Future<List<String>> getCountries() async {
_ensureInitialized();
@@ -13,10 +13,7 @@ dependencies:
sdk: flutter
http: ^1.5.0
json_annotation: ^4.9.0
socks5_proxy:
git:
url: https://github.com/sylvieon/socks_dart.git
ref: 04550bc08f42cc1a35f1e870f651b93a4f9aceab
socks5_proxy: ^2.1.1
dev_dependencies:
build_runner: ^2.4.15
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env sh
BASE=$(dirname "$0")
cd "$BASE/../packages/flutter_mozilla_components/javascript/container_proxy" || exit
npm ci
npx webpack
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env sh
BASE=$(dirname "$0")
cd "$BASE/../packages/flutter_mozilla_components/javascript/readability" || exit
npm ci
npx webpack
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env sh
BASE=$(dirname "$0")
cd "$BASE/../app/assets/preferences" || exit
rm -f builtin-bridges.json
wget -O builtin-bridges.json https://bridges.torproject.org/moat/circumvention/builtin