connectDevice method

  1. @override
Future<void> connectDevice()
override

Implementation

@override
Future<void> connectDevice() async {
  final body = await _post('/enumerate');
  final entries = jsonDecode(body) as List<dynamic>;
  if (entries.isEmpty) {
    throw StateError('No devices found');
  }

  final entry = _connectedDevice != null
      ? entries.firstWhere(
          (e) => e['path'] == _connectedDevice!.hidPath,
          orElse: () => entries.first,
        )
      : entries.first;

  final path = entry['path'] as String;
  final existingSession = entry['session'];

  // Atomic session steal: /acquire/<path>/<prev> hands ownership in one call.
  // Using separate release + acquire/null leaves a race window where another
  // client (Trezor Suite) can acquire between the two requests.
  final encodedPath = Uri.encodeComponent(path);
  final prev = (existingSession is String) ? existingSession : 'null';
  final acquireBody = await _post('/acquire/$encodedPath/$prev');
  final acquireJson = jsonDecode(acquireBody) as Map<String, dynamic>;
  _session = acquireJson['session'] as String;
  try {
    final (respType, respPayload) = await call(
      TrezorMsgType.initialize,
      buildInitialize(),
    );
    if (respType == TrezorMsgType.failure) {
      final failure = parseFailure(respPayload);
      throw StateError(
        'Initialize failed: ${failure.message ?? "unknown error"}',
      );
    }
    if (respType != TrezorMsgType.features) {
      throw StateError(
        'Expected Features (${TrezorMsgType.features}), got message type $respType',
      );
    }
    final features = parseFeatures(respPayload);
    _connectedDevice = TrezorDevice.forFirmware(features, hidPath: path);
  } catch (_) {
    _session = null;
    rethrow;
  }
}