-
Notifications
You must be signed in to change notification settings - Fork 14
feat(auth): poll trezor connection status and sign out when disconnected #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
9fccee3
7ebdf4c
32c0006
f1d1493
eae55fa
69f3c88
25265fd
1044a0b
cb186ed
4b1f5fd
9f2dc1c
09f622e
a8ca5e0
4ec1b11
71e1381
323dd31
8d1edc0
2f91435
8dec689
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import 'dart:async'; | ||
|
|
||
| import 'package:flutter/foundation.dart'; | ||
| import 'package:komodo_defi_local_auth/src/trezor/trezor_connection_status.dart'; | ||
| import 'package:komodo_defi_local_auth/src/trezor/trezor_repository.dart'; | ||
| import 'package:logging/logging.dart'; | ||
|
|
||
| /// Service responsible for monitoring Trezor device connection status | ||
| /// and providing callbacks for connection state changes. | ||
| class TrezorConnectionMonitor { | ||
| TrezorConnectionMonitor(this._trezorRepository); | ||
|
|
||
| static final _log = Logger('TrezorConnectionMonitor'); | ||
|
|
||
| final TrezorRepository _trezorRepository; | ||
| StreamSubscription<TrezorConnectionStatus>? _connectionSubscription; | ||
| TrezorConnectionStatus? _lastStatus; | ||
|
|
||
| /// Start monitoring the Trezor connection status. | ||
| /// | ||
| /// [onConnectionLost] will be called when the device becomes disconnected | ||
| /// or unreachable. | ||
| /// [onConnectionRestored] will be called when the device becomes connected | ||
| /// after being disconnected/unreachable. | ||
| /// [onStatusChanged] will be called for any status change. | ||
| /// [maxDuration] sets the maximum time to monitor before timing out. If null, | ||
| /// monitoring continues indefinitely until stopped or disconnected. | ||
| void startMonitoring({ | ||
| String? devicePubkey, | ||
| Duration pollInterval = const Duration(seconds: 1), | ||
| Duration? maxDuration, | ||
| VoidCallback? onConnectionLost, | ||
| VoidCallback? onConnectionRestored, | ||
| void Function(TrezorConnectionStatus)? onStatusChanged, | ||
| }) { | ||
| _log.info('Starting Trezor connection monitoring'); | ||
|
|
||
| // Stop any existing monitoring safely before starting a new one. | ||
| final previousSubscription = _connectionSubscription; | ||
| if (previousSubscription != null) { | ||
| _log.info('Stopping previous Trezor connection monitoring'); | ||
| _connectionSubscription = null; | ||
| _lastStatus = null; | ||
| unawaited(previousSubscription.cancel()); | ||
| } | ||
|
|
||
| _connectionSubscription = _trezorRepository | ||
| .watchConnectionStatus( | ||
| devicePubkey: devicePubkey, | ||
| pollInterval: pollInterval, | ||
| maxDuration: maxDuration, | ||
| ) | ||
| .listen( | ||
| (status) { | ||
| _log.fine('Connection status changed: ${status.value}'); | ||
|
|
||
| final previousStatus = _lastStatus; | ||
| _lastStatus = status; | ||
|
|
||
| onStatusChanged?.call(status); | ||
|
|
||
| if (status.isUnavailable && | ||
| (previousStatus?.isAvailable ?? false)) { | ||
| _log.warning('Trezor connection lost: ${status.value}'); | ||
| onConnectionLost?.call(); | ||
| } | ||
|
|
||
| if (status.isAvailable && | ||
| (previousStatus?.isUnavailable ?? false)) { | ||
| _log.info('Trezor connection restored'); | ||
| onConnectionRestored?.call(); | ||
| } | ||
|
takenagain marked this conversation as resolved.
takenagain marked this conversation as resolved.
takenagain marked this conversation as resolved.
|
||
| }, | ||
| onError: (Object error, StackTrace stackTrace) { | ||
| _log.severe( | ||
| 'Error monitoring Trezor connection: $error', | ||
| error, | ||
| stackTrace, | ||
| ); | ||
| // Only call onConnectionLost if this is a real connection error, not a disposal | ||
| if (_connectionSubscription != null) { | ||
| onConnectionLost?.call(); | ||
| } | ||
| }, | ||
| onDone: () { | ||
| _log.info('Trezor connection monitoring stopped'); | ||
| // Underlying stream ended; mark as not monitoring while keeping | ||
| // the last known status for inspection. | ||
| _connectionSubscription = null; | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| /// Stop monitoring the Trezor connection status. | ||
| Future<void> stopMonitoring() async { | ||
| if (_connectionSubscription != null) { | ||
| _log.info('Stopping Trezor connection monitoring'); | ||
| await _connectionSubscription?.cancel(); | ||
| _connectionSubscription = null; | ||
| _lastStatus = null; | ||
| } | ||
| } | ||
|
|
||
| /// Get the last known connection status. | ||
| TrezorConnectionStatus? get lastKnownStatus => _lastStatus; | ||
|
|
||
| /// Check if monitoring is currently active. | ||
| bool get isMonitoring => _connectionSubscription != null; | ||
|
|
||
| /// Dispose of the monitor and clean up resources. | ||
| void dispose() { | ||
| // Make monitoring appear stopped synchronously. | ||
| final previousSubscription = _connectionSubscription; | ||
| _connectionSubscription = null; | ||
| _lastStatus = null; | ||
| if (previousSubscription != null) { | ||
| unawaited(previousSubscription.cancel()); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /// Enum representing Trezor device connection status | ||
| enum TrezorConnectionStatus { | ||
| /// Device is connected and ready for operations | ||
| connected, | ||
|
|
||
| /// Device is disconnected | ||
| disconnected, | ||
|
|
||
| /// Device is busy with another operation | ||
| busy, | ||
|
|
||
| /// Device is unreachable (possibly hardware issue or driver problem) | ||
| unreachable, | ||
|
|
||
| /// Unknown status (for unrecognized status strings) | ||
| unknown; | ||
|
|
||
| /// Parse a string status from the API response into enum | ||
| static TrezorConnectionStatus fromString(String status) { | ||
| switch (status.toLowerCase()) { | ||
| case 'connected': | ||
| return TrezorConnectionStatus.connected; | ||
| case 'disconnected': | ||
| return TrezorConnectionStatus.disconnected; | ||
| case 'busy': | ||
| return TrezorConnectionStatus.busy; | ||
| case 'unreachable': | ||
| return TrezorConnectionStatus.unreachable; | ||
| default: | ||
| return TrezorConnectionStatus.unknown; | ||
| } | ||
| } | ||
|
|
||
| /// Human-readable label for display purposes | ||
| String get value { | ||
| switch (this) { | ||
| case TrezorConnectionStatus.connected: | ||
| return 'Connected'; | ||
| case TrezorConnectionStatus.disconnected: | ||
| return 'Disconnected'; | ||
| case TrezorConnectionStatus.busy: | ||
| return 'Busy'; | ||
| case TrezorConnectionStatus.unreachable: | ||
| return 'Unreachable'; | ||
| case TrezorConnectionStatus.unknown: | ||
| return 'Unknown'; | ||
| } | ||
| } | ||
|
|
||
| /// Lowercase identifier used by the API | ||
| String get apiValue => name; // matches fromString expectations | ||
|
|
||
| /// Check if the status indicates the device is available for operations | ||
| bool get isAvailable => this == TrezorConnectionStatus.connected; | ||
|
|
||
| /// Check if the status indicates the device is not available | ||
| bool get isUnavailable => | ||
| this == TrezorConnectionStatus.disconnected || | ||
| this == TrezorConnectionStatus.unreachable || | ||
| this == TrezorConnectionStatus.busy; | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Busy Trezor Misinterpreted as UnavailableThe |
||
| /// Check if the device should continue being monitored | ||
| bool get shouldContinueMonitoring => | ||
| this != TrezorConnectionStatus.disconnected; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.