Skip to content

Commit 46a0241

Browse files
authored
Merge pull request #2432 from SatoshiPortal/refactor-errors-bitbox
refactor(bitbox): sanitize errors with sealed failures and a Result boundary
2 parents 0961f6f + 28b19b6 commit 46a0241

40 files changed

Lines changed: 766 additions & 1080 deletions

lib/core/bitbox/data/datasources/bitbox_device_datasource.dart

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'dart:io' show Platform;
33

44
import 'package:bb_mobile/core/bitbox/data/models/bitbox_device_model.dart';
55
import 'package:bb_mobile/core/bitbox/domain/entities/bitbox_device_entity.dart';
6-
import 'package:bb_mobile/core/bitbox/domain/errors/bitbox_errors.dart';
6+
import 'package:bb_mobile/core/bitbox/domain/errors/bitbox_failure.dart';
77
import 'package:bb_mobile/core/utils/logger.dart';
88
import 'package:bb_mobile/core/wallet/domain/entities/wallet.dart';
99
import 'package:bitbox_transport/bitbox_transport.dart';
@@ -30,8 +30,7 @@ class BitBoxDeviceDatasource {
3030
return await _scanBleDevices();
3131
}
3232
} catch (e) {
33-
if (e is BitBoxError) rethrow;
34-
throw BitBoxError.operationFailed(message: e.toString());
33+
throw _mapOperationError(e);
3534
}
3635
}
3736

