@ledgerhq/device-transport-kit-react-native-hid 0.0.0-try-to-fix-20250429171448 → 0.0.0-web-ble-29-08---20250829104351

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/android/build.gradle +0 -9
  2. package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidModule.kt +11 -2
  3. package/android/src/main/kotlin/com/ledger/androidtransporthid/bridge/serialization.kt +2 -0
  4. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/DefaultAndroidUsbTransport.kt +57 -10
  5. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/connection/AndroidUsbApduSender.kt +75 -33
  6. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/utils/UsbDeviceMapper.kt +3 -0
  7. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceApduSender.kt +2 -1
  8. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnection.kt +5 -4
  9. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachine.kt +3 -2
  10. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/SendApduResult.kt +4 -0
  11. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/device/LedgerDevice.kt +16 -0
  12. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/connection/InternalConnectedDevice.kt +1 -1
  13. package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachineTest.kt +47 -31
  14. package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionTest.kt +9 -5
  15. package/lib/cjs/api/bridge/DefaultNativeModuleWrapper.js +1 -1
  16. package/lib/cjs/api/bridge/DefaultNativeModuleWrapper.js.map +3 -3
  17. package/lib/cjs/api/bridge/mapper.js +1 -1
  18. package/lib/cjs/api/bridge/mapper.js.map +2 -2
  19. package/lib/cjs/api/bridge/mapper.test.js +1 -1
  20. package/lib/cjs/api/bridge/mapper.test.js.map +2 -2
  21. package/lib/cjs/api/bridge/types.js +1 -1
  22. package/lib/cjs/api/bridge/types.js.map +1 -1
  23. package/lib/cjs/api/transport/NativeModuleWrapper.js +1 -1
  24. package/lib/cjs/api/transport/NativeModuleWrapper.js.map +1 -1
  25. package/lib/cjs/api/transport/RNHidTransport.js +1 -1
  26. package/lib/cjs/api/transport/RNHidTransport.js.map +3 -3
  27. package/lib/cjs/api/transport/RNHidTransport.test.js +1 -1
  28. package/lib/cjs/api/transport/RNHidTransport.test.js.map +2 -2
  29. package/lib/cjs/package.json +11 -11
  30. package/lib/esm/api/bridge/DefaultNativeModuleWrapper.js +1 -1
  31. package/lib/esm/api/bridge/DefaultNativeModuleWrapper.js.map +3 -3
  32. package/lib/esm/api/bridge/mapper.js +1 -1
  33. package/lib/esm/api/bridge/mapper.js.map +3 -3
  34. package/lib/esm/api/bridge/mapper.test.js +1 -1
  35. package/lib/esm/api/bridge/mapper.test.js.map +3 -3
  36. package/lib/esm/api/bridge/types.js.map +1 -1
  37. package/lib/esm/api/transport/RNHidTransport.js +1 -1
  38. package/lib/esm/api/transport/RNHidTransport.js.map +3 -3
  39. package/lib/esm/api/transport/RNHidTransport.test.js +1 -1
  40. package/lib/esm/api/transport/RNHidTransport.test.js.map +2 -2
  41. package/lib/esm/package.json +11 -11
  42. package/lib/types/api/bridge/DefaultNativeModuleWrapper.d.ts +1 -1
  43. package/lib/types/api/bridge/DefaultNativeModuleWrapper.d.ts.map +1 -1
  44. package/lib/types/api/bridge/mapper.d.ts.map +1 -1
  45. package/lib/types/api/bridge/types.d.ts +2 -2
  46. package/lib/types/api/bridge/types.d.ts.map +1 -1
  47. package/lib/types/api/transport/NativeModuleWrapper.d.ts +1 -1
  48. package/lib/types/api/transport/NativeModuleWrapper.d.ts.map +1 -1
  49. package/lib/types/api/transport/RNHidTransport.d.ts.map +1 -1
  50. package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
  51. package/package.json +13 -13
@@ -60,15 +60,6 @@ android {
60
60
  versionCode 1
61
61
  versionName "1.0"
62
62
  }
63
-
64
- buildTypes {
65
- release {
66
- // Enable ProGuard for release builds
67
- minifyEnabled false // TODO REMOVE
68
- // Use ProGuard for code obfuscation and optimization
69
- //proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
70
- }
71
- }
72
63
 
