@ledgerhq/device-transport-kit-react-native-hid 0.0.0-rn-hid-20250221115747 → 0.0.0-rnble-transport-20250422084848

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 (28) hide show
  1. package/README.md +1 -1
  2. package/android/build.gradle +3 -1
  3. package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidModule.kt +3 -3
  4. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/DefaultAndroidUsbTransport.kt +16 -22
  5. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/controller/UsbPermissionReceiver.kt +1 -1
  6. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/utils/UsbDeviceMapper.kt +27 -36
  7. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/UsbConst.android.kt +1 -1
  8. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnection.kt +6 -7
  9. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachine.kt +5 -2
  10. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/device/LedgerDevice.kt +64 -49
  11. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/device/UsbInfo.kt +4 -3
  12. package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachineTest.kt +713 -0
  13. package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionTest.kt +218 -0
  14. package/lib/cjs/api/bridge/NativeTransportModule.js +1 -1
  15. package/lib/cjs/api/bridge/NativeTransportModule.js.map +1 -1
  16. package/lib/cjs/package.json +7 -2
  17. package/lib/esm/api/bridge/NativeTransportModule.js +1 -1
  18. package/lib/esm/api/bridge/NativeTransportModule.js.map +1 -1
  19. package/lib/esm/package.json +7 -2
  20. package/lib/types/api/bridge/NativeTransportModule.d.ts.map +1 -1
  21. package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
  22. package/package.json +10 -5
  23. package/android/.settings/org.eclipse.buildship.core.prefs +0 -13
  24. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  25. package/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  26. package/android/gradlew +0 -252
  27. package/android/gradlew.bat +0 -94
  28. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/DeviceAction.kt +0 -23