@@ -46,7 +45,7 @@ class BitBoxDeviceDatasource {
4645

4746
while (DateTime.now().isBefore(deadline)) {
4847
if (usbScanSessionId != _usbScanSessionId) {
49-
throw const BitBoxError.operationCancelled();
48+
throw const OperationCancelledBitBoxFailure();
5049
}
5150

5251
var devices = <BitBox02Device>[];
@@ -59,7 +58,7 @@ class BitBoxDeviceDatasource {
5958
}
6059

6160
if (usbScanSessionId != _usbScanSessionId) {
62-
throw const BitBoxError.operationCancelled();
61+
throw const OperationCancelledBitBoxFailure();
6362
}
6463
if (devices.isNotEmpty) {
6564
final deviceModels = devices.map((device) {
@@ -78,10 +77,10 @@ class BitBoxDeviceDatasource {
7877
}
7978

8079
if (!scanSucceeded && lastScanError != null) {
81-
throw BitBoxError.operationFailed(message: lastScanError.toString());
80+
throw _mapOperationError(lastScanError);
8281
}
8382

84-
throw const BitBoxError.noDevicesFound();
83+
throw const NoDevicesFoundBitBoxFailure();
8584
}
8685

8786
Future<List<BitBoxDeviceModel>> _scanBleDevices() async {
@@ -92,10 +91,10 @@ class BitBoxDeviceDatasource {
9291
devices = await _scanBleDevicesForDuration();
9392
} on UniversalBleException catch (e) {
9493
if (_isBlePermissionError(e)) {
95-
throw const BitBoxError.permissionDenied();
94+
throw const PermissionDeniedBitBoxFailure();
9695
}
9796
if (_isBleUnavailableError(e)) {
98-
throw const BitBoxError.bluetoothUnavailable();
97+
throw const BluetoothUnavailableBitBoxFailure();
9998
}
10099
rethrow;
101100
}
@@ -125,21 +124,21 @@ class BitBoxDeviceDatasource {
125124
try {
126125
final bleState = await UniversalBle.getBluetoothAvailabilityState();
127126
if (bleState == AvailabilityState.unauthorized) {
128-
throw const BitBoxError.permissionDenied();
127+
throw const PermissionDeniedBitBoxFailure();
129128
}
130129
if (bleState == AvailabilityState.unsupported ||
131130
bleState == AvailabilityState.poweredOff) {
132-
throw const BitBoxError.bluetoothUnavailable();
131+
throw const BluetoothUnavailableBitBoxFailure();
133132
}
134133
if (bleState != AvailabilityState.poweredOn) {
135134
await _waitForBleTransportReady();
136135
}
137136
} on UniversalBleException catch (e) {
138137
if (_isBlePermissionError(e)) {
139-
throw const BitBoxError.permissionDenied();
138+
throw const PermissionDeniedBitBoxFailure();
140139
}
141140
if (_isBleUnavailableError(e)) {
142-
throw const BitBoxError.bluetoothUnavailable();
141+
throw const BluetoothUnavailableBitBoxFailure();
143142
}
144143
rethrow;
145144
}
@@ -165,9 +164,9 @@ class BitBoxDeviceDatasource {
165164

166165
if (bleState == AvailabilityState.poweredOn) return;
167166
if (bleState == AvailabilityState.unauthorized) {
168-
throw const BitBoxError.permissionDenied();
167+
throw const PermissionDeniedBitBoxFailure();
169168
}
170-
throw const BitBoxError.bluetoothUnavailable();
169+
throw const BluetoothUnavailableBitBoxFailure();
171170
}
172171

173172
bool _isBlePermissionError(UniversalBleException error) {
@@ -208,10 +207,10 @@ class BitBoxDeviceDatasource {
208207

209208
List<BitBoxDeviceModel> _ensureSingleDevice(List<BitBoxDeviceModel> devices) {
210209
if (devices.isEmpty) {
211-
throw const BitBoxError.noDevicesFound();
210+
throw const NoDevicesFoundBitBoxFailure();
212211
}
213212
if (devices.length > 1) {
214-
throw const BitBoxError.multipleDevicesFound();
213+
throw const MultipleDevicesFoundBitBoxFailure();
215214
}
216215

217216
return devices;
@@ -228,27 +227,26 @@ class BitBoxDeviceDatasource {
228227
return await _connectBleDevice(device);
229228
}
230229
} catch (e) {
231-
if (e is BitBoxError) rethrow;
232-
throw BitBoxError.operationFailed(message: e.toString());
230+
throw _mapOperationError(e);
233231
}
234232
}
235233

236234
Future<BitBoxDeviceModel> _connectUsbDevice(BitBoxDeviceModel device) async {
237235
if (_platformTransport != BitBoxConnectionType.usb) {
238-
throw const BitBoxError.connectionTypeNotInitialized();
236+
throw const ConnectionTypeNotInitializedBitBoxFailure();
239237
}
240238

241239
final hasPermission = await BitBoxApi.requestPermission(device.deviceName);
242240
if (!hasPermission) {
243-
throw const BitBoxError.permissionDenied();
241+
throw const PermissionDeniedBitBoxFailure();
244242
}
245243

246244
final opened = await BitBoxApi.openDevice(
247245
device.deviceName,
248246
device.serialNumber,
249247
);
250248
if (!opened) {
251-
throw const BitBoxError.connectionFailed();
249+
throw const ConnectionFailedBitBoxFailure();
252250
}
253251

254252
_connectedDevice = device;
@@ -257,7 +255,7 @@ class BitBoxDeviceDatasource {
257255

258256
Future<BitBoxDeviceModel> _connectBleDevice(BitBoxDeviceModel device) async {
259257
if (_platformTransport != BitBoxConnectionType.ble) {
260-
throw const BitBoxError.connectionTypeNotInitialized();
258+
throw const ConnectionTypeNotInitializedBitBoxFailure();
261259
}
262260

263261
await _ensureBleTransportReady();
@@ -269,24 +267,24 @@ class BitBoxDeviceDatasource {
269267
serialNumber: device.serialNumber,
270268
);
271269
} on TimeoutException {
272-
throw const BitBoxError.operationTimeout();
270+
throw const OperationTimeoutBitBoxFailure();
273271
} on UniversalBleException catch (e) {
274272
if (_isBlePermissionError(e)) {
275-
throw const BitBoxError.permissionDenied();
273+
throw const PermissionDeniedBitBoxFailure();
276274
}
277275
if (_isBleUnavailableError(e)) {
278-
throw const BitBoxError.bluetoothUnavailable();
276+
throw const BluetoothUnavailableBitBoxFailure();
279277
}
280278
if (_bleErrorCode(e) == UniversalBleErrorCode.connectionTimeout) {
281-
throw const BitBoxError.operationTimeout();
279+
throw const OperationTimeoutBitBoxFailure();
282280
}
283281
if (e is ConnectionException || _isBleConnectionError(e)) {
284-
throw const BitBoxError.connectionFailed();
282+
throw const ConnectionFailedBitBoxFailure();
285283
}
286284
rethrow;
287285
}
288286
if (!connected) {
289-
throw const BitBoxError.connectionFailed();
287+
throw const ConnectionFailedBitBoxFailure();
290288
}
291289

292290
_connectedDevice = device;
@@ -314,7 +312,7 @@ class BitBoxDeviceDatasource {
314312
);
315313

316314
if (!confirmed) {
317-
throw const BitBoxError.operationCancelled();
315+
throw const OperationCancelledBitBoxFailure();
318316
}
319317

320318
return await getMasterFingerprint(device);
@@ -394,10 +392,46 @@ class BitBoxDeviceDatasource {
394392
}
395393
}
396394

397-
BitBoxError _mapOperationError(Object error) {
398-
if (error is BitBoxError) return error;
395+
BitBoxFailure _mapOperationError(Object error) {
396+
if (error is BitBoxFailure) return error;
397+
398+
return _interpretOperationError(error.toString()) ??
399+
BitBoxUnexpectedFailure(error.toString());
400+
}
399401

400-
return BitBoxError.operationFailed(message: error.toString());
402+
BitBoxFailure? _interpretOperationError(String raw) {
403+
final normalized = raw.toLowerCase();
404+
405+
if (normalized.contains('permission denied')) {
406+
return const PermissionDeniedBitBoxFailure();
407+
}
408+
if (normalized.contains('no devices found')) {
409+
return const NoDevicesFoundBitBoxFailure();
410+
}
411+
if (normalized.contains('device not found')) {
412+
return const DeviceNotFoundBitBoxFailure();
413+
}
414+
if (normalized.contains('device not paired') ||
415+
normalized.contains('not paired')) {
416+
return const DeviceNotPairedBitBoxFailure();
417+
}
418+
if (normalized.contains('handshake')) {
419+
return const HandshakeFailedBitBoxFailure();
420+
}
421+
if (normalized.contains('timeout')) {
422+
return const OperationTimeoutBitBoxFailure();
423+
}
424+
if (normalized.contains('connection failed')) {
425+
return const ConnectionFailedBitBoxFailure();
426+
}
427+
if (normalized.contains('invalid response')) {
428+
return const InvalidResponseBitBoxFailure();
429+
}
430+
if (normalized.contains('operation cancelled') ||
431+
normalized.contains('operation canceled')) {
432+
return const OperationCancelledBitBoxFailure();
433+
}
434+
return null;
401435
}
402436

403437
Future<void> disconnectConnection(BitBoxDeviceModel device) async {

0 commit comments

Comments
 (0)