73
64
  compileOptions {
74
65
  // Use JavaVersion enums for compatibility
@@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.onEach
31
31
  import kotlinx.coroutines.launch
32
32
  import timber.log.Timber
33
33
  import kotlin.random.Random
34
+ import kotlin.time.Duration
34
35
  import kotlin.time.Duration.Companion.milliseconds
35
36
 
36
37
  class TransportHidModule(
@@ -206,7 +207,13 @@ class TransportHidModule(
206
207
  }
207
208
 
208
209
  @ReactMethod
209
- fun sendApdu(sessionId: String, apduBase64: String, promise: Promise) {
210
+ fun sendApdu(
211
+ sessionId: String,
212
+ apduBase64: String,
213
+ triggersDisconnection: Boolean,
214
+ abortTimeout: Int,
215
+ promise: Promise
216
+ ) {
210
217
  try {
211
218
  val device = connectedDevices.firstOrNull() { it.id == sessionId }
212
219
  if (device == null) {
@@ -216,7 +223,9 @@ class TransportHidModule(
216
223
  coroutineScope.launch {
217
224
  try {
218
225
  val apdu: ByteArray = Base64.decode(apduBase64, Base64.DEFAULT)
219
- val res = device.sendApduFn(apdu)
226
+ val abortTimeoutDuration = if (abortTimeout <= 0) Duration.INFINITE else abortTimeout.milliseconds
227
+ val res =
228
+ device.sendApduFn(apdu, triggersDisconnection, abortTimeoutDuration)
220
229
  promise.resolve(res.toWritableMap())
221
230
  } catch (e: Exception) {
222
231
  Timber.i("$e, ${e.cause}")
@@ -103,6 +103,8 @@ internal fun SendApduResult.toWritableMap(): WritableMap {
103
103
  SendApduFailureReason.NoUsbEndpointFound -> "NoUsbEndpointFound"
104
104
  SendApduFailureReason.DeviceDisconnected -> "DeviceDisconnected"
105
105
  SendApduFailureReason.Unknown -> "Unknown"
106
+ SendApduFailureReason.AbortTimeout -> "SendApduTimeout"
107
+ SendApduFailureReason.EmptyResponse -> "EmptyResponse"
106
108
  })
107
109
  }
108
110
  }
@@ -97,17 +97,32 @@ internal class DefaultAndroidUsbTransport(
97
97
  override fun updateUsbState(state: UsbState) {
98
98
  when (state) {
99
99
  is UsbState.Detached -> {
100
- loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Detached deviceId=${state.ledgerUsbDevice.uid}"))
100
+ loggerService.log(
101
+ buildSimpleDebugLogInfo(
102
+ "AndroidUsbTransport",
103
+ "Detached deviceId=${state.ledgerUsbDevice.uid}"
104
+ )
105
+ )
101
106
  usbConnections.entries.find {
102
107
  it.value.getApduSender().dependencies.ledgerUsbDevice.uid == state.ledgerUsbDevice.uid
103
108
  }.let { item ->
104
109
  scope.launch {
105
110
  if (item == null) {
106
- loggerService.log(buildSimpleWarningLogInfo("AndroidUsbTransport", "No connection found"))
111
+ loggerService.log(
112
+ buildSimpleWarningLogInfo(
113
+ "AndroidUsbTransport",
114
+ "No connection found"
115
+ )
116
+ )
107
117
  return@launch
108
118
  }
109
119
  val (key, deviceConnection) = item
110
- loggerService.log(buildSimpleInfoLogInfo("AndroidUsbTransport", "Device disconnected (sessionId=${deviceConnection.sessionId})"))
120
+ loggerService.log(
121
+ buildSimpleInfoLogInfo(
122
+ "AndroidUsbTransport",
123
+ "Device disconnected (sessionId=${deviceConnection.sessionId})"
124
+ )
125
+ )
111
126
  deviceConnection.handleDeviceDisconnected()
112
127
  usbConnections.remove(key)
113
128
  usbConnectionsPendingReconnection.add(deviceConnection)
@@ -116,12 +131,22 @@ internal class DefaultAndroidUsbTransport(
116
131
  }
117
132
 
118
133
  is UsbState.Attached -> {
119
- loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Attached deviceId=${state.ledgerUsbDevice.uid}, pendingReconnections=${usbConnectionsPendingReconnection}"))
134
+ loggerService.log(
135
+ buildSimpleDebugLogInfo(
136
+ "AndroidUsbTransport",
137
+ "Attached deviceId=${state.ledgerUsbDevice.uid}, pendingReconnections=${usbConnectionsPendingReconnection}"
138
+ )
139
+ )
120
140
  val usbDevice = usbManager.deviceList.values.firstOrNull {
121
141
  it.toLedgerUsbDevice()?.uid == state.ledgerUsbDevice.uid
122
142
  }
123
143
  if (usbDevice == null) {
124
- loggerService.log(buildSimpleWarningLogInfo("AndroidUsbTransport", "No UsbDevice found"))
144
+ loggerService.log(
145
+ buildSimpleWarningLogInfo(
146
+ "AndroidUsbTransport",
147
+ "No UsbDevice found"
148
+ )
149
+ )
125
150
  return
126
151
  }
127
152
  usbConnectionsPendingReconnection.firstOrNull {
@@ -138,14 +163,29 @@ internal class DefaultAndroidUsbTransport(
138
163
  )
139
164
  return@launch
140
165
  }
141
- loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Found matching device connection $deviceConnection"))
166
+ loggerService.log(
167
+ buildSimpleDebugLogInfo(
168
+ "AndroidUsbTransport",
169
+ "Found matching device connection $deviceConnection"
170
+ )
171
+ )
142
172
 
143
173
  val permissionResult = checkOrRequestPermission(usbDevice)
144
174
  if (permissionResult is PermissionResult.Denied) {
145
- loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Permission denied"))
175
+ loggerService.log(
176
+ buildSimpleDebugLogInfo(
177
+ "AndroidUsbTransport",
178
+ "Permission denied"
179
+ )
180
+ )
146
181
  return@launch
147
182
  }
148
- loggerService.log(buildSimpleInfoLogInfo("AndroidUsbTransport", "Reconnecting device (sessionId=${deviceConnection.sessionId})"))
183
+ loggerService.log(
184
+ buildSimpleInfoLogInfo(
185
+ "AndroidUsbTransport",
186
+ "Reconnecting device (sessionId=${deviceConnection.sessionId})"
187
+ )
188
+ )
149
189
  deviceConnection.handleDeviceConnected(
150
190
  AndroidUsbApduSender(
151
191
  dependencies = AndroidUsbApduSender.Dependencies(
@@ -195,7 +235,12 @@ internal class DefaultAndroidUsbTransport(
195
235
  device = usbDevice,
196
236
  )
197
237
 
198
- loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Waiting for permission result"))
238
+ loggerService.log(
239
+ buildSimpleDebugLogInfo(
240
+ "AndroidUsbTransport",
241
+ "Waiting for permission result"
242
+ )
243
+ )
199
244
 
200
245
  val result = eventsFlow.first {
201
246
  it is UsbPermissionEvent.PermissionGranted ||
@@ -276,7 +321,9 @@ internal class DefaultAndroidUsbTransport(
276
321
  discoveryDevice.name,
277
322
  discoveryDevice.ledgerDevice,
278
323
  discoveryDevice.connectivityType,
279
- sendApduFn = { apdu -> deviceConnection.requestSendApdu(apdu) },
324
+ sendApduFn = { apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: Duration ->
325
+ deviceConnection.requestSendApdu(apdu, triggersDisconnection, abortTimeoutDuration)
326
+ }
280
327
  )
281
328
 
282
329
  usbConnections[sessionId] = deviceConnection
@@ -13,21 +13,22 @@ import android.hardware.usb.UsbInterface
13
13
  import android.hardware.usb.UsbManager
14
14
  import android.hardware.usb.UsbRequest
15
15
  import com.ledger.devicesdk.shared.androidMain.transport.usb.model.LedgerUsbDevice
16
+ import com.ledger.devicesdk.shared.androidMainInternal.transport.USB_MTU
17
+ import com.ledger.devicesdk.shared.androidMainInternal.transport.deviceconnection.DeviceApduSender
16
18
  import com.ledger.devicesdk.shared.api.apdu.SendApduFailureReason
17
19
  import com.ledger.devicesdk.shared.api.apdu.SendApduResult
18
- import com.ledger.devicesdk.shared.androidMainInternal.transport.deviceconnection.DeviceApduSender
19
- import com.ledger.devicesdk.shared.api.utils.toHexadecimalString
20
- import com.ledger.devicesdk.shared.androidMainInternal.transport.USB_MTU
21
20
  import com.ledger.devicesdk.shared.internal.service.logger.LoggerService
22
21
  import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleErrorLogInfo
23
- import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleInfoLogInfo
24
22
  import com.ledger.devicesdk.shared.internal.transport.framer.FramerService
25
23
  import com.ledger.devicesdk.shared.internal.transport.framer.to2BytesArray
26
- import java.nio.ByteBuffer
27
- import kotlin.random.Random
28
24
  import kotlinx.coroutines.CoroutineDispatcher
25
+ import kotlinx.coroutines.cancel
26
+ import kotlinx.coroutines.delay
27
+ import kotlinx.coroutines.launch
29
28
  import kotlinx.coroutines.withContext
30
- import timber.log.Timber
29
+ import java.nio.ByteBuffer
30
+ import kotlin.random.Random
31
+ import kotlin.time.Duration
31
32
 
32
33
  private const val USB_TIMEOUT = 500
33
34
 
@@ -41,38 +42,70 @@ internal class AndroidUsbApduSender(
41
42
  private val ioDispatcher: CoroutineDispatcher,
42
43
  private val loggerService: LoggerService,
43
44
  ) : DeviceApduSender<AndroidUsbApduSender.Dependencies> {
44
-
45
45
  data class Dependencies(
46
46
  val usbDevice: UsbDevice,
47
47
  val ledgerUsbDevice: LedgerUsbDevice,
48
48
  )
49
49
 
50
- override suspend fun send(apdu: ByteArray): SendApduResult =
50
+ override suspend fun send(apdu: ByteArray, abortTimeoutDuration: Duration): SendApduResult =
51
51
  try {
52
52
  val usbDevice = dependencies.usbDevice
53
53
  withContext(context = ioDispatcher) {
54
54
  val usbInterface = usbDevice.getInterface(DEFAULT_USB_INTERFACE)
55
- val androidToUsbEndpoint = usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_OUT }
56
- val usbToAndroidEndpoint = usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_IN }
57
- val usbConnection = usbManager.openDevice(usbDevice).apply { claimInterface(usbInterface, true) }
58
-
59
- transmitApdu(
60
- usbConnection = usbConnection,
61
- androidToUsbEndpoint = androidToUsbEndpoint,
62
- rawApdu = apdu,
63
- )
64
- val apduResponse =
65
- receiveApdu(
55
+ val androidToUsbEndpoint =
56
+ usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_OUT }
57
+ val usbToAndroidEndpoint =
58
+ usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_IN }
59
+ val usbConnection = usbManager.openDevice(usbDevice)
60
+ .apply { claimInterface(usbInterface, true) }
61
+
62
+ val timeoutJob = launch {
63
+ delay(abortTimeoutDuration)
64
+ usbConnection.releaseInterface(usbInterface)
65
+ usbConnection.close()
66
+ throw SendApduTimeoutException
67
+ }
68
+
69
+ try {
70
+
71
+ transmitApdu(
66
72
  usbConnection = usbConnection,
67
- usbToAndroidEndpoint = usbToAndroidEndpoint,
73
+ androidToUsbEndpoint = androidToUsbEndpoint,
74
+ rawApdu = apdu,
68
75
  )
69
76
 
70
- usbConnection.releaseInterface(usbInterface)
71
- usbConnection.close()
77
+ val apduResponse =
78
+ receiveApdu(
79
+ usbConnection = usbConnection,
80
+ usbToAndroidEndpoint = usbToAndroidEndpoint,
81
+ )
82
+ timeoutJob.cancel()
83
+
84
+ if (apduResponse.isEmpty()) {
85
+ return@withContext SendApduResult.Failure(reason = SendApduFailureReason.EmptyResponse)
86
+ }
72
87
 
73
- SendApduResult.Success(apdu = apduResponse)
88
+ return@withContext SendApduResult.Success(apdu = apduResponse)
89
+ } finally {
90
+ usbConnection.releaseInterface(usbInterface)
91
+ usbConnection.close()
92
+ }
74
93
  }
94
+ } catch (e: SendApduTimeoutException) {
95
+ loggerService.log(
96
+ buildSimpleErrorLogInfo(
97
+ "AndroidUsbApduSender",
98
+ "timeout in send: $e"
99
+ )
100
+ )
101
+ SendApduResult.Failure(reason = SendApduFailureReason.AbortTimeout)
75
102
  } catch (e: NoSuchElementException) {
103
+ loggerService.log(
104
+ buildSimpleErrorLogInfo(
105
+ "AndroidUsbApduSender",
106
+ "no endpoint found: $e"
107
+ )
108
+ )
76
109
  SendApduResult.Failure(reason = SendApduFailureReason.NoUsbEndpointFound)
77
110
  } catch (e: Exception) {
78
111
  loggerService.log(buildSimpleErrorLogInfo("AndroidUsbApduSender", "error in send: $e"))
@@ -84,11 +117,16 @@ internal class AndroidUsbApduSender(
84
117
  androidToUsbEndpoint: UsbEndpoint,
85
118
  rawApdu: ByteArray,
86
119
  ) {
87
- framerService.serialize(mtu = USB_MTU, channelId = generateChannelId(), rawApdu = rawApdu).forEach { apduFrame ->
88
- val buffer = apduFrame.toByteArray()
89
- Timber.i("APDU sent = ${buffer.toHexadecimalString()}")
90
- usbConnection.bulkTransfer(androidToUsbEndpoint, buffer, apduFrame.size(), USB_TIMEOUT)
91
- }
120
+ framerService.serialize(mtu = USB_MTU, channelId = generateChannelId(), rawApdu = rawApdu)
121
+ .forEach { apduFrame ->
122
+ val buffer = apduFrame.toByteArray()
123
+ usbConnection.bulkTransfer(
124
+ androidToUsbEndpoint,
125
+ buffer,
126
+ apduFrame.size(),
127
+ USB_TIMEOUT
128
+ )
129
+ }
92
130
  }
93
131
 
94
132
  private fun receiveApdu(
@@ -99,7 +137,7 @@ internal class AndroidUsbApduSender(
99
137
  request.close()
100
138
  byteArrayOf()
101
139
  } else {
102
- val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true){
140
+ val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true) {
103
141
  val buffer = ByteArray(USB_MTU)
104
142
  val responseBuffer = ByteBuffer.allocate(USB_MTU)
105
143
 
@@ -107,8 +145,7 @@ internal class AndroidUsbApduSender(
107
145
  if (!queuingResult) {
108
146
  request.close()
109
147
  byteArrayOf()
110
- }
111
- else{
148
+ } else {
112
149
  usbConnection.requestWait()
113
150
  responseBuffer.rewind()
114
151
  responseBuffer.get(buffer, 0, responseBuffer.remaining())
@@ -129,5 +166,10 @@ internal class AndroidUsbApduSender(
129
166
  throw NoSuchElementException("No endpoint matching the predicate")
130
167
  }
131
168
 
132
- private fun generateChannelId(): ByteArray = Random.nextInt(0, until = Int.MAX_VALUE).to2BytesArray()
169
+ private fun generateChannelId(): ByteArray =
170
+ Random.nextInt(0, until = Int.MAX_VALUE).to2BytesArray()
171
+
172
+ private data object SendApduTimeoutException: Exception() {
173
+ private fun readResolve(): Any = SendApduTimeoutException
174
+ }
133
175
  }
@@ -25,6 +25,9 @@ internal fun ProductId.toLedgerDevice(): LedgerDevice? =
25
25
  this.id.isLedgerDeviceProductId(LedgerDevice.Flex) -> {
26
26
  LedgerDevice.Flex
27
27
  }
28
+ this.id.isLedgerDeviceProductId(LedgerDevice.Apex) -> {
29
+ LedgerDevice.Apex
30
+ }
28
31
  else -> {
29
32
  null
30
33
  }
@@ -6,8 +6,9 @@
6
6
  package com.ledger.devicesdk.shared.androidMainInternal.transport.deviceconnection
7
7
 
8
8
  import com.ledger.devicesdk.shared.api.apdu.SendApduResult
9
+ import kotlin.time.Duration
9
10
 
10
11
  internal interface DeviceApduSender<Dependencies> {
11
- suspend fun send(apdu: ByteArray): SendApduResult
12
+ suspend fun send(apdu: ByteArray, abortTimeoutDuration: Duration): SendApduResult
12
13
  val dependencies: Dependencies
13
14
  }
@@ -24,9 +24,9 @@ internal class DeviceConnection<Dependencies>(
24
24
 
25
25
  init {
26
26
  stateMachine = DeviceConnectionStateMachine(
27
- sendApduFn = {
27
+ sendApduFn = { apdu, abortTimeoutDuration ->
28
28
  coroutineScope.launch {
29
- val res = deviceApduSender.send(it)
29
+ val res = deviceApduSender.send(apdu, abortTimeoutDuration)
30
30
  handleApduResult(res)
31
31
  }
32
32
  },
@@ -66,12 +66,13 @@ internal class DeviceConnection<Dependencies>(
66
66
  stateMachine.handleDeviceDisconnected()
67
67
  }
68
68
 
69
- public suspend fun requestSendApdu(apdu: ByteArray): SendApduResult =
69
+ public suspend fun requestSendApdu(apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: Duration): SendApduResult =
70
70
  suspendCoroutine { cont ->
71
71
  stateMachine.requestSendApdu(
72
72
  DeviceConnectionStateMachine.SendApduRequestContent(
73
73
  apdu = apdu,
74
- triggersDisconnection = apduTriggersDisconnection(apdu),
74
+ triggersDisconnection = apduTriggersDisconnection(apdu) || triggersDisconnection,
75
+ abortTimeoutDuration = abortTimeoutDuration,
75
76
  resultCallback = cont::resume
76
77
  )
77
78
  )
@@ -12,7 +12,7 @@ import kotlinx.coroutines.launch
12
12
  import kotlin.time.Duration
13
13
 
14
14
  internal class DeviceConnectionStateMachine(
15
- private val sendApduFn: (apdu: ByteArray) -> Unit,
15
+ private val sendApduFn: (apdu: ByteArray, abortTimeoutDuration: Duration) -> Unit,
16
16
  private val onTerminated: () -> Unit,
17
17
  private val isFatalSendApduFailure: (SendApduResult.Failure) -> Boolean,
18
18
  private val reconnectionTimeoutDuration: Duration,
@@ -29,7 +29,7 @@ internal class DeviceConnectionStateMachine(
29
29
  when (newState) {
30
30
  is State.Connected -> {}
31
31
  is State.SendingApdu -> {
32
- sendApduFn(newState.requestContent.apdu)
32
+ sendApduFn(newState.requestContent.apdu, newState.requestContent.abortTimeoutDuration)
33
33
  }
34
34
 
35
35
  is State.WaitingForReconnection -> {
@@ -280,6 +280,7 @@ internal class DeviceConnectionStateMachine(
280
280
  data class SendApduRequestContent(
281
281
  val apdu: ByteArray,
282
282
  val triggersDisconnection: Boolean,
283
+ val abortTimeoutDuration: Duration,
283
284
  val resultCallback: (SendApduResult) -> Unit
284
285
  )
285
286
 
@@ -44,4 +44,8 @@ public sealed class SendApduFailureReason {
44
44
  public data object DeviceDisconnected : SendApduFailureReason()
45
45
 
46
46
  public data object Unknown : SendApduFailureReason()
47
+
48
+ public data object AbortTimeout : SendApduFailureReason()
49
+
50
+ public data object EmptyResponse : SendApduFailureReason()
47
51
  }
@@ -5,6 +5,21 @@ public sealed class LedgerDevice(
5
5
  public val usbInfo: UsbInfo,
6
6
  public val bleInformation: BleInformation? = null,
7
7
  ) {
8
+ public data object Apex :
9
+ LedgerDevice(
10
+ name = "Ledger Apex",
11
+ usbInfo = UsbInfo(LEDGER_USB_VENDOR_ID, "0x80", "0x0008"),
12
+ bleInformation =
13
+ BleInformation(
14
+ serviceUuid = "13d63400-2c97-6004-0000-4c6564676572",
15
+ notifyCharacteristicUuid =
16
+ "13d63400-2c97-6004-0001-4c6564676572",
17
+ writeWithResponseCharacteristicUuid =
18
+ "13d63400-2c97-6004-0002-4c6564676572",
19
+ writeWithoutResponseCharacteristicUuid =
20
+ "13d63400-2c97-6004-0003-4c6564676572",
21
+ ),
22
+ )
8
23
  public data object Flex :
9
24
  LedgerDevice(
10
25
  name = "Ledger Flex",
@@ -77,6 +92,7 @@ public sealed class LedgerDevice(
77
92
  add(NanoX)
78
93
  add(NanoSPlus)
79
94
  add(NanoS)
95
+ add(Apex)
80
96
  }
81
97
 
82
98
  public fun getAllDevices(): List<LedgerDevice> {
@@ -9,5 +9,5 @@ internal data class InternalConnectedDevice(
9
9
  val name: String,
10
10
  val ledgerDevice: LedgerDevice,
11
11
  val connectivity: ConnectivityType,
12
- val sendApduFn: suspend (ByteArray) -> SendApduResult,
12
+ val sendApduFn: suspend (apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: kotlin.time.Duration) -> SendApduResult,
13
13
  )