@ledgerhq/device-transport-kit-react-native-hid 0.0.0-rn-hid-fixed-device-id-20250521115949 → 0.0.0-rn-hid-improvements-20250522101701

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.
@@ -172,6 +172,10 @@ class TransportHidModule(
172
172
  @ReactMethod()
173
173
  fun connectDevice(uid: String, promise: Promise) {
174
174
  val device = discoveryDevices.firstOrNull { it.uid == uid }
175
+ if (device == null) {
176
+ promise.reject(Exception("[TransportHidModule][connectDevice] Device not found"))
177
+ return
178
+ }
175
179
 
176
180
  coroutineScope.launch {
177
181
  try {
@@ -17,7 +17,6 @@ import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.toLedgerUsbDe
17
17
  import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.toScannedDevice
18
18
  import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.toUsbDevices
19
19
  import com.ledger.devicesdk.shared.androidMainInternal.transport.deviceconnection.DeviceConnection
20
- import com.ledger.devicesdk.shared.api.discovery.ConnectivityType
21
20
  import com.ledger.devicesdk.shared.api.discovery.DiscoveryDevice
22
21
  import com.ledger.devicesdk.shared.internal.connection.InternalConnectedDevice
23
22
  import com.ledger.devicesdk.shared.internal.connection.InternalConnectionResult
@@ -124,9 +123,10 @@ internal class DefaultAndroidUsbTransport(
124
123
  "Device disconnected (sessionId=${deviceConnection.sessionId})"
125
124
  )
126
125
  )
127
- deviceConnection.handleDeviceDisconnected()
128
126
  usbConnections.remove(key)
129
127
  usbConnectionsPendingReconnection.add(deviceConnection)
128
+ deviceConnection.handleDeviceDisconnected()
129
+ (deviceConnection.getApduSender() as AndroidUsbApduSender).release()
130
130
  }
131
131
  }
132
132
  }
@@ -274,7 +274,7 @@ internal class DefaultAndroidUsbTransport(
274
274
  }
275
275
  }
276
276
 