@@ -0,0 +1,218 @@
1
+ package com.ledger.devicesdk.shared.androidMainInternal.transport.deviceconnection
2
+
3
+ import com.ledger.devicesdk.shared.api.apdu.SendApduResult
4
+ import com.ledger.devicesdk.shared.internal.service.logger.LogInfo
5
+ import com.ledger.devicesdk.shared.internal.service.logger.LoggerService
6
+ import kotlinx.coroutines.ExperimentalCoroutinesApi
7
+ import kotlinx.coroutines.async
8
+ import kotlinx.coroutines.test.StandardTestDispatcher
9
+ import kotlinx.coroutines.test.advanceTimeBy
10
+ import kotlinx.coroutines.test.runTest
11
+ import kotlin.time.Duration.Companion.seconds
12
+ import kotlin.test.Test
13
+ import org.junit.Assert.*
14
+ import kotlin.time.Duration.Companion.milliseconds
15
+
16
+ /**
17
+ * Those tests focus mainly on calling all public methods, and ensuring that the correct apduSender
18
+ * is used after reconnections.
19
+ * For full coverage of the possible scenarios, check the unit tests of DeviceConnectionStateMachine.
20
+ */
21
+ class DeviceConnectionTest {
22
+ @Test
23
+ fun `GIVEN a device connection with an APDU sender WHEN requestSendApdu is called THEN the apduSender is used and the result is obtained`() = runTest {
24
+ val apduSender = MockedApduSender()
25
+
26
+ val deviceConnection = DeviceConnection(
27
+ sessionId = "mockId",
28
+ deviceApduSender = apduSender,
29
+ onTerminated = { },
30
+ isFatalSendApduFailure = { false },
31
+ reconnectionTimeoutDuration = 5.seconds,
32
+ loggerService = FakeLoggerService(),
33
+ coroutineDispatcher = StandardTestDispatcher(testScheduler),
34
+ )
35
+
36
+ // Request sending an APDU
37
+ deviceConnection.requestSendApdu(mockedApdu)
38
+
39
+ // Send APDU should have been called once with the correct apdu
40
+ assertEquals(1, apduSender.sendCalls.size)
41
+ assertArrayEquals(mockedApdu, apduSender.sendCalls[0])
42
+ }
43
+
44
+ @Test
45
+ fun `GIVEN a device connection with an initial APDU sender and an APDU that triggers disconnection WHEN requestSendApdu is called and a reconnection occurs THEN the second apduSender is used and the result is obtained`() =
46
+ runTest {
47
+ apdusTriggeringDisconnection.forEach { apduTriggeringDisconnection ->
48
+ val apduSender1 = MockedApduSender()
49
+ apduSender1.nextResult = mockedSuccessApduResult
50
+
51
+ val deviceConnection = DeviceConnection(
52
+ sessionId = "mockId",
53
+ deviceApduSender = apduSender1,
54
+ onTerminated = { },
55
+ isFatalSendApduFailure = { false },
56
+ reconnectionTimeoutDuration = 5.seconds,
57
+ loggerService = FakeLoggerService(),
58
+ coroutineDispatcher = StandardTestDispatcher(testScheduler),
59
+ )
60
+
61
+ // Request sending an apdu
62
+ val result1 = deviceConnection.requestSendApdu(apduTriggeringDisconnection)
63
+
64
+ // apduSender1.sendApdu should have been called once with the correct apdu
65
+ assertEquals(1, apduSender1.sendCalls.size)
66
+ assertArrayEquals(apduTriggeringDisconnection, apduSender1.sendCalls[0])
67
+
68
+ // The result should have been obtained
69
+ assertEquals(mockedSuccessApduResult, result1)
70
+
71
+ // Request sending a second apdu
72
+ val result2 = async {
73
+ deviceConnection.requestSendApdu(mockedApdu)
74
+ }
75
+
76
+ // apduSender1.sendApdu shouldn't have been called again
77
+ assertEquals(1, apduSender1.sendCalls.size)
78
+
79
+ // Simulate reconnection
80
+ val apduSender2 = MockedApduSender()
81
+ apduSender2.nextResult = mockedSuccessApduResult
82
+ deviceConnection.handleDeviceConnected(apduSender2)
83
+
84
+ // The result should have been obtained
85
+ assertEquals(mockedSuccessApduResult, result2.await())
86
+
87
+ // apduSender2.sendApdu should have been called once with the correct apdu
88
+ assertEquals(1, apduSender2.sendCalls.size)
89
+ assertArrayEquals(mockedApdu, apduSender2.sendCalls[0])
90
+ }
91
+ }
92
+
93
+ @OptIn(ExperimentalCoroutinesApi::class)
94
+ @Test
95
+ fun `GIVEN a device connection WHEN handleDeviceDisconnected is called and the timeout elapses THEN onTerminated is triggered`() =
96
+ runTest {
97
+ var terminated = false
98
+ val apduSender = MockedApduSender()
99
+
100
+ val deviceConnection = DeviceConnection(
101
+ sessionId = "mockId",
102
+ deviceApduSender = apduSender,
103
+ onTerminated = {
104
+ terminated = true
105
+ },
106
+ isFatalSendApduFailure = { false },
107
+ reconnectionTimeoutDuration = 5.seconds,
108
+ loggerService = FakeLoggerService(),
109
+ coroutineDispatcher = StandardTestDispatcher(testScheduler),
110
+ )
111
+
112
+ // Simulate disconnection
113
+ deviceConnection.handleDeviceDisconnected()
114
+
115
+ // Timeout
116
+ advanceTimeBy(5.seconds)
117
+ assertFalse(terminated)
118
+ advanceTimeBy(1.milliseconds)
119
+ assertTrue(terminated)
120
+ }
121
+
122
+ @Test
123
+ fun `GIVEN a disconnected device connection WHEN a device gets reconnected and an APDU is requested THEN the correct apduSender is used and the result is obtained`() =
124
+ runTest {
125
+ val apduSender1 = MockedApduSender()
126
+
127
+ val deviceConnection = DeviceConnection(
128
+ sessionId = "mockId",
129
+ deviceApduSender = apduSender1,
130
+ onTerminated = { },
131
+ isFatalSendApduFailure = { false },
132
+ reconnectionTimeoutDuration = 5.seconds,
133
+ loggerService = FakeLoggerService(),
134
+ coroutineDispatcher = StandardTestDispatcher(testScheduler),
135
+ )
136
+
137
+ // Simulate disconnection
138
+ deviceConnection.handleDeviceDisconnected()
139
+
140
+ // Request sending an APDU
141
+ val result = async {
142
+ deviceConnection.requestSendApdu(mockedApdu)
143
+ }
144
+
145
+ // Simulate reconnection
146
+ val apduSender2 = MockedApduSender()
147
+ deviceConnection.handleDeviceConnected(apduSender2)
148
+
149
+ // The result should have been obtained
150
+ assertEquals(mockedSuccessApduResult, result.await())
151
+
152
+ // apduSender1.sendApdu should not have been called
153
+ assertEquals(0, apduSender1.sendCalls.size)
154
+
155
+ // apduSender2.sendApdu should have been called once with the correct apdu
156
+ assertEquals(1, apduSender2.sendCalls.size)
157
+ assertArrayEquals(mockedApdu, apduSender2.sendCalls[0])
158
+ }
159
+
160
+ @Test
161
+ fun `GIVEN a device connection WHEN requestCloseConnection is called THEN onTerminated is triggered`() = runTest {
162
+ var terminated = false
163
+
164
+ val deviceConnection = DeviceConnection(
165
+ sessionId = "mockId",
166
+ deviceApduSender = MockedApduSender(),
167
+ onTerminated = {
168
+ terminated = true
169
+ },
170
+ isFatalSendApduFailure = { false },
171
+ reconnectionTimeoutDuration = 5.seconds,
172
+ loggerService = FakeLoggerService(),
173
+ coroutineDispatcher = StandardTestDispatcher(testScheduler),
174
+ )
175
+
176
+ // Request closing connection
177
+ deviceConnection.requestCloseConnection()
178
+
179
+ // onTerminated should have been called
180
+ assertTrue(terminated)
181
+ }
182
+
183
+ // Helpers
184
+ companion object {
185
+
186
+ val mockedApdu: ByteArray = byteArrayOf(0x01, 0x02)
187
+
188
+ val apdusTriggeringDisconnection: List<ByteArray> = listOf(
189
+ byteArrayOf(0xe0.toByte(), 0xd8.toByte(), 0x01), // Open app
190
+ byteArrayOf(0xe0.toByte(), 0xd8.toByte(), 0x01) // Close app
191
+ )
192
+
193
+ val mockedSuccessApduResult =
194
+ SendApduResult.Success(byteArrayOf(0x05, 0x06, 0x90.toByte(), 0x00))
195
+
196
+ class MockedApduSender() : DeviceApduSender<String> {
197
+ // can be set from the outside for easy mocking
198
+ var nextResult: SendApduResult = mockedSuccessApduResult
199
+
200
+ private val _sendCalls: MutableList<ByteArray> = mutableListOf()
201
+ // to easily check from the outside the apdus sent
202
+ val sendCalls: MutableList<ByteArray>
203
+ get() = _sendCalls
204
+
205
+ override suspend fun send(apdu: ByteArray): SendApduResult {
206
+ _sendCalls += apdu
207
+ return nextResult
208
+ }
209
+
210
+ override val dependencies: String
211
+ get() = ""
212
+ }
213
+
214
+ internal class FakeLoggerService : LoggerService {
215
+ override fun log(info: LogInfo) {}
216
+ }
217
+ }
218
+ }
@@ -1,2 +1,2 @@
1
- "use strict";var p=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var i=Object.getOwnPropertyNames;var d=Object.prototype.hasOwnProperty;var l=(e,o)=>{for(var r in o)p(e,r,{get:o[r],enumerable:!0})},n=(e,o,r,a)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of i(o))!d.call(e,t)&&t!==r&&p(e,t,{get:()=>o[t],enumerable:!(a=T(o,t))||a.enumerable});return e};var u=e=>n(p({},"__esModule",{value:!0}),e);var m={};l(m,{NativeTransportModule:()=>M});module.exports=u(m);var s=require("react-native");const M=s.NativeModules.RCTTransportHIDModule;0&&(module.exports={NativeTransportModule});
1
+ "use strict";var p=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var d=(e,o)=>{for(var r in o)p(e,r,{get:o[r],enumerable:!0})},l=(e,o,r,a)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of M(o))!T.call(e,t)&&t!==r&&p(e,t,{get:()=>o[t],enumerable:!(a=i(o,t))||a.enumerable});return e};var n=e=>l(p({},"__esModule",{value:!0}),e);var m={};d(m,{NativeTransportModule:()=>u});module.exports=n(m);var s=require("react-native");const u=s.NativeModules.LDMKTransportHIDModule;0&&(module.exports={NativeTransportModule});
2
2
  //# sourceMappingURL=NativeTransportModule.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/api/bridge/NativeTransportModule.ts"],
