@ledgerhq/live-common 26.0.0 → 26.1.0-next.1
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.
- package/README.md +1 -1
- package/lib/__tests__/hw/getDeviceInfo.js +152 -0
- package/lib/__tests__/hw/getDeviceInfo.js.map +1 -1
- package/lib/api/Manager.d.ts +2 -1
- package/lib/api/Manager.d.ts.map +1 -1
- package/lib/api/Manager.js +37 -2
- package/lib/api/Manager.js.map +1 -1
- package/lib/ble/hooks/useBleDevicePairing.d.ts +18 -0
- package/lib/ble/hooks/useBleDevicePairing.d.ts.map +1 -0
- package/lib/ble/hooks/useBleDevicePairing.js +57 -0
- package/lib/ble/hooks/useBleDevicePairing.js.map +1 -0
- package/lib/ble/hooks/useBleDevicePairing.test.d.ts +2 -0
- package/lib/ble/hooks/useBleDevicePairing.test.d.ts.map +1 -0
- package/lib/ble/hooks/useBleDevicePairing.test.js +159 -0
- package/lib/ble/hooks/useBleDevicePairing.test.js.map +1 -0
- package/lib/ble/hooks/useBleDevicesScanning.d.ts +25 -0
- package/lib/ble/hooks/useBleDevicesScanning.d.ts.map +1 -0
- package/lib/ble/hooks/useBleDevicesScanning.js +117 -0
- package/lib/ble/hooks/useBleDevicesScanning.js.map +1 -0
- package/lib/ble/hooks/useBleDevicesScanning.test.d.ts +2 -0
- package/lib/ble/hooks/useBleDevicesScanning.test.d.ts.map +1 -0
- package/lib/ble/hooks/useBleDevicesScanning.test.js +281 -0
- package/lib/ble/hooks/useBleDevicesScanning.test.js.map +1 -0
- package/lib/ble/types.d.ts +21 -0
- package/lib/ble/types.d.ts.map +1 -0
- package/lib/ble/types.js +3 -0
- package/lib/ble/types.js.map +1 -0
- package/lib/errors.d.ts +2 -0
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +4 -2
- package/lib/errors.js.map +1 -1
- package/lib/families/cardano/js-estimateMaxSpendable.d.ts.map +1 -1
- package/lib/families/cardano/js-estimateMaxSpendable.js +2 -0
- package/lib/families/cardano/js-estimateMaxSpendable.js.map +1 -1
- package/lib/families/cosmos/specs.js +2 -2
- package/lib/families/cosmos/specs.js.map +1 -1
- package/lib/families/elrond/specs.js +1 -1
- package/lib/families/elrond/specs.js.map +1 -1
- package/lib/families/polkadot/bridge.integration.test.js +3 -3
- package/lib/families/polkadot/bridge.integration.test.js.map +1 -1
- package/lib/families/polkadot/specs.d.ts.map +1 -1
- package/lib/families/polkadot/specs.js +4 -3
- package/lib/families/polkadot/specs.js.map +1 -1
- package/lib/featureFlags/provider.d.ts +5 -1
- package/lib/featureFlags/provider.d.ts.map +1 -1
- package/lib/featureFlags/provider.js +6 -4
- package/lib/featureFlags/provider.js.map +1 -1
- package/lib/featureFlags/useFeature.d.ts.map +1 -1
- package/lib/featureFlags/useFeature.js +3 -1
- package/lib/featureFlags/useFeature.js.map +1 -1
- package/lib/generated/account.d.ts.map +1 -1
- package/lib/generated/account.js.map +1 -1
- package/lib/generated/bridge/js.d.ts.map +1 -1
- package/lib/generated/bridge/js.js.map +1 -1
- package/lib/generated/bridge/mock.d.ts.map +1 -1
- package/lib/generated/bridge/mock.js.map +1 -1
- package/lib/generated/cli-transaction.d.ts.map +1 -1
- package/lib/generated/cli-transaction.js.map +1 -1
- package/lib/generated/deviceTransactionConfig.d.ts.map +1 -1
- package/lib/generated/deviceTransactionConfig.js.map +1 -1
- package/lib/generated/exchange.d.ts.map +1 -1
- package/lib/generated/exchange.js.map +1 -1
- package/lib/generated/hw-getAddress.d.ts.map +1 -1
- package/lib/generated/hw-getAddress.js.map +1 -1
- package/lib/generated/hw-signMessage.d.ts.map +1 -1
- package/lib/generated/hw-signMessage.js.map +1 -1
- package/lib/generated/mock.d.ts.map +1 -1
- package/lib/generated/mock.js.map +1 -1
- package/lib/generated/platformAdapter.d.ts.map +1 -1
- package/lib/generated/platformAdapter.js.map +1 -1
- package/lib/generated/presync.d.ts.map +1 -1
- package/lib/generated/presync.js.map +1 -1
- package/lib/generated/specs.d.ts.map +1 -1
- package/lib/generated/specs.js.map +1 -1
- package/lib/generated/speculos-deviceActions.d.ts.map +1 -1
- package/lib/generated/speculos-deviceActions.js.map +1 -1
- package/lib/generated/transaction.d.ts.map +1 -1
- package/lib/generated/transaction.js.map +1 -1
- package/lib/generated/types.d.ts.map +1 -1
- package/lib/hw/actions/installLanguage.d.ts +21 -0
- package/lib/hw/actions/installLanguage.d.ts.map +1 -0
- package/lib/hw/actions/installLanguage.js +260 -0
- package/lib/hw/actions/installLanguage.js.map +1 -0
- package/lib/hw/attemptToQuitApp.d.ts +10 -0
- package/lib/hw/attemptToQuitApp.d.ts.map +1 -0
- package/lib/hw/attemptToQuitApp.js +22 -0
- package/lib/hw/attemptToQuitApp.js.map +1 -0
- package/lib/hw/connectManager.d.ts +2 -5
- package/lib/hw/connectManager.d.ts.map +1 -1
- package/lib/hw/connectManager.js +2 -14
- package/lib/hw/connectManager.js.map +1 -1
- package/lib/hw/getDeviceInfo.d.ts.map +1 -1
- package/lib/hw/getDeviceInfo.js +6 -3
- package/lib/hw/getDeviceInfo.js.map +1 -1
- package/lib/hw/getVersion.d.ts +3 -3
- package/lib/hw/getVersion.d.ts.map +1 -1
- package/lib/hw/getVersion.js +53 -5
- package/lib/hw/getVersion.js.map +1 -1
- package/lib/hw/installLanguage.d.ts +17 -0
- package/lib/hw/installLanguage.d.ts.map +1 -0
- package/lib/hw/installLanguage.js +173 -0
- package/lib/hw/installLanguage.js.map +1 -0
- package/lib/hw/uninstallLanguage.d.ts +15 -0
- package/lib/hw/uninstallLanguage.d.ts.map +1 -0
- package/lib/hw/uninstallLanguage.js +121 -0
- package/lib/hw/uninstallLanguage.js.map +1 -0
- package/lib/manager/hooks.d.ts +2 -1
- package/lib/manager/hooks.d.ts.map +1 -1
- package/lib/manager/hooks.js +13 -1
- package/lib/manager/hooks.js.map +1 -1
- package/lib/manager/index.d.ts +2 -1
- package/lib/manager/index.d.ts.map +1 -1
- package/lib/manager/index.js +16 -0
- package/lib/manager/index.js.map +1 -1
- package/lib/manager/localization.d.ts +3 -0
- package/lib/manager/localization.d.ts.map +1 -0
- package/lib/manager/localization.js +15 -0
- package/lib/manager/localization.js.map +1 -0
- package/lib/platform/react.d.ts.map +1 -1
- package/lib/platform/react.js +6 -3
- package/lib/platform/react.js.map +1 -1
- package/package.json +18 -18
- package/src/__tests__/hw/getDeviceInfo.ts +137 -0
- package/src/api/Manager.ts +50 -2
- package/src/ble/hooks/useBleDevicePairing.test.ts +91 -0
- package/src/ble/hooks/useBleDevicePairing.ts +51 -0
- package/src/ble/hooks/useBleDevicesScanning.test.ts +252 -0
- package/src/ble/hooks/useBleDevicesScanning.ts +147 -0
- package/src/ble/types.ts +42 -0
- package/src/errors.ts +9 -0
- package/src/families/cardano/js-estimateMaxSpendable.ts +2 -0
- package/src/families/cosmos/specs.ts +2 -2
- package/src/families/elrond/specs.ts +1 -1
- package/src/families/polkadot/bridge.integration.test.ts +3 -3
- package/src/families/polkadot/specs.ts +4 -3
- package/src/featureFlags/provider.tsx +11 -1
- package/src/featureFlags/useFeature.ts +6 -2
- package/src/generated/account.ts +0 -8
- package/src/generated/bridge/js.ts +0 -18
- package/src/generated/bridge/mock.ts +0 -8
- package/src/generated/cli-transaction.ts +0 -17
- package/src/generated/deviceTransactionConfig.ts +15 -31
- package/src/generated/exchange.ts +0 -5
- package/src/generated/hw-getAddress.ts +0 -18
- package/src/generated/hw-signMessage.ts +0 -3
- package/src/generated/mock.ts +0 -3
- package/src/generated/platformAdapter.ts +0 -4
- package/src/generated/presync.ts +0 -1
- package/src/generated/specs.ts +0 -17
- package/src/generated/speculos-deviceActions.ts +0 -17
- package/src/generated/transaction.ts +0 -18
- package/src/generated/types.ts +7 -4
- package/src/hw/actions/installLanguage.ts +343 -0
- package/src/hw/attemptToQuitApp.ts +32 -0
- package/src/hw/connectManager.ts +5 -26
- package/src/hw/getDeviceInfo.ts +6 -0
- package/src/hw/getVersion.ts +77 -3
- package/src/hw/installLanguage.ts +172 -0
- package/src/hw/uninstallLanguage.ts +127 -0
- package/src/manager/hooks.ts +21 -1
- package/src/manager/index.ts +16 -0
- package/src/manager/localization.ts +19 -0
- package/src/platform/react.ts +7 -3
|
@@ -515,3 +515,140 @@ test("Nano X BL >= 2", async () => {
|
|
|
515
515
|
pinValidated: true,
|
|
516
516
|
});
|
|
517
517
|
});
|
|
518
|
+
test("Nano X 1.2.4-6", async () => {
|
|
519
|
+
const t = await openTransportReplayer(
|
|
520
|
+
RecordStore.fromString(`
|
|
521
|
+
=> b001000000
|
|
522
|
+
<= 01054f4c4f5300072e322e342d36009000
|
|
523
|
+
=> e001000000
|
|
524
|
+
<= 3300000407312e322e342d3604a600000004322e31329000
|
|
525
|
+
`)
|
|
526
|
+
);
|
|
527
|
+
const res = await getDeviceInfo(t);
|
|
528
|
+
expect(res).toMatchObject({
|
|
529
|
+
version: "1.2.4-6",
|
|
530
|
+
mcuVersion: "2.12",
|
|
531
|
+
seVersion: "1.2.4-6",
|
|
532
|
+
majMin: "1.2",
|
|
533
|
+
providerName: null,
|
|
534
|
+
targetId: 855638020,
|
|
535
|
+
seTargetId: 855638020,
|
|
536
|
+
isOSU: false,
|
|
537
|
+
isBootloader: false,
|
|
538
|
+
isRecoveryMode: false,
|
|
539
|
+
managerAllowed: false,
|
|
540
|
+
pinValidated: true,
|
|
541
|
+
onboarded: true,
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
test("Nano X 2.0.0", async () => {
|
|
545
|
+
const t = await openTransportReplayer(
|
|
546
|
+
RecordStore.fromString(`
|
|
547
|
+
=> b001000000
|
|
548
|
+
<= 0105424f4c4f5305322e302e309000
|
|
549
|
+
=> e001000000
|
|
550
|
+
<= 3300000405322e302e3004a600000004322e323804312e313601009000
|
|
551
|
+
`)
|
|
552
|
+
);
|
|
553
|
+
const res = await getDeviceInfo(t);
|
|
554
|
+
expect(res).toMatchObject({
|
|
555
|
+
version: "2.0.0",
|
|
556
|
+
mcuVersion: "2.28",
|
|
557
|
+
seVersion: "2.0.0",
|
|
558
|
+
majMin: "2.0",
|
|
559
|
+
providerName: null,
|
|
560
|
+
targetId: 855638020,
|
|
561
|
+
seTargetId: 855638020,
|
|
562
|
+
isOSU: false,
|
|
563
|
+
isBootloader: false,
|
|
564
|
+
isRecoveryMode: false,
|
|
565
|
+
managerAllowed: false,
|
|
566
|
+
pinValidated: true,
|
|
567
|
+
onboarded: true,
|
|
568
|
+
bootloaderVersion: "1.16",
|
|
569
|
+
hardwareVersion: 0,
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
test("Nano X 2.1.0-lo2", async () => {
|
|
573
|
+
const t = await openTransportReplayer(
|
|
574
|
+
RecordStore.fromString(`
|
|
575
|
+
=> b001000000
|
|
576
|
+
<= 0105424f4c4f5309322e312e302d6c6f329000
|
|
577
|
+
=> e001000000
|
|
578
|
+
<= 3300000409322e312e302d6c6f3204e600000004322e333004312e3136010001019000
|
|
579
|
+
`)
|
|
580
|
+
);
|
|
581
|
+
const res = await getDeviceInfo(t);
|
|
582
|
+
expect(res).toMatchObject({
|
|
583
|
+
version: "2.1.0-lo2",
|
|
584
|
+
mcuVersion: "2.30",
|
|
585
|
+
seVersion: "2.1.0-lo2",
|
|
586
|
+
majMin: "2.1",
|
|
587
|
+
providerName: null,
|
|
588
|
+
targetId: 855638020,
|
|
589
|
+
seTargetId: 855638020,
|
|
590
|
+
isOSU: false,
|
|
591
|
+
isBootloader: false,
|
|
592
|
+
isRecoveryMode: false,
|
|
593
|
+
managerAllowed: false,
|
|
594
|
+
pinValidated: true,
|
|
595
|
+
onboarded: true,
|
|
596
|
+
bootloaderVersion: "1.16",
|
|
597
|
+
hardwareVersion: 0,
|
|
598
|
+
languageId: 1,
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
test("Nano S 2.1.0", async () => {
|
|
602
|
+
const t = await openTransportReplayer(
|
|
603
|
+
RecordStore.fromString(`
|
|
604
|
+
=> b001000000
|
|
605
|
+
<= 0105424f4c4f5305322e312e309000
|
|
606
|
+
=> e001000000
|
|
607
|
+
<= 3110000405322e312e3004a600000004312e313204302e31319000
|
|
608
|
+
`)
|
|
609
|
+
);
|
|
610
|
+
const res = await getDeviceInfo(t);
|
|
611
|
+
expect(res).toMatchObject({
|
|
612
|
+
version: "2.1.0",
|
|
613
|
+
mcuVersion: "1.12",
|
|
614
|
+
seVersion: "2.1.0",
|
|
615
|
+
majMin: "2.1",
|
|
616
|
+
providerName: null,
|
|
617
|
+
targetId: 823132164,
|
|
618
|
+
seTargetId: 823132164,
|
|
619
|
+
isOSU: false,
|
|
620
|
+
isBootloader: false,
|
|
621
|
+
isRecoveryMode: false,
|
|
622
|
+
managerAllowed: false,
|
|
623
|
+
pinValidated: true,
|
|
624
|
+
onboarded: true,
|
|
625
|
+
bootloaderVersion: "0.11",
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
test("Nano S+ 1.0.2", async () => {
|
|
629
|
+
const t = await openTransportReplayer(
|
|
630
|
+
RecordStore.fromString(`
|
|
631
|
+
=> b001000000
|
|
632
|
+
<= 0105424f4c4f5305312e302e329000
|
|
633
|
+
=> e001000000
|
|
634
|
+
<= 3310000405312e302e3204a600000004342e303204332e31329000
|
|
635
|
+
`)
|
|
636
|
+
);
|
|
637
|
+
const res = await getDeviceInfo(t);
|
|
638
|
+
expect(res).toMatchObject({
|
|
639
|
+
version: "1.0.2",
|
|
640
|
+
mcuVersion: "4.02",
|
|
641
|
+
seVersion: "1.0.2",
|
|
642
|
+
majMin: "1.0",
|
|
643
|
+
providerName: null,
|
|
644
|
+
targetId: 856686596,
|
|
645
|
+
seTargetId: 856686596,
|
|
646
|
+
isOSU: false,
|
|
647
|
+
isBootloader: false,
|
|
648
|
+
isRecoveryMode: false,
|
|
649
|
+
managerAllowed: false,
|
|
650
|
+
pinValidated: true,
|
|
651
|
+
onboarded: true,
|
|
652
|
+
bootloaderVersion: "3.12",
|
|
653
|
+
});
|
|
654
|
+
});
|
package/src/api/Manager.ts
CHANGED
|
@@ -30,6 +30,8 @@ import { getEnv } from "../env";
|
|
|
30
30
|
import { makeLRUCache } from "../cache";
|
|
31
31
|
import { getUserHashes } from "../user";
|
|
32
32
|
import {
|
|
33
|
+
LanguagePackage,
|
|
34
|
+
LanguagePackageResponse,
|
|
33
35
|
Application,
|
|
34
36
|
ApplicationVersion,
|
|
35
37
|
Category,
|
|
@@ -41,6 +43,7 @@ import {
|
|
|
41
43
|
OsuFirmware,
|
|
42
44
|
SocketEvent,
|
|
43
45
|
} from "@ledgerhq/types-live";
|
|
46
|
+
import { getProviderId } from "../manager/provider";
|
|
44
47
|
|
|
45
48
|
declare global {
|
|
46
49
|
namespace NodeJS {
|
|
@@ -205,6 +208,50 @@ const findBestMCU = (compatibleMCU: McuVersion[]): McuVersion | undefined => {
|
|
|
205
208
|
return best;
|
|
206
209
|
};
|
|
207
210
|
|
|
211
|
+
const getLanguagePackagesForDevice = async (
|
|
212
|
+
deviceInfo: DeviceInfo
|
|
213
|
+
): Promise<LanguagePackage[]> => {
|
|
214
|
+
const deviceVersion = await getDeviceVersion(
|
|
215
|
+
deviceInfo.targetId,
|
|
216
|
+
getProviderId(deviceInfo)
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const seFirmwareVersion = await getCurrentFirmware({
|
|
220
|
+
version: deviceInfo.version,
|
|
221
|
+
deviceId: deviceVersion.id,
|
|
222
|
+
provider: getProviderId(deviceInfo),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const { data }: { data: LanguagePackageResponse[] } = await network({
|
|
226
|
+
method: "GET",
|
|
227
|
+
url: URL.format({
|
|
228
|
+
pathname: `${getEnv("MANAGER_API_BASE")}/language-package`,
|
|
229
|
+
query: {
|
|
230
|
+
livecommonversion,
|
|
231
|
+
},
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const allPackages: LanguagePackage[] = data.reduce(
|
|
236
|
+
(acc, response) => [
|
|
237
|
+
...acc,
|
|
238
|
+
...response.language_package_version.map((p) => ({
|
|
239
|
+
...p,
|
|
240
|
+
language: response.language,
|
|
241
|
+
})),
|
|
242
|
+
],
|
|
243
|
+
[] as LanguagePackage[]
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const packages = allPackages.filter(
|
|
247
|
+
(pack) =>
|
|
248
|
+
pack.device_versions.includes(deviceVersion.id) &&
|
|
249
|
+
pack.se_firmware_final_versions.includes(seFirmwareVersion.id)
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return packages;
|
|
253
|
+
};
|
|
254
|
+
|
|
208
255
|
const getLatestFirmware: (arg0: {
|
|
209
256
|
current_se_firmware_final_version: Id;
|
|
210
257
|
device_version: Id;
|
|
@@ -340,8 +387,8 @@ const getDeviceVersion: (
|
|
|
340
387
|
target_id: targetId,
|
|
341
388
|
},
|
|
342
389
|
}).catch((error) => {
|
|
343
|
-
const status =
|
|
344
|
-
error && (error.status || (error.response && error.response.status));
|
|
390
|
+
const status =
|
|
391
|
+
error && (error.status || (error.response && error.response.status)); // FIXME LLD is doing error remapping already. we probably need to move the remapping in live-common
|
|
345
392
|
|
|
346
393
|
if (status === 404) {
|
|
347
394
|
throw new FirmwareNotRecognized(
|
|
@@ -528,6 +575,7 @@ const API = {
|
|
|
528
575
|
listInstalledApps,
|
|
529
576
|
listCategories,
|
|
530
577
|
getMcus,
|
|
578
|
+
getLanguagePackagesForDevice,
|
|
531
579
|
getLatestFirmware,
|
|
532
580
|
getCurrentOSU,
|
|
533
581
|
compatibleMCUForDeviceInfo,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { renderHook, act } from "@testing-library/react-hooks";
|
|
2
|
+
import { from } from "rxjs";
|
|
3
|
+
import { DeviceModelId } from "@ledgerhq/devices";
|
|
4
|
+
import Transport from "@ledgerhq/hw-transport";
|
|
5
|
+
import { withDevice } from "../../hw/deviceAccess";
|
|
6
|
+
import getVersion from "../../hw/getVersion";
|
|
7
|
+
import { useBleDevicePairing } from "./useBleDevicePairing";
|
|
8
|
+
|
|
9
|
+
jest.mock("../../hw/deviceAccess");
|
|
10
|
+
jest.mock("../../hw/getVersion");
|
|
11
|
+
jest.mock("@ledgerhq/hw-transport");
|
|
12
|
+
jest.useFakeTimers();
|
|
13
|
+
|
|
14
|
+
const mockedGetVersion = jest.mocked(getVersion);
|
|
15
|
+
const mockedWithDevice = jest.mocked(withDevice);
|
|
16
|
+
|
|
17
|
+
mockedWithDevice.mockReturnValue((job) => from(job(new Transport())));
|
|
18
|
+
|
|
19
|
+
const aFirmwareInfo = {
|
|
20
|
+
isBootloader: false,
|
|
21
|
+
rawVersion: "",
|
|
22
|
+
targetId: 0,
|
|
23
|
+
mcuVersion: "",
|
|
24
|
+
flags: Buffer.from([]),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const aDevice = {
|
|
28
|
+
deviceId: "DEVICE_ID_A",
|
|
29
|
+
deviceName: "DEVICE_NAME_A",
|
|
30
|
+
modelId: DeviceModelId.nanoFTS,
|
|
31
|
+
wired: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// FIXME: once we have our own definition of BleError class, this won't be needed
|
|
35
|
+
class BleError extends Error {
|
|
36
|
+
errorCode: number;
|
|
37
|
+
constructor(message: string, errorCode: number) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.errorCode = errorCode;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("useBleDevicePairing", () => {
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
mockedGetVersion.mockClear();
|
|
46
|
+
jest.clearAllTimers();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("When the request sent with the BLE transport to the device gets a success response", () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
mockedGetVersion.mockResolvedValue(aFirmwareInfo);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should inform the hook consumer that the device is paired", async () => {
|
|
55
|
+
const { result } = renderHook(() =>
|
|
56
|
+
useBleDevicePairing({
|
|
57
|
+
deviceId: aDevice.deviceId,
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
await act(async () => {
|
|
62
|
+
jest.advanceTimersByTime(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(result.current.isPaired).toBe(true);
|
|
66
|
+
expect(result.current.pairingError).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("When the request sent with the BLE transport is rejected with an error", () => {
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
const bleError = new BleError("An error during pairing", 201);
|
|
73
|
+
mockedGetVersion.mockRejectedValue(bleError);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should inform the hook consumer an error occurred", async () => {
|
|
77
|
+
const { result } = renderHook(() =>
|
|
78
|
+
useBleDevicePairing({
|
|
79
|
+
deviceId: aDevice.deviceId,
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
await act(async () => {
|
|
84
|
+
jest.advanceTimersByTime(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(result.current.isPaired).toBe(false);
|
|
88
|
+
expect(result.current.pairingError).not.toBeNull();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { from } from "rxjs";
|
|
3
|
+
import { first } from "rxjs/operators";
|
|
4
|
+
import type { FirmwareInfo } from "@ledgerhq/types-live";
|
|
5
|
+
import { withDevice } from "../../hw/deviceAccess";
|
|
6
|
+
import getVersion from "../../hw/getVersion";
|
|
7
|
+
import { BleError } from "../types";
|
|
8
|
+
|
|
9
|
+
export type useBleDevicePairingArgs = {
|
|
10
|
+
deviceId: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type PairingError = BleError | null;
|
|
14
|
+
|
|
15
|
+
export type useBleDevicePairingResult = {
|
|
16
|
+
isPaired: boolean;
|
|
17
|
+
pairingError: PairingError;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Triggers a BLE pairing with a device
|
|
22
|
+
* @param deviceId A BLE device id
|
|
23
|
+
* @returns An object containing:
|
|
24
|
+
* - isPaired: a boolean set to true if the device has been paired, false otherwise
|
|
25
|
+
* - pairingError: any PairingError that occurred, null otherwise
|
|
26
|
+
*/
|
|
27
|
+
export const useBleDevicePairing = ({
|
|
28
|
+
deviceId,
|
|
29
|
+
}: useBleDevicePairingArgs): useBleDevicePairingResult => {
|
|
30
|
+
const [isPaired, setIsPaired] = useState<boolean>(false);
|
|
31
|
+
const [pairingError, setPairingError] = useState<PairingError>(null);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const requestObservable = withDevice(deviceId)((t) =>
|
|
35
|
+
from(getVersion(t))
|
|
36
|
+
).pipe(first());
|
|
37
|
+
|
|
38
|
+
requestObservable.subscribe({
|
|
39
|
+
next: (_value: FirmwareInfo) => {
|
|
40
|
+
setIsPaired(true);
|
|
41
|
+
setPairingError(null);
|
|
42
|
+
},
|
|
43
|
+
error: (error: BleError) => {
|
|
44
|
+
setIsPaired(false);
|
|
45
|
+
setPairingError(error);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}, [deviceId]);
|
|
49
|
+
|
|
50
|
+
return { isPaired, pairingError };
|
|
51
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { renderHook, act } from "@testing-library/react-hooks";
|
|
2
|
+
import type {
|
|
3
|
+
Observer as TransportObserver,
|
|
4
|
+
Subscription as TransportSubscription,
|
|
5
|
+
DescriptorEvent,
|
|
6
|
+
} from "@ledgerhq/hw-transport";
|
|
7
|
+
import { DeviceModelId, getDeviceModel } from "@ledgerhq/devices";
|
|
8
|
+
import { useBleDevicesScanning } from "./useBleDevicesScanning";
|
|
9
|
+
import type { TransportBleDevice } from "../types";
|
|
10
|
+
|
|
11
|
+
jest.useFakeTimers();
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
14
|
+
const nanoXServiceUuid = getDeviceModel(DeviceModelId.nanoX).bluetoothSpec![0]
|
|
15
|
+
.serviceUuid;
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17
|
+
const nanoFTSServiceUuid = getDeviceModel(DeviceModelId.nanoFTS)
|
|
18
|
+
.bluetoothSpec![0].serviceUuid;
|
|
19
|
+
|
|
20
|
+
// Fake devices info we would get from the bluetooth transport listen
|
|
21
|
+
const aTransportBleDevice = (overrideProps?: Partial<TransportBleDevice>) => {
|
|
22
|
+
return {
|
|
23
|
+
id: "aBleNanoId",
|
|
24
|
+
name: "aBleNano",
|
|
25
|
+
localName: null,
|
|
26
|
+
rssi: 50,
|
|
27
|
+
mtu: 50,
|
|
28
|
+
serviceUUIDs: [nanoXServiceUuid],
|
|
29
|
+
...overrideProps,
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type bleTransportListenObserver = TransportObserver<
|
|
34
|
+
DescriptorEvent<TransportBleDevice | null>
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
// HOF creating a mocked Transport listen method to be given to useBleDevicesScanning
|
|
38
|
+
const setupMockBleTransportListen =
|
|
39
|
+
(mockEmitValuesByObserver: (observer: bleTransportListenObserver) => void) =>
|
|
40
|
+
(observer: bleTransportListenObserver): TransportSubscription => {
|
|
41
|
+
mockEmitValuesByObserver(observer);
|
|
42
|
+
|
|
43
|
+
const unsubscribe = () => {};
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
unsubscribe,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe("useBleDevicesScanning", () => {
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
jest.clearAllTimers();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("When several unique devices are found by the scanner", () => {
|
|
56
|
+
const deviceIdA = "ID_A";
|
|
57
|
+
const deviceIdB = "ID_B";
|
|
58
|
+
const deviceIdC = "ID_C";
|
|
59
|
+
|
|
60
|
+
const mockEmitValuesByObserver = (observer: bleTransportListenObserver) => {
|
|
61
|
+
observer.next({
|
|
62
|
+
type: "add",
|
|
63
|
+
descriptor: aTransportBleDevice({ id: deviceIdA }),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
observer.next({
|
|
68
|
+
type: "add",
|
|
69
|
+
descriptor: aTransportBleDevice({
|
|
70
|
+
id: deviceIdB,
|
|
71
|
+
serviceUUIDs: [nanoFTSServiceUuid],
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
}, 1000);
|
|
75
|
+
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
observer.next({
|
|
78
|
+
type: "add",
|
|
79
|
+
descriptor: aTransportBleDevice({ id: deviceIdC }),
|
|
80
|
+
});
|
|
81
|
+
}, 2000);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
describe("and when no filters are applied to the scanning", () => {
|
|
85
|
+
it("should update the list of scanned devices for each new scanned device", async () => {
|
|
86
|
+
const { result } = renderHook(() =>
|
|
87
|
+
useBleDevicesScanning({
|
|
88
|
+
bleTransportListen: setupMockBleTransportListen(
|
|
89
|
+
mockEmitValuesByObserver
|
|
90
|
+
),
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(result.current.scannedDevices).toHaveLength(1);
|
|
95
|
+
expect(result.current.scannedDevices[0].deviceId).toBe(deviceIdA);
|
|
96
|
+
// The model was correctly deduced from the ble spec
|
|
97
|
+
expect(result.current.scannedDevices[0].deviceModel.id).toBe(
|
|
98
|
+
DeviceModelId.nanoX
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
await act(async () => {
|
|
102
|
+
jest.advanceTimersByTime(1000);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(result.current.scannedDevices).toHaveLength(2);
|
|
106
|
+
expect(result.current.scannedDevices[1].deviceId).toBe(deviceIdB);
|
|
107
|
+
expect(result.current.scannedDevices[1].deviceModel.id).toBe(
|
|
108
|
+
DeviceModelId.nanoFTS
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("and when filterModelIds is not null", () => {
|
|
114
|
+
it("should filter the scanning result by the given model ids", async () => {
|
|
115
|
+
const { result } = renderHook(() =>
|
|
116
|
+
useBleDevicesScanning({
|
|
117
|
+
bleTransportListen: setupMockBleTransportListen(
|
|
118
|
+
mockEmitValuesByObserver
|
|
119
|
+
),
|
|
120
|
+
filterByModelIds: [DeviceModelId.nanoX],
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// The first scanned device was a nanoX
|
|
125
|
+
expect(result.current.scannedDevices).toHaveLength(1);
|
|
126
|
+
expect(result.current.scannedDevices[0].deviceId).toBe(deviceIdA);
|
|
127
|
+
// The model was correctly deduced from the ble spec
|
|
128
|
+
expect(result.current.scannedDevices[0].deviceModel.id).toBe(
|
|
129
|
+
DeviceModelId.nanoX
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
await act(async () => {
|
|
133
|
+
jest.advanceTimersByTime(1000);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// The second scanned device was a nanoFTS, and was filtered out
|
|
137
|
+
expect(result.current.scannedDevices).toHaveLength(1);
|
|
138
|
+
|
|
139
|
+
await act(async () => {
|
|
140
|
+
jest.advanceTimersByTime(1000);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// The third scanned device was a nanoX
|
|
144
|
+
expect(result.current.scannedDevices).toHaveLength(2);
|
|
145
|
+
expect(result.current.scannedDevices[1].deviceId).toBe(deviceIdC);
|
|
146
|
+
expect(result.current.scannedDevices[1].deviceModel.id).toBe(
|
|
147
|
+
DeviceModelId.nanoX
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("and when the hook consumer stops the scanning", () => {
|
|
153
|
+
it("should stop the scanning", async () => {
|
|
154
|
+
let stopBleScanning = false;
|
|
155
|
+
|
|
156
|
+
const { result, rerender } = renderHook(() =>
|
|
157
|
+
useBleDevicesScanning({
|
|
158
|
+
bleTransportListen: setupMockBleTransportListen(
|
|
159
|
+
mockEmitValuesByObserver
|
|
160
|
+
),
|
|
161
|
+
stopBleScanning,
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// At first the scanning finds device(s)
|
|
166
|
+
expect(result.current.scannedDevices).toHaveLength(1);
|
|
167
|
+
expect(result.current.scannedDevices[0].deviceId).toBe(deviceIdA);
|
|
168
|
+
expect(result.current.scannedDevices[0].deviceModel.id).toBe(
|
|
169
|
+
DeviceModelId.nanoX
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Then the consumer stops the scanning
|
|
173
|
+
stopBleScanning = true;
|
|
174
|
+
rerender({
|
|
175
|
+
bleTransportListen: setupMockBleTransportListen(
|
|
176
|
+
mockEmitValuesByObserver
|
|
177
|
+
),
|
|
178
|
+
stopBleScanning,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await act(async () => {
|
|
182
|
+
jest.advanceTimersByTime(2000);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// It should not find any new devices
|
|
186
|
+
expect(result.current.scannedDevices).toHaveLength(1);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("When the same device is being scanned several times", () => {
|
|
192
|
+
const deviceIdA = "ID_A";
|
|
193
|
+
const deviceIdB = "ID_B";
|
|
194
|
+
|
|
195
|
+
const mockEmitValuesByObserver = (observer: bleTransportListenObserver) => {
|
|
196
|
+
observer.next({
|
|
197
|
+
type: "add",
|
|
198
|
+
descriptor: aTransportBleDevice({ id: deviceIdA }),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
observer.next({
|
|
203
|
+
type: "add",
|
|
204
|
+
descriptor: aTransportBleDevice({ id: deviceIdA }),
|
|
205
|
+
});
|
|
206
|
+
}, 1000);
|
|
207
|
+
|
|
208
|
+
setTimeout(() => {
|
|
209
|
+
observer.next({
|
|
210
|
+
type: "add",
|
|
211
|
+
descriptor: aTransportBleDevice({ id: deviceIdB }),
|
|
212
|
+
});
|
|
213
|
+
}, 2000);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
it("should update the list of scanned devices without any duplicate", async () => {
|
|
217
|
+
const { result } = renderHook(() =>
|
|
218
|
+
useBleDevicesScanning({
|
|
219
|
+
bleTransportListen: setupMockBleTransportListen(
|
|
220
|
+
mockEmitValuesByObserver
|
|
221
|
+
),
|
|
222
|
+
})
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// The first time it gets the device from the scanning
|
|
226
|
+
expect(result.current.scannedDevices).toHaveLength(1);
|
|
227
|
+
expect(result.current.scannedDevices[0].deviceId).toBe(deviceIdA);
|
|
228
|
+
expect(result.current.scannedDevices[0].deviceModel.id).toBe(
|
|
229
|
+
DeviceModelId.nanoX
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
await act(async () => {
|
|
233
|
+
jest.advanceTimersByTime(1000);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// The second time it gets the same device from the scanning
|
|
237
|
+
// It should not have been added to the list
|
|
238
|
+
expect(result.current.scannedDevices).toHaveLength(1);
|
|
239
|
+
|
|
240
|
+
await act(async () => {
|
|
241
|
+
jest.advanceTimersByTime(1000);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// The third time it gets a new device
|
|
245
|
+
expect(result.current.scannedDevices).toHaveLength(2);
|
|
246
|
+
expect(result.current.scannedDevices[1].deviceId).toBe(deviceIdB);
|
|
247
|
+
expect(result.current.scannedDevices[1].deviceModel.id).toBe(
|
|
248
|
+
DeviceModelId.nanoX
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|