tor lifecycle management
This commit is contained in:
@@ -6,7 +6,7 @@ part of 'tab.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$tabRepositoryHash() => r'10768bd2c880edd40f82ed49532d9e2e2043e61f';
|
||||
String _$tabRepositoryHash() => r'9bdd6ddc0254c830b783eb2a987d98d4f1d313fb';
|
||||
|
||||
/// See also [TabRepository].
|
||||
@ProviderFor(TabRepository)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'lifecycle.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class BrowserViewLifecycle extends _$BrowserViewLifecycle {
|
||||
// ignore: use_setters_to_change_properties
|
||||
void update(AppLifecycleState? newState) {
|
||||
state = newState;
|
||||
}
|
||||
|
||||
@override
|
||||
AppLifecycleState? build() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'lifecycle.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$browserViewLifecycleHash() =>
|
||||
r'9082d058611aaaae268b60b8ce130150f73d453d';
|
||||
|
||||
/// See also [BrowserViewLifecycle].
|
||||
@ProviderFor(BrowserViewLifecycle)
|
||||
final browserViewLifecycleProvider =
|
||||
NotifierProvider<BrowserViewLifecycle, AppLifecycleState?>.internal(
|
||||
BrowserViewLifecycle.new,
|
||||
name: r'browserViewLifecycleProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$browserViewLifecycleHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$BrowserViewLifecycle = Notifier<AppLifecycleState?>;
|
||||
// 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
|
||||
+10
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -14,6 +15,7 @@ import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
|
||||
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/repositories/tab.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers/lifecycle.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/services/browser_data.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_replication.dart';
|
||||
@@ -135,6 +137,12 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref
|
||||
.read(browserViewLifecycleProvider.notifier)
|
||||
.update(SchedulerBinding.instance.lifecycleState);
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
//Initialize and register dependencies
|
||||
@@ -172,6 +180,8 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
|
||||
ref.read(browserViewLifecycleProvider.notifier).update(state);
|
||||
|
||||
switch (state) {
|
||||
case AppLifecycleState.detached:
|
||||
case AppLifecycleState.inactive:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||
import 'package:lensai/features/geckoview/features/browser/domain/providers/lifecycle.dart';
|
||||
import 'package:lensai/features/tor/utils/tor_entrypoint.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
@@ -18,6 +20,14 @@ class _TorService {
|
||||
await service.startService();
|
||||
}
|
||||
|
||||
Future<void> requestSync() async {
|
||||
service.invoke("sync");
|
||||
}
|
||||
|
||||
Future<void> sendHeartbeat() async {
|
||||
service.invoke("heartbeat");
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
service.invoke("stop");
|
||||
}
|
||||
@@ -39,9 +49,9 @@ class _TorService {
|
||||
});
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
unawaited(stop());
|
||||
unawaited(_portSubject.close());
|
||||
Future<void> dispose() async {
|
||||
await stop();
|
||||
await _portSubject.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,17 +59,44 @@ class _TorService {
|
||||
class TorProxyService extends _$TorProxyService {
|
||||
final _tor = _TorService();
|
||||
|
||||
Timer? _heartbeatUpdate;
|
||||
//This is managed by widget state changes to resume a timer
|
||||
bool _timerPaused = false;
|
||||
|
||||
void _enableHeartbeatTimer() {
|
||||
_heartbeatUpdate?.cancel();
|
||||
_timerPaused = false;
|
||||
_heartbeatUpdate = Timer.periodic(const Duration(seconds: 30), (
|
||||
timer,
|
||||
) async {
|
||||
await _tor.sendHeartbeat();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> connect({bool forceReconnect = false}) async {
|
||||
final currentPort = _tor.portStream.valueOrNull;
|
||||
final currentPort = await requestSync();
|
||||
|
||||
if (currentPort == null || forceReconnect) {
|
||||
state = const AsyncLoading();
|
||||
await _tor.start();
|
||||
_enableHeartbeatTimer();
|
||||
}
|
||||
}
|
||||
|
||||
Future<int?> requestSync() async {
|
||||
final nextPortUpdate = _tor.portStream.first;
|
||||
|
||||
await _tor.requestSync();
|
||||
return nextPortUpdate;
|
||||
}
|
||||
|
||||
Future<void> disconnect() async {
|
||||
state = const AsyncLoading();
|
||||
|
||||
_heartbeatUpdate?.cancel();
|
||||
_heartbeatUpdate = null;
|
||||
_timerPaused = false;
|
||||
|
||||
await _tor.stop();
|
||||
}
|
||||
|
||||
@@ -71,9 +108,31 @@ class TorProxyService extends _$TorProxyService {
|
||||
|
||||
await _tor.initializeService();
|
||||
|
||||
ref.listen(browserViewLifecycleProvider, (previous, next) {
|
||||
switch (next) {
|
||||
case AppLifecycleState.resumed:
|
||||
if (_timerPaused) {
|
||||
_enableHeartbeatTimer();
|
||||
}
|
||||
case AppLifecycleState.detached:
|
||||
case AppLifecycleState.inactive:
|
||||
case AppLifecycleState.hidden:
|
||||
case AppLifecycleState.paused:
|
||||
case null:
|
||||
if (_heartbeatUpdate?.isActive == true) {
|
||||
_heartbeatUpdate?.cancel();
|
||||
_timerPaused = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ref.onDispose(() async {
|
||||
_heartbeatUpdate?.cancel();
|
||||
_heartbeatUpdate = null;
|
||||
_timerPaused = false;
|
||||
|
||||
await portSub.cancel();
|
||||
_tor.dispose();
|
||||
await _tor.dispose();
|
||||
});
|
||||
|
||||
return _tor.portStream.valueOrNull;
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'tor_proxy.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$torProxyServiceHash() => r'782708b8e5159931cecf8d32b843cb96a929fb1f';
|
||||
String _$torProxyServiceHash() => r'1f0c7611fd939ff25ad0acbdbd205b7e6e83067c';
|
||||
|
||||
/// See also [TorProxyService].
|
||||
@ProviderFor(TorProxyService)
|
||||
|
||||
@@ -12,24 +12,28 @@ class StartProxyController extends _$StartProxyController {
|
||||
// ignore: document_ignores is used for dialog
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future<void> maybeStartProxy(BuildContext context) async {
|
||||
final torProxyRunning = ref.read(torProxyServiceProvider);
|
||||
final torProxyRunning =
|
||||
await ref.read(torProxyServiceProvider.notifier).requestSync();
|
||||
|
||||
if (torProxyRunning.valueOrNull == null) {
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return TorDialog();
|
||||
},
|
||||
);
|
||||
if (torProxyRunning == null) {
|
||||
if (context.mounted) {
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return TorDialog();
|
||||
},
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
final connection = ref.read(torProxyServiceProvider.notifier).connect();
|
||||
if (result == true) {
|
||||
final connection =
|
||||
ref.read(torProxyServiceProvider.notifier).connect();
|
||||
|
||||
ref
|
||||
.read(overlayControllerProvider.notifier)
|
||||
.show(Positioned(top: 0, left: 0, child: TorNotification()));
|
||||
ref
|
||||
.read(overlayControllerProvider.notifier)
|
||||
.show(Positioned(top: 0, left: 0, child: TorNotification()));
|
||||
|
||||
await connection;
|
||||
await connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'start_tor_proxy.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$startProxyControllerHash() =>
|
||||
r'7ba8d6eded4664a8aa3eb1360926c56e9185cac9';
|
||||
r'b83cdc1b6d1c358a2042ff2b7cc2f19f82e5d5ed';
|
||||
|
||||
/// See also [StartProxyController].
|
||||
@ProviderFor(StartProxyController)
|
||||
|
||||
@@ -3,6 +3,7 @@ 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';
|
||||
import 'package:lensai/presentation/hooks/on_initialization.dart';
|
||||
|
||||
class TorProxyScreen extends HookConsumerWidget {
|
||||
const TorProxyScreen();
|
||||
@@ -11,6 +12,10 @@ class TorProxyScreen extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final torProxyPort = ref.watch(torProxyServiceProvider);
|
||||
|
||||
useOnInitialization(() async {
|
||||
await ref.read(torProxyServiceProvider.notifier).requestSync();
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Tor Proxy')),
|
||||
body: ColoredBox(
|
||||
|
||||
@@ -1,22 +1,50 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||
import 'package:tor/tor.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> onStart(ServiceInstance service) async {
|
||||
Timer? timeout;
|
||||
|
||||
await Tor.init();
|
||||
|
||||
final portSub = Tor.instance.events.stream.listen((port) {
|
||||
service.invoke('portUpdate', {'port': port});
|
||||
});
|
||||
|
||||
await Tor.instance.start();
|
||||
Future<void> stopService() async {
|
||||
timeout?.cancel();
|
||||
timeout = null;
|
||||
|
||||
service.on("stop").listen((event) async {
|
||||
Tor.instance.stop();
|
||||
service.invoke('portUpdate', {'port': -1});
|
||||
|
||||
await portSub.cancel();
|
||||
|
||||
await service.stopSelf();
|
||||
}
|
||||
|
||||
void addHeartbeat() {
|
||||
timeout?.cancel();
|
||||
timeout = Timer(const Duration(minutes: 15), () async {
|
||||
// logger.i('Terminating tor due to timeout');
|
||||
await stopService();
|
||||
});
|
||||
}
|
||||
|
||||
await Tor.instance.start();
|
||||
|
||||
service.on("heartbeat").listen((event) {
|
||||
// logger.d('Received tor heartbeat');
|
||||
addHeartbeat();
|
||||
});
|
||||
|
||||
service.on("sync").listen((event) {
|
||||
service.invoke('portUpdate', {'port': Tor.instance.port});
|
||||
});
|
||||
|
||||
service.on("stop").listen((event) async {
|
||||
await stopService();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user