@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.
Files changed (163) hide show
  1. package/README.md +1 -1
  2. package/lib/__tests__/hw/getDeviceInfo.js +152 -0
  3. package/lib/__tests__/hw/getDeviceInfo.js.map +1 -1
  4. package/lib/api/Manager.d.ts +2 -1
  5. package/lib/api/Manager.d.ts.map +1 -1
  6. package/lib/api/Manager.js +37 -2
  7. package/lib/api/Manager.js.map +1 -1
  8. package/lib/ble/hooks/useBleDevicePairing.d.ts +18 -0
  9. package/lib/ble/hooks/useBleDevicePairing.d.ts.map +1 -0
  10. package/lib/ble/hooks/useBleDevicePairing.js +57 -0
  11. package/lib/ble/hooks/useBleDevicePairing.js.map +1 -0
  12. package/lib/ble/hooks/useBleDevicePairing.test.d.ts +2 -0
  13. package/lib/ble/hooks/useBleDevicePairing.test.d.ts.map +1 -0
  14. package/lib/ble/hooks/useBleDevicePairing.test.js +159 -0
  15. package/lib/ble/hooks/useBleDevicePairing.test.js.map +1 -0
  16. package/lib/ble/hooks/useBleDevicesScanning.d.ts +25 -0
  17. package/lib/ble/hooks/useBleDevicesScanning.d.ts.map +1 -0
  18. package/lib/ble/hooks/useBleDevicesScanning.js +117 -0
  19. package/lib/ble/hooks/useBleDevicesScanning.js.map +1 -0
  20. package/lib/ble/hooks/useBleDevicesScanning.test.d.ts +2 -0
  21. package/lib/ble/hooks/useBleDevicesScanning.test.d.ts.map +1 -0
  22. package/lib/ble/hooks/useBleDevicesScanning.test.js +281 -0
  23. package/lib/ble/hooks/useBleDevicesScanning.test.js.map +1 -0
  24. package/lib/ble/types.d.ts +21 -0
  25. package/lib/ble/types.d.ts.map +1 -0
  26. package/lib/ble/types.js +3 -0
  27. package/lib/ble/types.js.map +1 -0
  28. package/lib/errors.d.ts +2 -0
  29. package/lib/errors.d.ts.map +1 -1
  30. package/lib/errors.js +4 -2
  31. package/lib/errors.js.map +1 -1
  32. package/lib/families/cardano/js-estimateMaxSpendable.d.ts.map +1 -1
  33. package/lib/families/cardano/js-estimateMaxSpendable.js +2 -0
  34. package/lib/families/cardano/js-estimateMaxSpendable.js.map +1 -1
  35. package/lib/families/cosmos/specs.js +2 -2
  36. package/lib/families/cosmos/specs.js.map +1 -1
  37. package/lib/families/elrond/specs.js +1 -1
  38. package/lib/families/elrond/specs.js.map +1 -1
  39. package/lib/families/polkadot/bridge.integration.test.js +3 -3
  40. package/lib/families/polkadot/bridge.integration.test.js.map +1 -1
  41. package/lib/families/polkadot/specs.d.ts.map +1 -1
  42. package/lib/families/polkadot/specs.js +4 -3
  43. package/lib/families/polkadot/specs.js.map +1 -1
  44. package/lib/featureFlags/provider.d.ts +5 -1
  45. package/lib/featureFlags/provider.d.ts.map +1 -1
  46. package/lib/featureFlags/provider.js +6 -4
  47. package/lib/featureFlags/provider.js.map +1 -1
  48. package/lib/featureFlags/useFeature.d.ts.map +1 -1
  49. package/lib/featureFlags/useFeature.js +3 -1
  50. package/lib/featureFlags/useFeature.js.map +1 -1
  51. package/lib/generated/account.d.ts.map +1 -1
  52. package/lib/generated/account.js.map +1 -1
  53. package/lib/generated/bridge/js.d.ts.map +1 -1
  54. package/lib/generated/bridge/js.js.map +1 -1
  55. package/lib/generated/bridge/mock.d.ts.map +1 -1
  56. package/lib/generated/bridge/mock.js.map +1 -1
  57. package/lib/generated/cli-transaction.d.ts.map +1 -1
  58. package/lib/generated/cli-transaction.js.map +1 -1
  59. package/lib/generated/deviceTransactionConfig.d.ts.map +1 -1
  60. package/lib/generated/deviceTransactionConfig.js.map +1 -1
  61. package/lib/generated/exchange.d.ts.map +1 -1
  62. package/lib/generated/exchange.js.map +1 -1
  63. package/lib/generated/hw-getAddress.d.ts.map +1 -1
  64. package/lib/generated/hw-getAddress.js.map +1 -1
  65. package/lib/generated/hw-signMessage.d.ts.map +1 -1
  66. package/lib/generated/hw-signMessage.js.map +1 -1
  67. package/lib/generated/mock.d.ts.map +1 -1
  68. package/lib/generated/mock.js.map +1 -1
  69. package/lib/generated/platformAdapter.d.ts.map +1 -1
  70. package/lib/generated/platformAdapter.js.map +1 -1
  71. package/lib/generated/presync.d.ts.map +1 -1
  72. package/lib/generated/presync.js.map +1 -1
  73. package/lib/generated/specs.d.ts.map +1 -1
  74. package/lib/generated/specs.js.map +1 -1
  75. package/lib/generated/speculos-deviceActions.d.ts.map +1 -1
  76. package/lib/generated/speculos-deviceActions.js.map +1 -1
  77. package/lib/generated/transaction.d.ts.map +1 -1
  78. package/lib/generated/transaction.js.map +1 -1
  79. package/lib/generated/types.d.ts.map +1 -1
  80. package/lib/hw/actions/installLanguage.d.ts +21 -0
  81. package/lib/hw/actions/installLanguage.d.ts.map +1 -0
  82. package/lib/hw/actions/installLanguage.js +260 -0
  83. package/lib/hw/actions/installLanguage.js.map +1 -0
  84. package/lib/hw/attemptToQuitApp.d.ts +10 -0
  85. package/lib/hw/attemptToQuitApp.d.ts.map +1 -0
  86. package/lib/hw/attemptToQuitApp.js +22 -0
  87. package/lib/hw/attemptToQuitApp.js.map +1 -0
  88. package/lib/hw/connectManager.d.ts +2 -5
  89. package/lib/hw/connectManager.d.ts.map +1 -1
  90. package/lib/hw/connectManager.js +2 -14
  91. package/lib/hw/connectManager.js.map +1 -1
  92. package/lib/hw/getDeviceInfo.d.ts.map +1 -1
  93. package/lib/hw/getDeviceInfo.js +6 -3
  94. package/lib/hw/getDeviceInfo.js.map +1 -1
  95. package/lib/hw/getVersion.d.ts +3 -3
  96. package/lib/hw/getVersion.d.ts.map +1 -1
  97. package/lib/hw/getVersion.js +53 -5
  98. package/lib/hw/getVersion.js.map +1 -1
  99. package/lib/hw/installLanguage.d.ts +17 -0
  100. package/lib/hw/installLanguage.d.ts.map +1 -0
  101. package/lib/hw/installLanguage.js +173 -0
  102. package/lib/hw/installLanguage.js.map +1 -0
  103. package/lib/hw/uninstallLanguage.d.ts +15 -0
  104. package/lib/hw/uninstallLanguage.d.ts.map +1 -0
  105. package/lib/hw/uninstallLanguage.js +121 -0
  106. package/lib/hw/uninstallLanguage.js.map +1 -0
  107. package/lib/manager/hooks.d.ts +2 -1
  108. package/lib/manager/hooks.d.ts.map +1 -1
  109. package/lib/manager/hooks.js +13 -1
  110. package/lib/manager/hooks.js.map +1 -1
  111. package/lib/manager/index.d.ts +2 -1
  112. package/lib/manager/index.d.ts.map +1 -1
  113. package/lib/manager/index.js +16 -0
  114. package/lib/manager/index.js.map +1 -1
  115. package/lib/manager/localization.d.ts +3 -0
  116. package/lib/manager/localization.d.ts.map +1 -0
  117. package/lib/manager/localization.js +15 -0
  118. package/lib/manager/localization.js.map +1 -0
  119. package/lib/platform/react.d.ts.map +1 -1
  120. package/lib/platform/react.js +6 -3
  121. package/lib/platform/react.js.map +1 -1
  122. package/package.json +18 -18
  123. package/src/__tests__/hw/getDeviceInfo.ts +137 -0
  124. package/src/api/Manager.ts +50 -2
  125. package/src/ble/hooks/useBleDevicePairing.test.ts +91 -0
  126. package/src/ble/hooks/useBleDevicePairing.ts +51 -0
  127. package/src/ble/hooks/useBleDevicesScanning.test.ts +252 -0
  128. package/src/ble/hooks/useBleDevicesScanning.ts +147 -0
  129. package/src/ble/types.ts +42 -0
  130. package/src/errors.ts +9 -0
  131. package/src/families/cardano/js-estimateMaxSpendable.ts +2 -0
  132. package/src/families/cosmos/specs.ts +2 -2
  133. package/src/families/elrond/specs.ts +1 -1
  134. package/src/families/polkadot/bridge.integration.test.ts +3 -3
  135. package/src/families/polkadot/specs.ts +4 -3
  136. package/src/featureFlags/provider.tsx +11 -1
  137. package/src/featureFlags/useFeature.ts +6 -2
  138. package/src/generated/account.ts +0 -8
  139. package/src/generated/bridge/js.ts +0 -18
  140. package/src/generated/bridge/mock.ts +0 -8
  141. package/src/generated/cli-transaction.ts +0 -17
  142. package/src/generated/deviceTransactionConfig.ts +15 -31
  143. package/src/generated/exchange.ts +0 -5
  144. package/src/generated/hw-getAddress.ts +0 -18
  145. package/src/generated/hw-signMessage.ts +0 -3
  146. package/src/generated/mock.ts +0 -3
  147. package/src/generated/platformAdapter.ts +0 -4
  148. package/src/generated/presync.ts +0 -1
  149. package/src/generated/specs.ts +0 -17
  150. package/src/generated/speculos-deviceActions.ts +0 -17
  151. package/src/generated/transaction.ts +0 -18
  152. package/src/generated/types.ts +7 -4
  153. package/src/hw/actions/installLanguage.ts +343 -0
  154. package/src/hw/attemptToQuitApp.ts +32 -0
  155. package/src/hw/connectManager.ts +5 -26
  156. package/src/hw/getDeviceInfo.ts +6 -0
  157. package/src/hw/getVersion.ts +77 -3
  158. package/src/hw/installLanguage.ts +172 -0
  159. package/src/hw/uninstallLanguage.ts +127 -0
  160. package/src/manager/hooks.ts +21 -1
  161. package/src/manager/index.ts +16 -0
  162. package/src/manager/localization.ts +19 -0
  163. 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
+ });
@@ -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 = // FIXME LLD is doing error remapping already. we probably need to move the remapping in live-common
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
+ });