Add thread-safety and BLE support for Ledger and Trezor devices (#3638)

* feat: support BLE device detection for Ledger and Trezor wallets, update trezor-flutter dependency

* feat: add BLE connection state listener for Trezor devices, handle disconnect events, and clean up resources on close

* feat: add mutex locking to MoneroTrezorService for thread-safe operations
This commit is contained in:
Konstantin Ullrich
2026-09-18 21:10:59 +02:00
committed by GitHub
parent 410475b2b8
commit 3d03a62e21
9 changed files with 77 additions and 9 deletions
@@ -108,6 +108,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadConnectedDevices();
_bleStateTimer = Timer.periodic(
const Duration(seconds: 1),
(_) => widget.hardwareWalletVM.updateBleState(),
@@ -131,6 +133,23 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
});
}
Future<void> _loadConnectedDevices() async {
try {
final connected = await widget.hardwareWalletVM.getConnectedBleDevices();
printV(connected);
if (!mounted || connected.isEmpty) {
return;
}
setState(() {
bleDevices.addAll(connected);
longWait = false;
});
} catch (e) {
printV(e);
}
}
@override
void dispose() {
_bleRefreshTimer?.cancel();
@@ -18,6 +18,8 @@ abstract class HardwareWalletViewModel {
Future<List<HardwareWalletDevice>> getAllUsbDevices();
Future<List<HardwareWalletDevice>> getConnectedBleDevices() async => [];
Future<void> stopScanning();
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type);
@@ -120,6 +120,14 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
Future<List<HardwareWalletDevice>> getAllUsbDevices() =>
ledgerPlusUSB.devices.then((devices) => devices.map(LedgerHardwareWalletDevice.new).toList());
@override
Future<List<HardwareWalletDevice>> getConnectedBleDevices() async {
if (!_bleIsInitialized) {
return const [];
}
return (await ledgerPlusBLE.devices).map(LedgerHardwareWalletDevice.new).toList();
}
@override
Future<void> stopScanning() async {
if (_bleIsInitialized) {
@@ -48,6 +48,7 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
}
}
StreamSubscription<sdk.BleConnectionState>? _connectionChangeSubscription;
final connect_sdk.TrezorConnect trezorConnect;
final SecureStorage _secureStorage;
@@ -125,6 +126,14 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
Future<List<HardwareWalletDevice>> getAllUsbDevices() =>
trezorUSB.devices.then((devices) => devices.map(TrezorHardwareWalletDevice.new).toList());
@override
Future<List<HardwareWalletDevice>> getConnectedBleDevices() async {
if (!_bleIsInitialized) {
return const [];
}
return (await trezorBLE.devices).map(TrezorHardwareWalletDevice.new).toList();
}
@override
Future<void> stopScanning() async {
if (_bleIsInitialized) {
@@ -168,6 +177,9 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
device.connectionType == HardwareWalletConnectionType.ble ? trezorBLE : trezorUSB;
final connection = await trezorInterface.connect(device.device);
_connectionChangeSubscription ??=
trezorInterface.deviceStateChanges.listen(_connectionChangeListener);
if (!isRetry) {
unawaited(
showModalBottomSheet(
@@ -342,6 +354,13 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
}
}
void _connectionChangeListener(sdk.BleConnectionState event) {
printV("Ledger Device State Changed: $event");
if (event == sdk.BleConnectionState.disconnected && !isConnecting) {
_client = null;
}
}
Future<bool> syncKeyImages(WalletBase wallet) async {
if (wallet.type == WalletType.monero) {
try {
@@ -352,6 +371,17 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
}
return true;
}
@override
Future<void> close() async {
try {
await _connectionChangeSubscription?.cancel();
_connectionChangeSubscription = null;
isConnecting = false;
await stopScanning();
} catch (_) {}
}
}
abstract class TrezorParingState {