277
- override suspend fun connect(discoveryDevice: DiscoveryDevice?): InternalConnectionResult {
277
+ override suspend fun connect(discoveryDevice: DiscoveryDevice): InternalConnectionResult {
278
278
 
279
279
  loggerService.log(
280
280
  buildSimpleDebugLogInfo(
@@ -286,13 +286,13 @@ internal class DefaultAndroidUsbTransport(
286
286
  val usbDevices = usbManager.deviceList.values
287
287
 
288
288
  var usbDevice: UsbDevice? =
289
- usbDevices.firstOrNull { it.deviceId == discoveryDevice?.uid?.toInt() }
289
+ usbDevices.firstOrNull { it.deviceId == discoveryDevice.uid.toInt() }
290
290
 
291
291
  if (usbDevice == null) {
292
292
  // This is useful for LL during the OS update
293
293
  loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "[connect] No device found with matching id, looking for device with matching model"))
294
294
  usbDevice =
295
- usbDevices.firstOrNull { it.toLedgerUsbDevice() != null }
295
+ usbDevices.firstOrNull { it.toLedgerUsbDevice()?.ledgerDevice == discoveryDevice.ledgerDevice }
296
296
  } else {
297
297
  loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "[connect] Found device with matching id"))
298
298
  }
@@ -326,9 +326,10 @@ internal class DefaultAndroidUsbTransport(
326
326
  val deviceConnection = DeviceConnection(
327
327
  sessionId = sessionId,
328
328
  deviceApduSender = apduSender,
329
- isFatalSendApduFailure = { false }, // TODO: refine this
329
+ isFatalSendApduFailure = { false },
330
330
  reconnectionTimeoutDuration = 5.seconds,
331
331
  onTerminated = {
332
+ (it.getApduSender() as AndroidUsbApduSender).release()
332
333
  usbConnections.remove(sessionId)
333
334
  usbConnectionsPendingReconnection.remove(it)
334
335
  eventDispatcher.dispatch(TransportEvent.DeviceConnectionLost(sessionId))
@@ -340,9 +341,9 @@ internal class DefaultAndroidUsbTransport(
340
341
  val connectedDevice =
341
342
  InternalConnectedDevice(
342
343
  sessionId,
343
- ledgerUsbDevice.name,
344
- ledgerUsbDevice.ledgerDevice,
345
- ConnectivityType.Usb,
344
+ discoveryDevice.name,
345
+ discoveryDevice.ledgerDevice,
346
+ discoveryDevice.connectivityType,
346
347
  sendApduFn = { apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: Duration ->
347
348
  deviceConnection.requestSendApdu(apdu, triggersDisconnection, abortTimeoutDuration)
348
349
  }
@@ -22,7 +22,6 @@ import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleErrorLogIn
22
22
  import com.ledger.devicesdk.shared.internal.transport.framer.FramerService
23
23
  import com.ledger.devicesdk.shared.internal.transport.framer.to2BytesArray
24
24
  import kotlinx.coroutines.CoroutineDispatcher
25
- import kotlinx.coroutines.cancel
26
25
  import kotlinx.coroutines.delay
27
26
  import kotlinx.coroutines.launch
28
27
  import kotlinx.coroutines.withContext
@@ -36,7 +35,7 @@ private const val DEFAULT_USB_INTERFACE = 0
36
35
 
37
36
  internal class AndroidUsbApduSender(
38
37
  override val dependencies: Dependencies,
39
- private val usbManager: UsbManager,
38
+ usbManager: UsbManager,
40
39
  private val framerService: FramerService,
41
40
  private val request: UsbRequest,
42
41
  private val ioDispatcher: CoroutineDispatcher,
@@ -47,49 +46,48 @@ internal class AndroidUsbApduSender(
47
46
  val ledgerUsbDevice: LedgerUsbDevice,
48
47
  )
49
48
 
49
+ private val usbDevice = dependencies.usbDevice
50
+ private val usbInterface = usbDevice.getInterface(DEFAULT_USB_INTERFACE)
51
+ private val androidToUsbEndpoint =
52
+ usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_OUT }
53
+ private val usbToAndroidEndpoint =
54
+ usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_IN }
55
+ private val usbConnection = usbManager.openDevice(usbDevice)
56
+ .apply { claimInterface(usbInterface, true) }
57
+
58
+
59
+ fun release() {
60
+ usbConnection.releaseInterface(usbInterface)
61
+ usbConnection.close()
62
+ }
63
+
50
64
  override suspend fun send(apdu: ByteArray, abortTimeoutDuration: Duration): SendApduResult =
51
65
  try {
52
- val usbDevice = dependencies.usbDevice
53
66
  withContext(context = ioDispatcher) {
54
- val usbInterface = usbDevice.getInterface(DEFAULT_USB_INTERFACE)
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
67
 
62
68
  val timeoutJob = launch {
63
69
  delay(abortTimeoutDuration)
64
- usbConnection.releaseInterface(usbInterface)
65
- usbConnection.close()
66
70
  throw SendApduTimeoutException
67
71
  }
68
72
 
69
- try {
73
+ transmitApdu(
74
+ usbConnection = usbConnection,
75
+ androidToUsbEndpoint = androidToUsbEndpoint,
76
+ rawApdu = apdu,
77
+ )
70
78
 
71
- transmitApdu(
79
+ val apduResponse =
80
+ receiveApdu(
72
81
  usbConnection = usbConnection,
73
- androidToUsbEndpoint = androidToUsbEndpoint,
74
- rawApdu = apdu,
82
+ usbToAndroidEndpoint = usbToAndroidEndpoint,
75
83
  )
84
+ timeoutJob.cancel()
76
85
 
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
- }
87
-
88
- return@withContext SendApduResult.Success(apdu = apduResponse)
89
- } finally {
90
- usbConnection.releaseInterface(usbInterface)
91
- usbConnection.close()
86
+ if (apduResponse.isEmpty()) {
87
+ return@withContext SendApduResult.Failure(reason = SendApduFailureReason.EmptyResponse)
92
88
  }
89
+
90
+ return@withContext SendApduResult.Success(apdu = apduResponse)
93
91
  }
94
92
  } catch (e: SendApduTimeoutException) {
95
93
  loggerService.log(
@@ -132,8 +130,8 @@ internal class AndroidUsbApduSender(
132
130
  private fun receiveApdu(
133
131
  usbConnection: UsbDeviceConnection,
134
132
  usbToAndroidEndpoint: UsbEndpoint,
135
- ): ByteArray =
136
- if (!request.initialize(usbConnection, usbToAndroidEndpoint)) {
133
+ ): ByteArray {
134
+ return if (!request.initialize(usbConnection, usbToAndroidEndpoint)) {
137
135
  request.close()
138
136
  byteArrayOf()
139
137
  } else {
@@ -154,6 +152,7 @@ internal class AndroidUsbApduSender(
154
152
  }
155
153
  framerService.deserialize(mtu = USB_MTU, frames)
156
154
  }
155
+ }
157
156
 
158
157
  private fun UsbInterface.firstEndpointOrThrow(predicate: (Int) -> Boolean): UsbEndpoint {
159
158
  for (endp in 0..this.endpointCount) {
@@ -169,7 +168,7 @@ internal class AndroidUsbApduSender(
169
168
  private fun generateChannelId(): ByteArray =
170
169
  Random.nextInt(0, until = Int.MAX_VALUE).to2BytesArray()
171
170
 
172
- private data object SendApduTimeoutException: Exception() {
171
+ private data object SendApduTimeoutException : Exception() {
173
172
  private fun readResolve(): Any = SendApduTimeoutException
174
173
  }
175
174
  }
@@ -14,7 +14,8 @@ internal interface Transport {
14
14
 
15
15
  fun stopScan()
16
16
 
17
- suspend fun connect(discoveryDevice: DiscoveryDevice?): InternalConnectionResult
17
+ // TODO change by Flow<ConnectedDeviceState> or add observe device connection for listening device state flow through?
18
+ suspend fun connect(discoveryDevice: DiscoveryDevice): InternalConnectionResult
18
19
 
19
20
  suspend fun disconnect(deviceId: String)
20
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ledgerhq/device-transport-kit-react-native-hid",
3
- "version": "0.0.0-rn-hid-fixed-device-id-20250521115949",
3
+ "version": "0.0.0-rn-hid-improvements-20250522101701",
4
4
  "license": "Apache-2.0",
5
5
  "private": false,
6
6
  "react-native": "src/index.ts",
@@ -35,17 +35,17 @@
35
35
  "@types/uuid": "^10.0.0",
36
36
  "react-native": "0.76.6",
37
37
  "rxjs": "^7.8.2",
38
- "@ledgerhq/device-management-kit": "0.0.0-rn-hid-fixed-device-id-20250521115949",
38
+ "@ledgerhq/device-management-kit": "0.0.0-rn-hid-improvements-20250522101701",
39
39
  "@ledgerhq/eslint-config-dsdk": "0.0.2",
40
40
  "@ledgerhq/ldmk-tool": "0.0.1",
41
41
  "@ledgerhq/prettier-config-dsdk": "0.0.2",
42
- "@ledgerhq/tsconfig-dsdk": "1.0.1",
43
- "@ledgerhq/vitest-config-dmk": "0.0.0"
42
+ "@ledgerhq/vitest-config-dmk": "0.0.0",
43
+ "@ledgerhq/tsconfig-dsdk": "1.0.1"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "react-native": ">0.74.1",
47
47
  "rxjs": "^7.8.2",
48
- "@ledgerhq/device-management-kit": "0.0.0-rn-hid-fixed-device-id-20250521115949"
48
+ "@ledgerhq/device-management-kit": "0.0.0-rn-hid-improvements-20250522101701"
49
49
  },
50
50
  "scripts": {
51
51
  "prebuild": "rimraf lib",