4
- "sourcesContent": ["import { NativeModules } from \"react-native\";\n\nimport type { NativeTransportModuleType } from \"./types\";\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\nexport const NativeTransportModule: NativeTransportModuleType =\n NativeModules[\"RCTTransportHIDModule\"];\n"],
4
+ "sourcesContent": ["import { NativeModules } from \"react-native\";\n\nimport type { NativeTransportModuleType } from \"./types\";\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\nexport const NativeTransportModule: NativeTransportModuleType =\n NativeModules[\"LDMKTransportHIDModule\"];\n"],
5
5
  "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,2BAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAA8B,wBAKvB,MAAMF,EACX,gBAAc",
6
6
  "names": ["NativeTransportModule_exports", "__export", "NativeTransportModule", "__toCommonJS", "import_react_native"]
7
7
  }
@@ -19,7 +19,12 @@
19
19
  },
20
20
  "files": [
21
21
  "./android",
22
- "./lib"
22
+ "./lib",
23
+ "!android/build",
24
+ "!android/gradle",
25
+ "!android/gradlew",
26
+ "!android/gradlew.bat",
27
+ "!android/local.properties"
23
28
  ],
24
29
  "scripts": {
25
30
  "prebuild": "rimraf lib",
@@ -55,7 +60,7 @@
55
60
  },
