tor lifecycle management
This commit is contained in:
@@ -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