56
61
  "peerDependencies": {
57
62
  "@ledgerhq/device-management-kit": "workspace:*",
58
- "react-native": ">0.72",
63
+ "react-native": ">0.74.1",
59
64
  "rxjs": "^7.8.1"
60
65
  }
61
66
  }
@@ -1,2 +1,2 @@
1
- import{NativeModules as o}from"react-native";const t=o.RCTTransportHIDModule;export{t as NativeTransportModule};
1
+ import{NativeModules as o}from"react-native";const t=o.LDMKTransportHIDModule;export{t as NativeTransportModule};
2
2
  //# sourceMappingURL=NativeTransportModule.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/api/bridge/NativeTransportModule.ts"],
4
- "sourcesContent": ["import { NativeModules } from \"react-native\";\n\nimport type { NativeTransportModuleType } from \"./types\";\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\nexport const NativeTransportModule: NativeTransportModuleType =\n NativeModules[\"RCTTransportHIDModule\"];\n"],
4
+ "sourcesContent": ["import { NativeModules } from \"react-native\";\n\nimport type { NativeTransportModuleType } from \"./types\";\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\nexport const NativeTransportModule: NativeTransportModuleType =\n NativeModules[\"LDMKTransportHIDModule\"];\n"],
5
5
  "mappings": "AAAA,OAAS,iBAAAA,MAAqB,eAKvB,MAAMC,EACXD,EAAc",
6
6
  "names": ["NativeModules", "NativeTransportModule"]
7
7
  }
@@ -19,7 +19,12 @@
19
19
  },
20
20
  "files": [
21
21
  "./android",
22
- "./lib"
22
+ "./lib",
23
+ "!android/build",
24
+ "!android/gradle",
25
+ "!android/gradlew",
26
+ "!android/gradlew.bat",
27
+ "!android/local.properties"
23
28
  ],
24
29
  "scripts": {
25
30
  "prebuild": "rimraf lib",
@@ -55,7 +60,7 @@
55
60
  },
56
61
  "peerDependencies": {
57
62
  "@ledgerhq/device-management-kit": "workspace:*",
58
- "react-native": ">0.72",
63
+ "react-native": ">0.74.1",
59
64
  "rxjs": "^7.8.1"
60
65
  }
61
66
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NativeTransportModule.d.ts","sourceRoot":"","sources":["../../../../src/api/bridge/NativeTransportModule.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAGzD,eAAO,MAAM,qBAAqB,EAAE,yBACI,CAAC"}
1
+ {"version":3,"file":"NativeTransportModule.d.ts","sourceRoot":"","sources":["../../../../src/api/bridge/NativeTransportModule.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAGzD,eAAO,MAAM,qBAAqB,EAAE,yBACK,CAAC"}