From 3c3ee418513c6cb609ba49afc810dc10d0a95787 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Tue, 30 Jan 2024 16:35:34 +0100 Subject: [PATCH 01/19] refactor(common/listappsv2): create use case in "device/" --- libs/ledger-live-common/src/apps/hw.ts | 4 ++-- libs/ledger-live-common/src/apps/listApps/v2.ts | 7 ++++--- .../src/device/use-cases/listAppsV2UseCase.ts | 12 ++++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 libs/ledger-live-common/src/device/use-cases/listAppsV2UseCase.ts diff --git a/libs/ledger-live-common/src/apps/hw.ts b/libs/ledger-live-common/src/apps/hw.ts index 20b6a8a42a7d..e8c1292f67a6 100644 --- a/libs/ledger-live-common/src/apps/hw.ts +++ b/libs/ledger-live-common/src/apps/hw.ts @@ -7,7 +7,7 @@ import uninstallApp from "../hw/uninstallApp"; import type { ListAppsEvent } from "./types"; import listAppsV1 from "./listApps/v1"; -import listAppsV2 from "./listApps/v2"; +import { listAppsV2UseCase } from "../device/use-cases/listAppsV2UseCase"; export const execWithTransport = (transport: Transport): Exec => @@ -32,4 +32,4 @@ export const listApps = ( transport: Transport, deviceInfo: DeviceInfo, ): Observable => - listAppsV2Enabled ? listAppsV2(transport, deviceInfo) : listAppsV1(transport, deviceInfo); + listAppsV2Enabled ? listAppsV2UseCase(transport, deviceInfo) : listAppsV1(transport, deviceInfo); diff --git a/libs/ledger-live-common/src/apps/listApps/v2.ts b/libs/ledger-live-common/src/apps/listApps/v2.ts index 5ec27e001a04..b8e2ef557c75 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.ts @@ -26,7 +26,10 @@ const appsWithDynamicHashes = ["Fido U2F", "Security Key"]; // Empty hash data means we won't have information on the app. const emptyHashData = "0".repeat(64); -const listApps = (transport: Transport, deviceInfo: DeviceInfo): Observable => { +export const listAppsV2 = ( + transport: Transport, + deviceInfo: DeviceInfo, +): Observable => { const tracer = new LocalTracer("list-apps", { transport: transport.getTraceContext() }); tracer.trace("Using new version", { deviceInfo }); @@ -292,5 +295,3 @@ const listApps = (transport: Transport, deviceInfo: DeviceInfo): Observable { + return listAppsV2(transport, deviceInfo); +} From a9c0dba33d6cf0c99c5bc25fbd0f16ed30121a55 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Tue, 30 Jan 2024 16:47:39 +0100 Subject: [PATCH 02/19] refactor(common/listappsv2): remove direct usages of getEnv --- .../src/apps/listApps/v2.ts | 35 +++++++++++++------ .../src/device/use-cases/listAppsV2UseCase.ts | 2 +- .../src/manager/provider.ts | 14 ++++++-- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/libs/ledger-live-common/src/apps/listApps/v2.ts b/libs/ledger-live-common/src/apps/listApps/v2.ts index b8e2ef557c75..ae89e0b70570 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.ts @@ -5,7 +5,6 @@ import { Observable, throwError, Subscription } from "rxjs"; import { App, DeviceInfo, idsToLanguage, languageIds } from "@ledgerhq/types-live"; import { LocalTracer } from "@ledgerhq/logs"; import type { ListAppsEvent, ListAppsResult, ListAppResponse } from "../types"; -import { getProviderId } from "../../manager"; import hwListApps from "../../hw/listApps"; import staxFetchImageSize from "../../hw/staxFetchImageSize"; import { @@ -14,10 +13,10 @@ import { findCryptoCurrencyById, } from "../../currencies"; import ManagerAPI from "../../manager/api"; -import { getEnv } from "@ledgerhq/live-env"; import getDeviceName from "../../hw/getDeviceName"; import { getLatestFirmwareForDeviceUseCase } from "../../device/use-cases/getLatestFirmwareForDeviceUseCase"; +import { getProviderIdPure } from "../../manager/provider"; // Hash discrepancies for these apps do NOT indicate a potential update, // these apps have a mechanism that makes their hash change every time. @@ -26,10 +25,21 @@ const appsWithDynamicHashes = ["Fido U2F", "Security Key"]; // Empty hash data means we won't have information on the app. const emptyHashData = "0".repeat(64); -export const listAppsV2 = ( - transport: Transport, - deviceInfo: DeviceInfo, -): Observable => { +type ListAppsV2Params = { + transport: Transport; + deviceInfo: DeviceInfo; + deviceProxyModel?: DeviceModelId; // TODO: this should be optional, but then the logic below should handle it properly + managerDevMode?: boolean; + forceProvider?: number; +}; + +export const listAppsV2 = ({ + transport, + deviceInfo, + deviceProxyModel, + managerDevMode, + forceProvider, +}: ListAppsV2Params): Observable => { const tracer = new LocalTracer("list-apps", { transport: transport.getTraceContext() }); tracer.trace("Using new version", { deviceInfo }); @@ -40,13 +50,14 @@ export const listAppsV2 = ( const deviceModelId = (transport.deviceModel && transport.deviceModel.id) || (deviceInfo && identifyTargetId(deviceInfo.targetId as number))?.id || - (getEnv("DEVICE_PROXY_MODEL") as DeviceModelId); + deviceProxyModel; return new Observable(o => { let sub: Subscription; async function main() { - const isDevMode = getEnv("MANAGER_DEV_MODE"); - const provider = getProviderId(deviceInfo); + const provider = getProviderIdPure({ deviceInfo, forceProvider }); + + if (!deviceModelId) throw new Error("Bad usage of listAppsV2: missing deviceModelId"); const deviceModel = getDeviceModel(deviceModelId); const bytesPerBlock = deviceModel.getBlockSize(deviceInfo.version); @@ -142,7 +153,7 @@ export const listAppsV2 = ( */ const sortedCryptoCurrenciesPromise = currenciesByMarketcap( - listCryptoCurrencies(isDevMode, true), + listCryptoCurrencies(managerDevMode, true), ); /** @@ -233,7 +244,9 @@ export const listAppsV2 = ( // Used to hide apps that are dev tools if user didn't opt-in. const appsListNames = catalogForDevice - .filter(({ isDevTools, name }) => isDevMode || !isDevTools || name in installedAppNames) + .filter( + ({ isDevTools, name }) => managerDevMode || !isDevTools || name in installedAppNames, + ) .map(({ name }) => name); /** diff --git a/libs/ledger-live-common/src/device/use-cases/listAppsV2UseCase.ts b/libs/ledger-live-common/src/device/use-cases/listAppsV2UseCase.ts index 4b2cebf02c4c..5d52e67caa48 100644 --- a/libs/ledger-live-common/src/device/use-cases/listAppsV2UseCase.ts +++ b/libs/ledger-live-common/src/device/use-cases/listAppsV2UseCase.ts @@ -8,5 +8,5 @@ export function listAppsV2UseCase( transport: Transport, deviceInfo: DeviceInfo, ): Observable { - return listAppsV2(transport, deviceInfo); + return listAppsV2({transport, deviceInfo, deviceProxyModel: getEnv("DEVICE_PROXY_MODEL") as DeviceModelId}); } diff --git a/libs/ledger-live-common/src/manager/provider.ts b/libs/ledger-live-common/src/manager/provider.ts index a76b3590e8bd..510c2ded4572 100644 --- a/libs/ledger-live-common/src/manager/provider.ts +++ b/libs/ledger-live-common/src/manager/provider.ts @@ -6,8 +6,14 @@ export const PROVIDERS: Record = { shitcoins: 4, ee: 5, }; -export function getProviderId(deviceInfo: DeviceInfo | undefined | null): number { - const forceProvider = getEnv("FORCE_PROVIDER"); + +export function getProviderIdPure({ + deviceInfo, + forceProvider, +}: { + deviceInfo: DeviceInfo | undefined | null; + forceProvider?: number; +}): number { if (forceProvider && forceProvider !== 1) return forceProvider; if (!deviceInfo) return 1; const { providerName } = deviceInfo; @@ -15,3 +21,7 @@ export function getProviderId(deviceInfo: DeviceInfo | undefined | null): number if (maybeProvider) return maybeProvider; return 1; } + +export function getProviderId(deviceInfo: DeviceInfo | undefined | null) { + return getProviderIdPure({ deviceInfo, forceProvider: getEnv("FORCE_PROVIDER") }); +} From d950d8f628807720b46d99b7afc2d3bc5cf2066c Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Wed, 31 Jan 2024 10:54:28 +0100 Subject: [PATCH 03/19] refactor(common/device): move listApps entrypoint to listAppsUsecase --- apps/cli/src/commands/appUninstallAll.ts | 7 ++-- .../src/commands/appsCheckAllAppVersions.ts | 7 ++-- apps/cli/src/commands/appsInstallAll.ts | 7 ++-- apps/cli/src/commands/appsUpdateTestAll.ts | 9 +++-- .../cli/src/commands/devDeviceAppsScenario.ts | 7 ++-- apps/cli/src/commands/managerListApps.ts | 2 +- .../src/renderer/Default.tsx | 2 +- .../src/components/RootNavigator/index.tsx | 2 +- .../src/screens/PairDevices/index.tsx | 4 +-- .../src/apps/inlineAppInstall.ts | 4 +-- .../src/apps/listApps/v1.ts | 4 +-- .../src/apps/listApps/v2.ts | 2 +- .../use-cases/listAppsUseCase.ts} | 33 ++++++++++++------- .../src/device/use-cases/listAppsV2UseCase.ts | 12 ------- .../src/hw/connectManager.ts | 4 +-- 15 files changed, 58 insertions(+), 48 deletions(-) rename libs/ledger-live-common/src/{apps/hw.ts => device/use-cases/listAppsUseCase.ts} (57%) delete mode 100644 libs/ledger-live-common/src/device/use-cases/listAppsV2UseCase.ts diff --git a/apps/cli/src/commands/appUninstallAll.ts b/apps/cli/src/commands/appUninstallAll.ts index 06af58273ef1..9bc033d8f1b2 100644 --- a/apps/cli/src/commands/appUninstallAll.ts +++ b/apps/cli/src/commands/appUninstallAll.ts @@ -4,7 +4,10 @@ import { mergeMap, filter, map } from "rxjs/operators"; import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; import { reducer, runAll } from "@ledgerhq/live-common/apps/index"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import { + listAppsUseCase, + execWithTransport, +} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import { command as uninstallAllApps } from "@ledgerhq/live-common/hw/uninstallAllApps"; import { deviceOpt } from "../scan"; @@ -25,7 +28,7 @@ export default { } else { return from(getDeviceInfo(t)).pipe( mergeMap(deviceInfo => - listApps(t, deviceInfo).pipe( + listAppsUseCase(t, deviceInfo).pipe( filter(e => e.type === "result"), map((e: any) => reducer(e.result, { diff --git a/apps/cli/src/commands/appsCheckAllAppVersions.ts b/apps/cli/src/commands/appsCheckAllAppVersions.ts index 6c1ce0418b3a..952cf8cfbb1c 100644 --- a/apps/cli/src/commands/appsCheckAllAppVersions.ts +++ b/apps/cli/src/commands/appsCheckAllAppVersions.ts @@ -10,7 +10,10 @@ import network from "@ledgerhq/live-network/network"; import installApp from "@ledgerhq/live-common/hw/installApp"; import uninstallApp from "@ledgerhq/live-common/hw/uninstallApp"; import { initState, reducer, runAll } from "@ledgerhq/live-common/apps/index"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import { + listAppsUseCase, + execWithTransport, +} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import { delay } from "@ledgerhq/live-common/promise"; import { getEnv } from "@ledgerhq/live-env"; import { getDependencies } from "@ledgerhq/live-common/apps/polyfill"; @@ -274,7 +277,7 @@ const checkInstalled = (installed, candidate: Candidate) => { }; const wipeAll = (t, deviceInfo) => - listApps(t, deviceInfo).pipe( + listAppsUseCase(t, deviceInfo).pipe( filter(e => e.type === "result"), map((e: any) => e.result), mergeMap(listAppsResult => { diff --git a/apps/cli/src/commands/appsInstallAll.ts b/apps/cli/src/commands/appsInstallAll.ts index 2302801e2ca6..3e2c164e1e63 100644 --- a/apps/cli/src/commands/appsInstallAll.ts +++ b/apps/cli/src/commands/appsInstallAll.ts @@ -4,7 +4,10 @@ import { mergeMap, filter, map } from "rxjs/operators"; import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; import { initState, reducer, runAll } from "@ledgerhq/live-common/apps/index"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import { + listAppsUseCase, + execWithTransport, +} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import { deviceOpt } from "../scan"; export default { description: "test script to install and uninstall all apps", @@ -18,7 +21,7 @@ export default { const exec = execWithTransport(t); return from(getDeviceInfo(t)).pipe( mergeMap(deviceInfo => - listApps(t, deviceInfo).pipe( + listAppsUseCase(t, deviceInfo).pipe( filter(e => e.type === "result"), map((e: any) => e.result.appsListNames.reduce( diff --git a/apps/cli/src/commands/appsUpdateTestAll.ts b/apps/cli/src/commands/appsUpdateTestAll.ts index a03a81472c15..f5cf169bf8a7 100644 --- a/apps/cli/src/commands/appsUpdateTestAll.ts +++ b/apps/cli/src/commands/appsUpdateTestAll.ts @@ -4,7 +4,10 @@ import { mergeMap, ignoreElements, filter, map } from "rxjs/operators"; import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; import { initState, reducer, runAll, getActionPlan } from "@ledgerhq/live-common/apps/index"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import { + listAppsUseCase, + execWithTransport, +} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import type { AppOp } from "@ledgerhq/live-common/apps/types"; import { deviceOpt } from "../scan"; @@ -34,7 +37,7 @@ export default { // FIXME: mergeMap deprecated, using map inside pipe should do the work map( deviceInfo => - listApps(t, deviceInfo).pipe( + listAppsUseCase(t, deviceInfo).pipe( filter(e => e.type === "result"), map((e: any) => e.result), mergeMap(listAppsResult => { @@ -82,7 +85,7 @@ export default { new Observable(o => { let sub; const timeout = setTimeout(() => { - sub = listApps(t, deviceInfo).subscribe(o); + sub = listAppsUseCase(t, deviceInfo).subscribe(o); }, 4000); return () => { clearTimeout(timeout); diff --git a/apps/cli/src/commands/devDeviceAppsScenario.ts b/apps/cli/src/commands/devDeviceAppsScenario.ts index 86eb34233343..3dc7943a4f39 100644 --- a/apps/cli/src/commands/devDeviceAppsScenario.ts +++ b/apps/cli/src/commands/devDeviceAppsScenario.ts @@ -4,7 +4,10 @@ import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; import { initState, ListAppsResult, reducer, runAll } from "@ledgerhq/live-common/apps/index"; import ManagerAPI from "@ledgerhq/live-common/manager/api"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import { + listAppsUseCase, + execWithTransport, +} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import installApp from "@ledgerhq/live-common/hw/installApp"; import { deviceOpt } from "../scan"; import { Application } from "@ledgerhq/types-live"; @@ -64,7 +67,7 @@ export default { // $FlowFixMe return from(getDeviceInfo(t)).pipe( mergeMap(deviceInfo => - listApps(t, deviceInfo).pipe( + listAppsUseCase(t, deviceInfo).pipe( filter(e => e.type === "result"), map<{ type: "result"; result: ListAppsResult }, ListAppsResult>(e => e.result), mergeMap>(listAppsResult => { diff --git a/apps/cli/src/commands/managerListApps.ts b/apps/cli/src/commands/managerListApps.ts index 6d5a6636f165..dea19f71995a 100644 --- a/apps/cli/src/commands/managerListApps.ts +++ b/apps/cli/src/commands/managerListApps.ts @@ -2,7 +2,7 @@ import { from } from "rxjs"; import { filter, map, mergeMap, repeat } from "rxjs/operators"; import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; -import { enableListAppsV2, listApps } from "@ledgerhq/live-common/apps/hw"; +import { enableListAppsV2, listApps } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import { deviceOpt } from "../scan"; export default { description: "List apps that can be installed on the device", diff --git a/apps/ledger-live-desktop/src/renderer/Default.tsx b/apps/ledger-live-desktop/src/renderer/Default.tsx index 27a5fc274754..e9413dadc326 100644 --- a/apps/ledger-live-desktop/src/renderer/Default.tsx +++ b/apps/ledger-live-desktop/src/renderer/Default.tsx @@ -40,7 +40,7 @@ import VaultSignerBanner from "~/renderer/components/VaultSignerBanner"; import { hasCompletedOnboardingSelector } from "~/renderer/reducers/settings"; import { updateIdentify } from "./analytics/segment"; import { useFeature, FeatureToggle } from "@ledgerhq/live-common/featureFlags/index"; -import { enableListAppsV2 } from "@ledgerhq/live-common/apps/hw"; +import { enableListAppsV2 } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import { useFetchCurrencyAll, useFetchCurrencyFrom, diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/index.tsx b/apps/ledger-live-mobile/src/components/RootNavigator/index.tsx index 193115629dd0..7678765eefff 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/index.tsx +++ b/apps/ledger-live-mobile/src/components/RootNavigator/index.tsx @@ -10,7 +10,7 @@ import BaseOnboardingNavigator from "./BaseOnboardingNavigator"; import { RootStackParamList } from "./types/RootNavigator"; import { AnalyticsContextProvider } from "~/analytics/AnalyticsContext"; import { StartupTimeMarker } from "../../StartupTimeMarker"; -import { enableListAppsV2 } from "@ledgerhq/live-common/apps/hw"; +import { enableListAppsV2 } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; export default function RootNavigator() { const hasCompletedOnboarding = useSelector(hasCompletedOnboardingSelector); diff --git a/apps/ledger-live-mobile/src/screens/PairDevices/index.tsx b/apps/ledger-live-mobile/src/screens/PairDevices/index.tsx index e8a5e1300561..aba589e8e62f 100644 --- a/apps/ledger-live-mobile/src/screens/PairDevices/index.tsx +++ b/apps/ledger-live-mobile/src/screens/PairDevices/index.tsx @@ -6,7 +6,7 @@ import { timeout, tap } from "rxjs/operators"; import { v4 as uuid } from "uuid"; import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; import getDeviceName from "@ledgerhq/live-common/hw/getDeviceName"; -import { listApps } from "@ledgerhq/live-common/apps/hw"; +import { listAppsUseCase } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import { DeviceModelId } from "@ledgerhq/devices"; import { delay } from "@ledgerhq/live-common/promise"; import type { Device } from "@ledgerhq/live-common/hw/actions/types"; @@ -145,7 +145,7 @@ function PairDevicesInner({ navigation, route }: NavigationProps) { // Waits until listApps completes or emits and error await lastValueFrom( - listApps(transport, deviceInfo).pipe( + listAppsUseCase(transport, deviceInfo).pipe( timeout(GENUINE_CHECK_TIMEOUT), tap(e => { tracer.trace("Event from listApps", { eventType: e.type }); diff --git a/libs/ledger-live-common/src/apps/inlineAppInstall.ts b/libs/ledger-live-common/src/apps/inlineAppInstall.ts index e4fb7b863b99..d3ac3798747c 100644 --- a/libs/ledger-live-common/src/apps/inlineAppInstall.ts +++ b/libs/ledger-live-common/src/apps/inlineAppInstall.ts @@ -2,7 +2,7 @@ import Transport from "@ledgerhq/hw-transport"; import { Observable, concat, of, from, EMPTY, defer } from "rxjs"; import { ConnectAppEvent } from "../hw/connectApp"; import getDeviceInfo from "../hw/getDeviceInfo"; -import { listApps, execWithTransport } from "./hw"; +import { listAppsUseCase, execWithTransport } from "../device/use-cases/listAppsUseCase"; import { reducer, initState, isOutOfMemoryState, predictOptimisticState } from "./logic"; import { runAllWithProgress } from "./runner"; import { InlineAppInstallEvent } from "./types"; @@ -43,7 +43,7 @@ const inlineAppInstall = ({ from(getDeviceInfo(transport)).pipe( mergeMap(deviceInfo => { tracer.trace("Got device info", { deviceInfo }); - return listApps(transport, deviceInfo); + return listAppsUseCase(transport, deviceInfo); }), mergeMap(e => { // Bubble up events diff --git a/libs/ledger-live-common/src/apps/listApps/v1.ts b/libs/ledger-live-common/src/apps/listApps/v1.ts index 7657d3ded574..15c5be38cf19 100644 --- a/libs/ledger-live-common/src/apps/listApps/v1.ts +++ b/libs/ledger-live-common/src/apps/listApps/v1.ts @@ -25,7 +25,7 @@ const emptyHashData = "000000000000000000000000000000000000000000000000000000000 //TODO if you are reading this, don't worry, a big rewrite is coming and we'll be able //to simplify this a lot. Stay calm. -const listApps = (transport: Transport, deviceInfo: DeviceInfo): Observable => { +export const listApps = (transport: Transport, deviceInfo: DeviceInfo): Observable => { const tracer = new LocalTracer("list-apps", { transport: transport.getTraceContext() }); tracer.trace("Using legacy version", { deviceInfo }); @@ -297,5 +297,3 @@ const listApps = (transport: Transport, deviceInfo: DeviceInfo): Observable @@ -28,8 +29,16 @@ export const execWithTransport = */ let listAppsV2Enabled = false; export const enableListAppsV2 = (enabled: boolean) => (listAppsV2Enabled = enabled); -export const listApps = ( + +export function listAppsUseCase( transport: Transport, deviceInfo: DeviceInfo, -): Observable => - listAppsV2Enabled ? listAppsV2UseCase(transport, deviceInfo) : listAppsV1(transport, deviceInfo); +): Observable { + return listAppsV2Enabled + ? listAppsV1(transport, deviceInfo) + : listAppsV2({ + transport, + deviceInfo, + deviceProxyModel: getEnv("DEVICE_PROXY_MODEL") as DeviceModelId, + }); +} diff --git a/libs/ledger-live-common/src/device/use-cases/listAppsV2UseCase.ts b/libs/ledger-live-common/src/device/use-cases/listAppsV2UseCase.ts deleted file mode 100644 index 5d52e67caa48..000000000000 --- a/libs/ledger-live-common/src/device/use-cases/listAppsV2UseCase.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Observable } from "rxjs"; -import Transport from "@ledgerhq/hw-transport"; -import { DeviceInfo } from "@ledgerhq/types-live"; -import { listAppsV2 } from "../../apps/listApps/v2"; -import { ListAppsEvent } from "../../apps"; - -export function listAppsV2UseCase( - transport: Transport, - deviceInfo: DeviceInfo, -): Observable { - return listAppsV2({transport, deviceInfo, deviceProxyModel: getEnv("DEVICE_PROXY_MODEL") as DeviceModelId}); -} diff --git a/libs/ledger-live-common/src/hw/connectManager.ts b/libs/ledger-live-common/src/hw/connectManager.ts index ec507db5da78..9aef0203796e 100644 --- a/libs/ledger-live-common/src/hw/connectManager.ts +++ b/libs/ledger-live-common/src/hw/connectManager.ts @@ -8,7 +8,7 @@ import { } from "@ledgerhq/errors"; import { DeviceInfo } from "@ledgerhq/types-live"; import type { ListAppsEvent } from "../apps"; -import { listApps } from "../apps/hw"; +import { listAppsUseCase } from "../device/use-cases/listAppsUseCase"; import { withDevice } from "./deviceAccess"; import getDeviceInfo from "./getDeviceInfo"; import getAppAndVersion from "./getAppAndVersion"; @@ -78,7 +78,7 @@ const cmd = ({ deviceId, request }: Input): Observable => type: "listingApps", deviceInfo, } as ConnectManagerEvent), - listApps(transport, deviceInfo), + listAppsUseCase(transport, deviceInfo), ); }), catchError((e: unknown) => { From f63c41315005126ce1fef6ca25d8464b8044cb53 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Wed, 31 Jan 2024 16:56:27 +0100 Subject: [PATCH 04/19] refactor(common): add TODO, rename variables --- libs/ledger-live-common/src/apps/listApps/v2.ts | 14 +++++++------- .../repositories/ManagerApiRepository.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libs/ledger-live-common/src/apps/listApps/v2.ts b/libs/ledger-live-common/src/apps/listApps/v2.ts index 673cdb9c83bc..ae0a3f827d2f 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.ts @@ -25,11 +25,11 @@ const appsWithDynamicHashes = ["Fido U2F", "Security Key"]; // Empty hash data means we won't have information on the app. const emptyHashData = "0".repeat(64); -type ListAppsV2Params = { +type ListAppsParams = { transport: Transport; deviceInfo: DeviceInfo; - deviceProxyModel?: DeviceModelId; // TODO: this should be optional, but then the logic below should handle it properly - managerDevMode?: boolean; + deviceProxyModel?: DeviceModelId; + managerDevModeEnabled?: boolean; forceProvider?: number; }; @@ -37,9 +37,9 @@ export const listApps = ({ transport, deviceInfo, deviceProxyModel, - managerDevMode, + managerDevModeEnabled, forceProvider, -}: ListAppsV2Params): Observable => { +}: ListAppsParams): Observable => { const tracer = new LocalTracer("list-apps", { transport: transport.getTraceContext() }); tracer.trace("Using new version", { deviceInfo }); @@ -153,7 +153,7 @@ export const listApps = ({ */ const sortedCryptoCurrenciesPromise = currenciesByMarketcap( - listCryptoCurrencies(managerDevMode, true), + listCryptoCurrencies(managerDevModeEnabled, true), ); /** @@ -245,7 +245,7 @@ export const listApps = ({ // Used to hide apps that are dev tools if user didn't opt-in. const appsListNames = catalogForDevice .filter( - ({ isDevTools, name }) => managerDevMode || !isDevTools || name in installedAppNames, + ({ isDevTools, name }) => managerDevModeEnabled || !isDevTools || name in installedAppNames, ) .map(({ name }) => name); diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts index 79c01370de9c..a9ad82ac7855 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts @@ -10,7 +10,7 @@ export interface ManagerApiRepository { userId: string; }): Promise; - fetchMcus(): Promise; + fetchMcus(): Promise; // TODO: type properly getDeviceVersion({ targetId, From c62414de39033c7a511a2f8addba8c7ac1fcea9b Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Wed, 31 Jan 2024 18:16:12 +0100 Subject: [PATCH 05/19] test(common/listApps/v2): test basic error cases test(common/listApps/v2): more error cases --- .../src/apps/listApps/v2.test.ts | 194 ++++++++++++++++++ .../src/apps/listApps/v2.ts | 7 +- .../src/mock/fixtures/aDeviceVersion.ts | 20 ++ .../src/mock/fixtures/aFinalFirmware.ts | 25 +++ 4 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 libs/ledger-live-common/src/apps/listApps/v2.test.ts create mode 100644 libs/ledger-live-common/src/mock/fixtures/aDeviceVersion.ts create mode 100644 libs/ledger-live-common/src/mock/fixtures/aFinalFirmware.ts diff --git a/libs/ledger-live-common/src/apps/listApps/v2.test.ts b/libs/ledger-live-common/src/apps/listApps/v2.test.ts new file mode 100644 index 000000000000..63296c229527 --- /dev/null +++ b/libs/ledger-live-common/src/apps/listApps/v2.test.ts @@ -0,0 +1,194 @@ +import { UnexpectedBootloader } from "@ledgerhq/errors"; +import { aTransportBuilder } from "@ledgerhq/hw-transport-mocker"; +import { listApps } from "./v2"; +import { aDeviceInfoBuilder } from "../../mock/fixtures/aDeviceInfo"; +import ManagerAPI from "../../manager/api"; +import { from } from "rxjs"; +import { aDeviceVersionBuilder } from "../../mock/fixtures/aDeviceVersion"; + +jest.useFakeTimers(); + +describe("listApps v2", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should return an observable that errors if deviceInfo.isOSU is true", done => { + const transport = aTransportBuilder(); + const deviceInfo = aDeviceInfoBuilder({ isOSU: true, isBootloader: false }); + const result = listApps({ transport, deviceInfo }); + + result.subscribe({ + error: err => { + expect(err).toBeInstanceOf(UnexpectedBootloader); + done(); + }, + complete: () => { + fail("this observable should not complete"); + }, + }); + jest.advanceTimersByTime(1); + }); + + it("should return an observable that errors if deviceInfo.isBootloader is true", done => { + const transport = aTransportBuilder(); + const deviceInfo = aDeviceInfoBuilder({ isOSU: false, isBootloader: true }); + const result = listApps({ transport, deviceInfo }); + + result.subscribe({ + error: err => { + expect(err).toBeInstanceOf(UnexpectedBootloader); + done(); + }, + complete: () => { + fail("this observable should not complete"); + }, + }); + jest.advanceTimersByTime(1); + }); + + it("should return an observable that errors if transport.deviceModel.id is undefined, identifyTargetId returns a falsy value and deviceProxyModel is undefined", done => { + const transport = aTransportBuilder({ deviceModel: null }); + const deviceInfo = aDeviceInfoBuilder({ isOSU: false, isBootloader: false, targetId: 0 }); + + const result = listApps({ transport, deviceInfo }); + + result.subscribe({ + error: err => { + expect(err.message).toBe("Bad usage of listAppsV2: missing deviceModelId"); + done(); + }, + complete: () => { + fail("this observable should not complete"); + }, + }); + jest.advanceTimersByTime(1); + }); + + it("should call hwListApps() if deviceInfo.managerAllowed is true", () => { + const listAppsCommandSpy = jest + .spyOn(jest.requireActual("../../hw/listApps"), "default") + .mockReturnValue(Promise.resolve([])); + const listInstalledAppsSpy = jest.spyOn(ManagerAPI, "listInstalledApps"); + + const transport = aTransportBuilder(); + const deviceInfo = aDeviceInfoBuilder({ + isOSU: false, + isBootloader: false, + managerAllowed: true, + targetId: 0x33200000, + }); + + listApps({ transport, deviceInfo }).subscribe({}); + jest.advanceTimersByTime(1); + + expect(listAppsCommandSpy).toHaveBeenCalled(); + expect(listInstalledAppsSpy).not.toHaveBeenCalled(); + }); + + it("should call ManagerAPI.listInstalledApps() if deviceInfo.managerAllowed is false", () => { + const listAppsCommandSpy = jest.spyOn(jest.requireActual("../../hw/listApps"), "default"); + const listInstalledAppsSpy = jest + .spyOn(ManagerAPI, "listInstalledApps") + .mockReturnValue(from([])); + + const transport = aTransportBuilder(); + const deviceInfo = aDeviceInfoBuilder({ + isOSU: false, + isBootloader: false, + managerAllowed: false, + targetId: 0x33200000, + }); + + listApps({ transport, deviceInfo }).subscribe(); + jest.advanceTimersByTime(1); + + expect(listAppsCommandSpy).not.toHaveBeenCalled(); + expect(listInstalledAppsSpy).toHaveBeenCalled(); + }); + + it("should return an observable that errors if ManagerAPI.getDeviceVersion() throws", () => { + jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); + jest.spyOn(ManagerAPI, "getDeviceVersion").mockImplementation(() => { + throw new Error("getDeviceVersion failed"); + }); + jest.spyOn(ManagerAPI, "catalogForDevice").mockReturnValue(Promise.resolve([])); + jest.spyOn(ManagerAPI, "getLanguagePackagesForDevice").mockReturnValue(Promise.resolve([])); + + const transport = aTransportBuilder(); + const deviceInfo = aDeviceInfoBuilder({ + isOSU: false, + isBootloader: false, + managerAllowed: false, + targetId: 0x33200000, + }); + + listApps({ transport, deviceInfo }).subscribe({ + error: err => { + expect(err.message).toBe("getDeviceVersion failed"); + }, + complete: () => { + fail("this observable should not complete"); + }, + }); + jest.advanceTimersByTime(1); + }); + + it("should return an observable that errors if ManagerAPI.catalogForDevice() throws", () => { + jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); + jest + .spyOn(ManagerAPI, "getDeviceVersion") + .mockReturnValue(Promise.resolve(aDeviceVersionBuilder())); + jest.spyOn(ManagerAPI, "catalogForDevice").mockImplementation(() => { + throw new Error("catalogForDevice failed"); + }); + jest.spyOn(ManagerAPI, "getLanguagePackagesForDevice").mockReturnValue(Promise.resolve([])); + + const transport = aTransportBuilder(); + const deviceInfo = aDeviceInfoBuilder({ + isOSU: false, + isBootloader: false, + managerAllowed: false, + targetId: 0x33200000, + }); + + listApps({ transport, deviceInfo }).subscribe({ + error: err => { + expect(err.message).toBe("catalogForDevice failed"); + }, + complete: () => { + fail("this observable should not complete"); + }, + }); + jest.advanceTimersByTime(1); + }); + + it("should return an observable that errors if ManagerAPI.getLanguagePackagesForDevice() throws", () => { + jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); + jest + .spyOn(ManagerAPI, "getDeviceVersion") + .mockReturnValue(Promise.resolve(aDeviceVersionBuilder())); + jest.spyOn(ManagerAPI, "catalogForDevice").mockReturnValue(Promise.resolve([])); + jest.spyOn(ManagerAPI, "getLanguagePackagesForDevice").mockImplementation(() => { + throw new Error("getLanguagePackagesForDevice failed"); + }); + + const transport = aTransportBuilder(); + const deviceInfo = aDeviceInfoBuilder({ + isOSU: false, + isBootloader: false, + managerAllowed: false, + targetId: 0x33200000, + }); + + listApps({ transport, deviceInfo }).subscribe({ + error: err => { + expect(err.message).toBe("getLanguagePackagesForDevice failed"); + }, + complete: () => { + fail("this observable should not complete"); + }, + }); + jest.advanceTimersByTime(1); + }); +}); diff --git a/libs/ledger-live-common/src/apps/listApps/v2.ts b/libs/ledger-live-common/src/apps/listApps/v2.ts index ae0a3f827d2f..bd139580c2a2 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.ts @@ -48,8 +48,8 @@ export const listApps = ({ } const deviceModelId = - (transport.deviceModel && transport.deviceModel.id) || - (deviceInfo && identifyTargetId(deviceInfo.targetId as number))?.id || + transport?.deviceModel?.id || + identifyTargetId(deviceInfo.targetId as number)?.id || deviceProxyModel; return new Observable(o => { @@ -245,7 +245,8 @@ export const listApps = ({ // Used to hide apps that are dev tools if user didn't opt-in. const appsListNames = catalogForDevice .filter( - ({ isDevTools, name }) => managerDevModeEnabled || !isDevTools || name in installedAppNames, + ({ isDevTools, name }) => + managerDevModeEnabled || !isDevTools || name in installedAppNames, ) .map(({ name }) => name); diff --git a/libs/ledger-live-common/src/mock/fixtures/aDeviceVersion.ts b/libs/ledger-live-common/src/mock/fixtures/aDeviceVersion.ts new file mode 100644 index 000000000000..bab8b34e7621 --- /dev/null +++ b/libs/ledger-live-common/src/mock/fixtures/aDeviceVersion.ts @@ -0,0 +1,20 @@ +import { DeviceVersion } from "@ledgerhq/types-live"; + +export const aDeviceVersionBuilder = (props?: Partial): DeviceVersion => { + return { + name: "Ledger Nano S", + device: 3, + providers: [], + id: 5, + display_name: "Ledger Nano S", + target_id: "0x31100004", + description: "Ledger Nano S", + mcu_versions: [1.7], + se_firmware_final_versions: [1.2], + osu_versions: [], + application_versions: [], + date_creation: "2020-04-30T13:50:00.000Z", + date_last_modified: "2020-04-30T13:50:00.000Z", + ...props, + }; +}; diff --git a/libs/ledger-live-common/src/mock/fixtures/aFinalFirmware.ts b/libs/ledger-live-common/src/mock/fixtures/aFinalFirmware.ts new file mode 100644 index 000000000000..397d4dfb6e36 --- /dev/null +++ b/libs/ledger-live-common/src/mock/fixtures/aFinalFirmware.ts @@ -0,0 +1,25 @@ +import { FinalFirmware } from "@ledgerhq/types-live"; + +export const aFinalFirmwareBuilder = (props?: Partial): FinalFirmware => { + return { + id: 1, + name: "FINAL", + description: null, + display_name: null, + notes: null, + perso: "", + firmware: "", + firmware_key: "", + hash: "", + date_creation: "", + date_last_modified: "", + device_versions: [], + providers: [], + version: "0", + se_firmware: 1, + osu_versions: [], + mcu_versions: [], + application_versions: [], + ...props, + }; +}; From aaa8cc2e928f19eb43d1641e02e4d2d1a93100f9 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 14:48:39 +0100 Subject: [PATCH 06/19] refactor(ManagerApiRepository): add getAppsByHash --- .../managerApi/entities/AppEntity.ts | 66 +++++++++++++++++++ .../repositories/HttpManagerApiRepository.ts | 39 ++++++++++- .../repositories/ManagerApiRepository.ts | 7 ++ libs/ledger-live-common/src/manager/api.ts | 37 ----------- 4 files changed, 111 insertions(+), 38 deletions(-) create mode 100644 libs/ledger-live-common/src/device-core/managerApi/entities/AppEntity.ts diff --git a/libs/ledger-live-common/src/device-core/managerApi/entities/AppEntity.ts b/libs/ledger-live-common/src/device-core/managerApi/entities/AppEntity.ts new file mode 100644 index 000000000000..36fd4c546010 --- /dev/null +++ b/libs/ledger-live-common/src/device-core/managerApi/entities/AppEntity.ts @@ -0,0 +1,66 @@ +import { Id } from "./Id"; + +export enum AppType { + app = "app", // NB Legacy from v1, drop after we default to v2. + currency = "currency", + plugin = "plugin", + tool = "tool", + swap = "swap", +} + +/** App is higher level on top of Application and ApplicationVersion +with all fields Live needs and in normalized form (but still serializable) */ +export type AppEntity = { + id: Id; + name: string; + displayName: string; + version: string; + currencyId: string | null | undefined; + description: string | null | undefined; + dateModified: string; + icon: string; + authorName: string | null | undefined; + supportURL: string | null | undefined; + contactURL: string | null | undefined; + sourceURL: string | null | undefined; + hash: string; + perso: string; + firmware: string; + firmware_key: string; + delete: string; + delete_key: string; + // we use names to identify an app + dependencies: string[]; + bytes: number | null | undefined; + warning: string | null | undefined; + // -1 if coin not in marketcap, otherwise index in the tickers list of https://countervalues.api.live.ledger.com/tickers + indexOfMarketCap: number; + isDevTools: boolean; + type: AppType; +}; + +export type ApplicationV2Entity = { + versionId: Id; + versionName: string; + versionDisplayName: string; + version: string; + currencyId: string; + description: string; + applicationType: AppType; + dateModified: string; + icon: string; + authorName: string; + supportURL: string; + contactURL: string; + sourceURL: string; + hash: string; + perso: string; + parentName: string | null; + firmware: string; + firmwareKey: string; + delete: string; + deleteKey: string; + bytes: number; + warning: string | null; + isDevTools: boolean; +}; \ No newline at end of file diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts index be78dc8f1144..1659d00e2132 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts @@ -2,10 +2,11 @@ import { makeLRUCache } from "@ledgerhq/live-network/cache"; import network from "@ledgerhq/live-network/network"; import { getUserHashes } from "../../../user"; import URL from "url"; -import { FirmwareNotRecognized } from "@ledgerhq/errors"; +import { FirmwareNotRecognized, NetworkDown } from "@ledgerhq/errors"; import { ManagerApiRepository } from "./ManagerApiRepository"; import { FinalFirmware, OsuFirmware } from "../entities/FirmwareUpdateContextEntity"; import { DeviceVersionEntity } from "../entities/DeviceVersionEntity"; +import { ApplicationV2Entity } from "../entities/AppEntity"; export class HttpManagerApiRepository implements ManagerApiRepository { private readonly managerApiBase: string; @@ -166,4 +167,40 @@ export class HttpManagerApiRepository implements ManagerApiRepository { }, id => String(id), ); + + /** + * Resolve applications details by hashes. + * Order of outputs matches order of inputs. + * If an application version is not found, a null is returned instead. + * If several versions match the same hash, only the latest one is returned. + * + * Given an array of hashes that we can obtain by either listInstalledApps in this same + * API (a websocket connection to a scriptrunner) or via direct apdus using hw/listApps.ts + * retrieve all the information needed from the backend for those applications. + */ + readonly getAppsByHash = makeLRUCache( + async hashes => { + const { + data, + }: { + data: Array; + } = await network({ + method: "POST", + url: URL.format({ + pathname: `${this.managerApiBase}/v2/apps/hash`, + query: { + livecommonversion: this.liveCommonVersion, + }, + }), + data: hashes, + }); + + if (!data || !Array.isArray(data)) { + throw new NetworkDown(""); + } + + return data; + }, + hashes => `${this.managerApiBase}_${hashes.join("-")}`, + ); } diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts index a9ad82ac7855..0a9725d2abe3 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts @@ -1,3 +1,4 @@ +import { ApplicationV2Entity } from "../entities/AppEntity"; import { DeviceVersionEntity } from "../entities/DeviceVersionEntity"; import { FinalFirmware, OsuFirmware } from "../entities/FirmwareUpdateContextEntity"; import { Id } from "../entities/Id"; @@ -33,4 +34,10 @@ export interface ManagerApiRepository { }): Promise; getFinalFirmwareById(id: number): Promise; + + getAppsByHash(hashes: string[]): Promise>; + + // TODO: catalogForDevice + + // TODO: getLanguagePackagesForDevice } diff --git a/libs/ledger-live-common/src/manager/api.ts b/libs/ledger-live-common/src/manager/api.ts index 1ccb1829a808..a686e2aa7a5c 100644 --- a/libs/ledger-live-common/src/manager/api.ts +++ b/libs/ledger-live-common/src/manager/api.ts @@ -348,42 +348,6 @@ const getFinalFirmwareById: (id: number) => Promise = makeLRUCach id => `${getEnv("MANAGER_API_BASE")}}_${String(id)}`, ); -/** - * Resolve applications details by hashes. - * Order of outputs matches order of inputs. - * If an application version is not found, a null is returned instead. - * If several versions match the same hash, only the latest one is returned. - * - * Given an array of hashes that we can obtain by either listInstalledApps in this same - * API (a websocket connection to a scriptrunner) or via direct apdus using hw/listApps.ts - * retrieve all the information needed from the backend for those applications. - */ -const getAppsByHash: (hashes: string[]) => Promise> = makeLRUCache( - async hashes => { - const { - data, - }: { - data: Array; - } = await network({ - method: "POST", - url: URL.format({ - pathname: `${getEnv("MANAGER_API_BASE")}/v2/apps/hash`, - query: { - livecommonversion, - }, - }), - data: hashes, - }); - - if (!data || !Array.isArray(data)) { - throw new NetworkDown(""); - } - - return data.map(appV2 => (appV2 ? mapApplicationV2ToApp(appV2) : null)); - }, - hashes => `${getEnv("MANAGER_API_BASE")}_${hashes.join("-")}`, -); - const getDeviceVersion: (targetId: string | number, provider: number) => Promise = makeLRUCache( async (targetId, provider) => { @@ -600,7 +564,6 @@ const API = { listInstalledApps, listCategories, getLanguagePackagesForDevice, - getAppsByHash, getCurrentOSU, compatibleMCUForDeviceInfo, findBestMCU, From 765d2f5516467de3d63cfb73ad40c528f0f95b77 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 14:48:59 +0100 Subject: [PATCH 07/19] refactor(ManagerApiRepository): implement StubManagerApiRepository --- .../repositories/StubManagerApiRepository.ts | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts new file mode 100644 index 000000000000..48691bf2a68b --- /dev/null +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts @@ -0,0 +1,122 @@ +import { DeviceVersionEntity } from "../entities/DeviceVersionEntity"; +import { FinalFirmware, OsuFirmware } from "../entities/FirmwareUpdateContextEntity"; +import { ManagerApiRepository } from "./ManagerApiRepository"; + +export class StubManagerApiRepository implements ManagerApiRepository { + readonly fetchLatestFirmware = () => { + const result: OsuFirmware = { + id: 0, + name: "", + display_name: "", + notes: "", + perso: "", + firmware: "", + firmware_key: "", + hash: "", + device_versions: [], + next_se_firmware_final_version: 0, + providers: [], + date_creation: "", + date_last_modified: "", + description: "", + previous_se_firmware_final_version: [], + }; + return Promise.resolve(result); + }; + + readonly fetchMcus = () => { + return Promise.resolve([]); + }; + + readonly getDeviceVersion = () => { + const result: DeviceVersionEntity = { + name: "Ledger Nano S", + device: 3, + providers: [], + id: 5, + display_name: "Ledger Nano S", + target_id: "0x31100004", + description: "Ledger Nano S", + mcu_versions: [1.7], + se_firmware_final_versions: [1.2], + osu_versions: [], + application_versions: [], + date_creation: "2020-04-30T13:50:00.000Z", + date_last_modified: "2020-04-30T13:50:00.000Z", + }; + return Promise.resolve(result); + }; + + readonly getCurrentOSU = () => { + const result: OsuFirmware = { + id: 0, + name: "", + display_name: "", + notes: "", + perso: "", + firmware: "", + firmware_key: "", + hash: "", + device_versions: [], + next_se_firmware_final_version: 0, + providers: [], + date_creation: "", + date_last_modified: "", + description: "", + previous_se_firmware_final_version: [], + }; + return Promise.resolve(result); + }; + + readonly getCurrentFirmware = () => { + const result: FinalFirmware = { + id: 1, + name: "FINAL", + description: null, + display_name: null, + notes: null, + perso: "", + firmware: "", + firmware_key: "", + hash: "", + date_creation: "", + date_last_modified: "", + device_versions: [], + providers: [], + version: "0", + se_firmware: 1, + osu_versions: [], + mcu_versions: [], + application_versions: [], + }; + return Promise.resolve(result); + }; + + readonly getFinalFirmwareById = () => { + const result: FinalFirmware = { + id: 1, + name: "FINAL", + description: null, + display_name: null, + notes: null, + perso: "", + firmware: "", + firmware_key: "", + hash: "", + date_creation: "", + date_last_modified: "", + device_versions: [], + providers: [], + version: "0", + se_firmware: 1, + osu_versions: [], + mcu_versions: [], + application_versions: [], + }; + return Promise.resolve(result); + }; + + readonly getAppsByHash = () => { + return Promise.resolve([]); + }; +} From 31ffbd899e005519cfd027c95d6fbb689b752411 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 14:50:21 +0100 Subject: [PATCH 08/19] refactor(listAppsV2): use managerApiRepository --- .../src/apps/listApps/v2.test.ts | 101 ++++++++++++++---- .../src/apps/listApps/v2.ts | 32 ++++-- .../getLatestFirmwareForDevice.test.ts | 1 + .../src/device/use-cases/listAppsUseCase.ts | 4 + 4 files changed, 108 insertions(+), 30 deletions(-) diff --git a/libs/ledger-live-common/src/apps/listApps/v2.test.ts b/libs/ledger-live-common/src/apps/listApps/v2.test.ts index 63296c229527..865fd45a3c4f 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.test.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.test.ts @@ -1,22 +1,46 @@ -import { UnexpectedBootloader } from "@ledgerhq/errors"; import { aTransportBuilder } from "@ledgerhq/hw-transport-mocker"; import { listApps } from "./v2"; import { aDeviceInfoBuilder } from "../../mock/fixtures/aDeviceInfo"; import ManagerAPI from "../../manager/api"; -import { from } from "rxjs"; import { aDeviceVersionBuilder } from "../../mock/fixtures/aDeviceVersion"; +import { ManagerApiRepository } from "../../device-core/managerApi/repositories/ManagerApiRepository"; +import { StubManagerApiRepository } from "../../device-core/managerApi/repositories/StubManagerApiRepository"; +import { from } from "rxjs"; +import { UnexpectedBootloader } from "@ledgerhq/errors"; jest.useFakeTimers(); describe("listApps v2", () => { + let mockedGetDeviceVersion; + let mockedManagerApiRepository: ManagerApiRepository; + beforeEach(() => { + jest + .spyOn( + jest.requireActual("../../device/use-cases/getLatestFirmwareForDeviceUseCase"), + "getLatestFirmwareForDeviceUseCase", + ) + .mockReturnValue(Promise.resolve(null)); + mockedGetDeviceVersion = jest.fn().mockReturnValue(Promise.resolve(aDeviceVersionBuilder())); + mockedManagerApiRepository = { + ...new StubManagerApiRepository(), + getDeviceVersion: mockedGetDeviceVersion, + }; + }); + + afterEach(() => { + jest.clearAllTimers(); jest.clearAllMocks(); }); it("should return an observable that errors if deviceInfo.isOSU is true", done => { const transport = aTransportBuilder(); const deviceInfo = aDeviceInfoBuilder({ isOSU: true, isBootloader: false }); - const result = listApps({ transport, deviceInfo }); + const result = listApps({ + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + }); result.subscribe({ error: err => { @@ -33,7 +57,11 @@ describe("listApps v2", () => { it("should return an observable that errors if deviceInfo.isBootloader is true", done => { const transport = aTransportBuilder(); const deviceInfo = aDeviceInfoBuilder({ isOSU: false, isBootloader: true }); - const result = listApps({ transport, deviceInfo }); + const result = listApps({ + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + }); result.subscribe({ error: err => { @@ -51,7 +79,11 @@ describe("listApps v2", () => { const transport = aTransportBuilder({ deviceModel: null }); const deviceInfo = aDeviceInfoBuilder({ isOSU: false, isBootloader: false, targetId: 0 }); - const result = listApps({ transport, deviceInfo }); + const result = listApps({ + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + }); result.subscribe({ error: err => { @@ -65,11 +97,14 @@ describe("listApps v2", () => { jest.advanceTimersByTime(1); }); - it("should call hwListApps() if deviceInfo.managerAllowed is true", () => { + it("should call hwListApps() if deviceInfo.managerAllowed is true", done => { const listAppsCommandSpy = jest .spyOn(jest.requireActual("../../hw/listApps"), "default") .mockReturnValue(Promise.resolve([])); const listInstalledAppsSpy = jest.spyOn(ManagerAPI, "listInstalledApps"); + mockedGetDeviceVersion.mockReturnValue(Promise.resolve(aDeviceVersionBuilder())); + jest.spyOn(ManagerAPI, "catalogForDevice").mockReturnValue(Promise.resolve([])); + jest.spyOn(ManagerAPI, "getLanguagePackagesForDevice").mockReturnValue(Promise.resolve([])); const transport = aTransportBuilder(); const deviceInfo = aDeviceInfoBuilder({ @@ -79,7 +114,18 @@ describe("listApps v2", () => { targetId: 0x33200000, }); - listApps({ transport, deviceInfo }).subscribe({}); + listApps({ + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + }).subscribe({ + complete: () => { + done(); + }, + error: () => { + done(); + }, + }); jest.advanceTimersByTime(1); expect(listAppsCommandSpy).toHaveBeenCalled(); @@ -100,16 +146,20 @@ describe("listApps v2", () => { targetId: 0x33200000, }); - listApps({ transport, deviceInfo }).subscribe(); + listApps({ + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + }).subscribe(); jest.advanceTimersByTime(1); expect(listAppsCommandSpy).not.toHaveBeenCalled(); expect(listInstalledAppsSpy).toHaveBeenCalled(); }); - it("should return an observable that errors if ManagerAPI.getDeviceVersion() throws", () => { + it("should return an observable that errors if getDeviceVersion() throws", done => { jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); - jest.spyOn(ManagerAPI, "getDeviceVersion").mockImplementation(() => { + mockedGetDeviceVersion.mockImplementation(() => { throw new Error("getDeviceVersion failed"); }); jest.spyOn(ManagerAPI, "catalogForDevice").mockReturnValue(Promise.resolve([])); @@ -123,9 +173,14 @@ describe("listApps v2", () => { targetId: 0x33200000, }); - listApps({ transport, deviceInfo }).subscribe({ + listApps({ + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + }).subscribe({ error: err => { expect(err.message).toBe("getDeviceVersion failed"); + done(); }, complete: () => { fail("this observable should not complete"); @@ -134,11 +189,8 @@ describe("listApps v2", () => { jest.advanceTimersByTime(1); }); - it("should return an observable that errors if ManagerAPI.catalogForDevice() throws", () => { + it("should return an observable that errors if ManagerAPI.catalogForDevice() throws", done => { jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); - jest - .spyOn(ManagerAPI, "getDeviceVersion") - .mockReturnValue(Promise.resolve(aDeviceVersionBuilder())); jest.spyOn(ManagerAPI, "catalogForDevice").mockImplementation(() => { throw new Error("catalogForDevice failed"); }); @@ -152,9 +204,14 @@ describe("listApps v2", () => { targetId: 0x33200000, }); - listApps({ transport, deviceInfo }).subscribe({ + listApps({ + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + }).subscribe({ error: err => { expect(err.message).toBe("catalogForDevice failed"); + done(); }, complete: () => { fail("this observable should not complete"); @@ -163,11 +220,8 @@ describe("listApps v2", () => { jest.advanceTimersByTime(1); }); - it("should return an observable that errors if ManagerAPI.getLanguagePackagesForDevice() throws", () => { + it("should return an observable that errors if ManagerAPI.getLanguagePackagesForDevice() throws", done => { jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); - jest - .spyOn(ManagerAPI, "getDeviceVersion") - .mockReturnValue(Promise.resolve(aDeviceVersionBuilder())); jest.spyOn(ManagerAPI, "catalogForDevice").mockReturnValue(Promise.resolve([])); jest.spyOn(ManagerAPI, "getLanguagePackagesForDevice").mockImplementation(() => { throw new Error("getLanguagePackagesForDevice failed"); @@ -181,9 +235,14 @@ describe("listApps v2", () => { targetId: 0x33200000, }); - listApps({ transport, deviceInfo }).subscribe({ + listApps({ + transport, + deviceInfo, + managerApiRepository: mockedManagerApiRepository, + }).subscribe({ error: err => { expect(err.message).toBe("getLanguagePackagesForDevice failed"); + done(); }, complete: () => { fail("this observable should not complete"); diff --git a/libs/ledger-live-common/src/apps/listApps/v2.ts b/libs/ledger-live-common/src/apps/listApps/v2.ts index bd139580c2a2..c8e2309551e1 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.ts @@ -17,6 +17,8 @@ import ManagerAPI from "../../manager/api"; import getDeviceName from "../../hw/getDeviceName"; import { getLatestFirmwareForDeviceUseCase } from "../../device/use-cases/getLatestFirmwareForDeviceUseCase"; import { getProviderIdPure } from "../../manager/provider"; +import { ManagerApiRepository } from "../../device-core/managerApi/repositories/ManagerApiRepository"; +import { mapApplicationV2ToApp } from "../polyfill"; // Hash discrepancies for these apps do NOT indicate a potential update, // these apps have a mechanism that makes their hash change every time. @@ -31,6 +33,7 @@ type ListAppsParams = { deviceProxyModel?: DeviceModelId; managerDevModeEnabled?: boolean; forceProvider?: number; + managerApiRepository: ManagerApiRepository; }; export const listApps = ({ @@ -39,6 +42,7 @@ export const listApps = ({ deviceProxyModel, managerDevModeEnabled, forceProvider, + managerApiRepository, }: ListAppsParams): Observable => { const tracer = new LocalTracer("list-apps", { transport: transport.getTraceContext() }); tracer.trace("Using new version", { deviceInfo }); @@ -112,7 +116,11 @@ export const listApps = ({ const listAppsAndMatchesPromise = filteredListAppsPromise.then(result => { const hashes = result.map(({ hash }) => hash); - const matches = result.length ? ManagerAPI.getAppsByHash(hashes) : []; + const matches = result.length + ? managerApiRepository + .getAppsByHash(hashes) + .then(matches => matches.map(appV2 => (appV2 ? mapApplicationV2ToApp(appV2) : null))) + : []; // TODO: replace by managerApiRepository return Promise.all([result, matches]); }); @@ -121,21 +129,26 @@ export const listApps = ({ * for the device */ - const deviceVersionPromise = ManagerAPI.getDeviceVersion(deviceInfo.targetId, provider); + const deviceVersionPromise = managerApiRepository.getDeviceVersion({ + targetId: deviceInfo.targetId, + providerId: provider, + }); const currentFirmwarePromise = deviceVersionPromise.then(deviceVersion => - ManagerAPI.getCurrentFirmware({ + managerApiRepository.getCurrentFirmware({ deviceId: deviceVersion.id, version: deviceInfo.version, - provider, + providerId: provider, }), ); const latestFirmwarePromise = currentFirmwarePromise.then(currentFirmware => - getLatestFirmwareForDeviceUseCase(deviceInfo).then(updateAvailable => ({ - ...currentFirmware, - updateAvailable, - })), + getLatestFirmwareForDeviceUseCase(deviceInfo, managerApiRepository).then( + updateAvailable => ({ + ...currentFirmware, + updateAvailable, + }), + ), ); /** @@ -143,6 +156,7 @@ export const listApps = ({ */ const catalogForDevicesPromise = ManagerAPI.catalogForDevice({ + // TODO: replace by repository provider, targetId: deviceInfo.targetId, firmwareVersion: deviceInfo.version, @@ -160,7 +174,7 @@ export const listApps = ({ * Sequence 5: get language pack available for the device */ - const languagePackForDevicePromise = ManagerAPI.getLanguagePackagesForDevice(deviceInfo); + const languagePackForDevicePromise = ManagerAPI.getLanguagePackagesForDevice(deviceInfo); // TODO: replace by repository /* Running all sequences 1 2 3 4 5 defined above in parallel */ const [[listApps, matches], catalogForDevice, firmware, sortedCryptoCurrencies, languages] = diff --git a/libs/ledger-live-common/src/device-core/managerApi/use-cases/getLatestFirmwareForDevice.test.ts b/libs/ledger-live-common/src/device-core/managerApi/use-cases/getLatestFirmwareForDevice.test.ts index c3d09bdc83f4..073fa8d00cbc 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/use-cases/getLatestFirmwareForDevice.test.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/use-cases/getLatestFirmwareForDevice.test.ts @@ -15,6 +15,7 @@ const mockedManagerApiRepository: ManagerApiRepository = { getCurrentOSU: mockedGetCurrentOSU, getCurrentFirmware: jest.fn(), getFinalFirmwareById: jest.fn(), + getAppsByHash: jest.fn(), }; // TODO: complete this test diff --git a/libs/ledger-live-common/src/device/use-cases/listAppsUseCase.ts b/libs/ledger-live-common/src/device/use-cases/listAppsUseCase.ts index 2f77fc26111d..11ffbf3bf8b8 100644 --- a/libs/ledger-live-common/src/device/use-cases/listAppsUseCase.ts +++ b/libs/ledger-live-common/src/device/use-cases/listAppsUseCase.ts @@ -9,6 +9,8 @@ import { DeviceModelId } from "@ledgerhq/devices"; import { App } from "@ledgerhq/types-live"; import installApp from "../../hw/installApp"; import uninstallApp from "../../hw/uninstallApp"; +import { ManagerApiRepository } from "../../device-core/managerApi/repositories/ManagerApiRepository"; +import { HttpManagerApiRepositoryFactory } from "../factories/HttpManagerApiRepositoryFactory"; export const execWithTransport = (transport: Transport): Exec => @@ -33,6 +35,7 @@ export const enableListAppsV2 = (enabled: boolean) => (listAppsV2Enabled = enabl export function listAppsUseCase( transport: Transport, deviceInfo: DeviceInfo, + managerApiRepository: ManagerApiRepository = HttpManagerApiRepositoryFactory.getInstance(), ): Observable { return listAppsV2Enabled ? listAppsV1(transport, deviceInfo) @@ -40,5 +43,6 @@ export function listAppsUseCase( transport, deviceInfo, deviceProxyModel: getEnv("DEVICE_PROXY_MODEL") as DeviceModelId, + managerApiRepository, }); } From 68f02188f6305eb7ce7be6edf708a580dec1aa89 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 15:44:17 +0100 Subject: [PATCH 09/19] refactor(ManagerApiRepository): catalogForDevice, getLanguagePackagesForDevice --- .../src/apps/listApps/v2.test.ts | 32 +++---- .../src/apps/listApps/v2.ts | 25 +++--- .../entities/LanguagePackageEntity.ts | 21 +++++ .../repositories/HttpManagerApiRepository.ts | 90 ++++++++++++++++--- .../repositories/ManagerApiRepository.ts | 28 +++++- .../repositories/StubManagerApiRepository.ts | 10 +++ .../use-cases/getProviderIdUseCase.ts | 23 +++++ libs/ledger-live-common/src/manager/api.ts | 42 --------- .../src/manager/provider.ts | 25 +----- 9 files changed, 189 insertions(+), 107 deletions(-) create mode 100644 libs/ledger-live-common/src/device-core/managerApi/entities/LanguagePackageEntity.ts create mode 100644 libs/ledger-live-common/src/device-core/managerApi/use-cases/getProviderIdUseCase.ts diff --git a/libs/ledger-live-common/src/apps/listApps/v2.test.ts b/libs/ledger-live-common/src/apps/listApps/v2.test.ts index 865fd45a3c4f..2597d9d691de 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.test.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.test.ts @@ -2,7 +2,6 @@ import { aTransportBuilder } from "@ledgerhq/hw-transport-mocker"; import { listApps } from "./v2"; import { aDeviceInfoBuilder } from "../../mock/fixtures/aDeviceInfo"; import ManagerAPI from "../../manager/api"; -import { aDeviceVersionBuilder } from "../../mock/fixtures/aDeviceVersion"; import { ManagerApiRepository } from "../../device-core/managerApi/repositories/ManagerApiRepository"; import { StubManagerApiRepository } from "../../device-core/managerApi/repositories/StubManagerApiRepository"; import { from } from "rxjs"; @@ -11,7 +10,6 @@ import { UnexpectedBootloader } from "@ledgerhq/errors"; jest.useFakeTimers(); describe("listApps v2", () => { - let mockedGetDeviceVersion; let mockedManagerApiRepository: ManagerApiRepository; beforeEach(() => { @@ -21,11 +19,8 @@ describe("listApps v2", () => { "getLatestFirmwareForDeviceUseCase", ) .mockReturnValue(Promise.resolve(null)); - mockedGetDeviceVersion = jest.fn().mockReturnValue(Promise.resolve(aDeviceVersionBuilder())); - mockedManagerApiRepository = { - ...new StubManagerApiRepository(), - getDeviceVersion: mockedGetDeviceVersion, - }; + + mockedManagerApiRepository = new StubManagerApiRepository(); }); afterEach(() => { @@ -102,9 +97,6 @@ describe("listApps v2", () => { .spyOn(jest.requireActual("../../hw/listApps"), "default") .mockReturnValue(Promise.resolve([])); const listInstalledAppsSpy = jest.spyOn(ManagerAPI, "listInstalledApps"); - mockedGetDeviceVersion.mockReturnValue(Promise.resolve(aDeviceVersionBuilder())); - jest.spyOn(ManagerAPI, "catalogForDevice").mockReturnValue(Promise.resolve([])); - jest.spyOn(ManagerAPI, "getLanguagePackagesForDevice").mockReturnValue(Promise.resolve([])); const transport = aTransportBuilder(); const deviceInfo = aDeviceInfoBuilder({ @@ -159,11 +151,9 @@ describe("listApps v2", () => { it("should return an observable that errors if getDeviceVersion() throws", done => { jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); - mockedGetDeviceVersion.mockImplementation(() => { + jest.spyOn(mockedManagerApiRepository, "getDeviceVersion").mockImplementation(() => { throw new Error("getDeviceVersion failed"); }); - jest.spyOn(ManagerAPI, "catalogForDevice").mockReturnValue(Promise.resolve([])); - jest.spyOn(ManagerAPI, "getLanguagePackagesForDevice").mockReturnValue(Promise.resolve([])); const transport = aTransportBuilder(); const deviceInfo = aDeviceInfoBuilder({ @@ -189,12 +179,11 @@ describe("listApps v2", () => { jest.advanceTimersByTime(1); }); - it("should return an observable that errors if ManagerAPI.catalogForDevice() throws", done => { + it("should return an observable that errors if catalogForDevice() throws", done => { jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); - jest.spyOn(ManagerAPI, "catalogForDevice").mockImplementation(() => { + jest.spyOn(mockedManagerApiRepository, "catalogForDevice").mockImplementation(() => { throw new Error("catalogForDevice failed"); }); - jest.spyOn(ManagerAPI, "getLanguagePackagesForDevice").mockReturnValue(Promise.resolve([])); const transport = aTransportBuilder(); const deviceInfo = aDeviceInfoBuilder({ @@ -220,12 +209,13 @@ describe("listApps v2", () => { jest.advanceTimersByTime(1); }); - it("should return an observable that errors if ManagerAPI.getLanguagePackagesForDevice() throws", done => { + it("should return an observable that errors if getLanguagePackagesForDevice() throws", done => { jest.spyOn(ManagerAPI, "listInstalledApps").mockReturnValue(from([])); - jest.spyOn(ManagerAPI, "catalogForDevice").mockReturnValue(Promise.resolve([])); - jest.spyOn(ManagerAPI, "getLanguagePackagesForDevice").mockImplementation(() => { - throw new Error("getLanguagePackagesForDevice failed"); - }); + jest + .spyOn(mockedManagerApiRepository, "getLanguagePackagesForDevice") + .mockImplementation(() => { + throw new Error("getLanguagePackagesForDevice failed"); + }); const transport = aTransportBuilder(); const deviceInfo = aDeviceInfoBuilder({ diff --git a/libs/ledger-live-common/src/apps/listApps/v2.ts b/libs/ledger-live-common/src/apps/listApps/v2.ts index c8e2309551e1..ef22ee2bb6d6 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.ts @@ -16,7 +16,7 @@ import ManagerAPI from "../../manager/api"; import getDeviceName from "../../hw/getDeviceName"; import { getLatestFirmwareForDeviceUseCase } from "../../device/use-cases/getLatestFirmwareForDeviceUseCase"; -import { getProviderIdPure } from "../../manager/provider"; +import { getProviderIdUseCase } from "../../device-core/managerApi/use-cases/getProviderIdUseCase"; import { ManagerApiRepository } from "../../device-core/managerApi/repositories/ManagerApiRepository"; import { mapApplicationV2ToApp } from "../polyfill"; @@ -59,7 +59,7 @@ export const listApps = ({ return new Observable(o => { let sub: Subscription; async function main() { - const provider = getProviderIdPure({ deviceInfo, forceProvider }); + const provider = getProviderIdUseCase({ deviceInfo, forceProvider }); if (!deviceModelId) throw new Error("Bad usage of listAppsV2: missing deviceModelId"); const deviceModel = getDeviceModel(deviceModelId); @@ -86,6 +86,7 @@ export const listApps = ({ tracer.trace("Using scriptrunner listapps"); listAppsResponsePromise = new Promise((resolve, reject) => { + // TODO: migrate this ManagerAPI call to ManagerApiRepository sub = ManagerAPI.listInstalledApps(transport, { targetId: deviceInfo.targetId, perso: "perso_11", @@ -120,7 +121,7 @@ export const listApps = ({ ? managerApiRepository .getAppsByHash(hashes) .then(matches => matches.map(appV2 => (appV2 ? mapApplicationV2ToApp(appV2) : null))) - : []; // TODO: replace by managerApiRepository + : []; return Promise.all([result, matches]); }); @@ -155,12 +156,13 @@ export const listApps = ({ * Sequence 3: get catalog of apps available for the device */ - const catalogForDevicesPromise = ManagerAPI.catalogForDevice({ - // TODO: replace by repository - provider, - targetId: deviceInfo.targetId, - firmwareVersion: deviceInfo.version, - }); + const catalogForDevicesPromise = managerApiRepository + .catalogForDevice({ + provider, + targetId: deviceInfo.targetId, + firmwareVersion: deviceInfo.version, + }) + .then(apps => apps.map(mapApplicationV2ToApp)); /** * Sequence 4: list all currencies, sorted by market cp @@ -174,7 +176,10 @@ export const listApps = ({ * Sequence 5: get language pack available for the device */ - const languagePackForDevicePromise = ManagerAPI.getLanguagePackagesForDevice(deviceInfo); // TODO: replace by repository + const languagePackForDevicePromise = managerApiRepository.getLanguagePackagesForDevice( + deviceInfo, + forceProvider, + ); /* Running all sequences 1 2 3 4 5 defined above in parallel */ const [[listApps, matches], catalogForDevice, firmware, sortedCryptoCurrencies, languages] = diff --git a/libs/ledger-live-common/src/device-core/managerApi/entities/LanguagePackageEntity.ts b/libs/ledger-live-common/src/device-core/managerApi/entities/LanguagePackageEntity.ts new file mode 100644 index 000000000000..72c57ba13b7f --- /dev/null +++ b/libs/ledger-live-common/src/device-core/managerApi/entities/LanguagePackageEntity.ts @@ -0,0 +1,21 @@ +export type Language = "french" | "english" | "spanish"; + +export type LanguagePackageEntity = { + language: Language; + languagePackageVersionId: number; + version: string; // "0.0.1" + language_package_id: number; + apdu_install_url: string; + apdu_uninstall_url: string; // <= Useless + device_versions: number[]; + se_firmware_final_versions: number[]; + bytes: number; + date_creation: string; + date_last_modified: string; +}; + +export type LanguagePackageResponseEntity = { + id: number; + language: Language; + language_package_version: LanguagePackageEntity[]; +}; diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts index 1659d00e2132..7ba9c3cc4743 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts @@ -7,6 +7,12 @@ import { ManagerApiRepository } from "./ManagerApiRepository"; import { FinalFirmware, OsuFirmware } from "../entities/FirmwareUpdateContextEntity"; import { DeviceVersionEntity } from "../entities/DeviceVersionEntity"; import { ApplicationV2Entity } from "../entities/AppEntity"; +import { DeviceInfoEntity } from "../entities/DeviceInfoEntity"; +import { + LanguagePackageEntity, + LanguagePackageResponseEntity, +} from "../entities/LanguagePackageEntity"; +import { getProviderIdUseCase } from "../use-cases/getProviderIdUseCase"; export class HttpManagerApiRepository implements ManagerApiRepository { private readonly managerApiBase: string; @@ -168,16 +174,6 @@ export class HttpManagerApiRepository implements ManagerApiRepository { id => String(id), ); - /** - * Resolve applications details by hashes. - * Order of outputs matches order of inputs. - * If an application version is not found, a null is returned instead. - * If several versions match the same hash, only the latest one is returned. - * - * Given an array of hashes that we can obtain by either listInstalledApps in this same - * API (a websocket connection to a scriptrunner) or via direct apdus using hw/listApps.ts - * retrieve all the information needed from the backend for those applications. - */ readonly getAppsByHash = makeLRUCache( async hashes => { const { @@ -203,4 +199,78 @@ export class HttpManagerApiRepository implements ManagerApiRepository { }, hashes => `${this.managerApiBase}_${hashes.join("-")}`, ); + + readonly catalogForDevice = makeLRUCache( + async params => { + const { provider, targetId, firmwareVersion } = params; + const { + data, + }: { + data: Array; + } = await network({ + method: "GET", + url: URL.format({ + pathname: `${this.managerApiBase}/v2/apps/by-target`, + query: { + livecommonversion: this.liveCommonVersion, + provider, + target_id: targetId, + firmware_version_name: firmwareVersion, + }, + }), + }); + + if (!data || !Array.isArray(data)) { + throw new NetworkDown(""); + } + + return data; + }, + a => `${this.managerApiBase}_${a.provider}_${a.targetId}_${a.firmwareVersion}`, + ); + + readonly getLanguagePackagesForDevice = async ( + deviceInfo: DeviceInfoEntity, + forceProvider?: number, + ) => { + const deviceVersion = await this.getDeviceVersion({ + deviceInfo: deviceInfo.targetId, + provider: getProviderIdUseCase({ deviceInfo, forceProvider }), + }); + + const seFirmwareVersion = await this.getCurrentFirmware({ + version: deviceInfo.version, + deviceId: deviceVersion.id, + provider: getProviderIdUseCase({ deviceInfo, forceProvider }), + }); + + const { data }: { data: LanguagePackageResponseEntity[] } = await network({ + method: "GET", + url: URL.format({ + pathname: `${this.managerApiBase}/language-package`, + query: { + livecommonversion: this.liveCommonVersion, + }, + }), + }); + + const allPackages: LanguagePackageEntity[] = data.reduce( + (acc, response) => [ + ...acc, + ...response.language_package_version.map(p => ({ + ...p, + language: response.language, + })), + ], + [] as LanguagePackageEntity[], + ); + + const packages = allPackages.filter( + pack => + pack.device_versions.includes(deviceVersion.id) && + pack.se_firmware_final_versions.includes(seFirmwareVersion.id), + ); + + return packages; + }; } diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts index 0a9725d2abe3..8a8ff8af1e02 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/ManagerApiRepository.ts @@ -1,7 +1,9 @@ import { ApplicationV2Entity } from "../entities/AppEntity"; +import { DeviceInfoEntity } from "../entities/DeviceInfoEntity"; import { DeviceVersionEntity } from "../entities/DeviceVersionEntity"; import { FinalFirmware, OsuFirmware } from "../entities/FirmwareUpdateContextEntity"; import { Id } from "../entities/Id"; +import { LanguagePackageEntity } from "../entities/LanguagePackageEntity"; export interface ManagerApiRepository { fetchLatestFirmware(params: { @@ -35,9 +37,31 @@ export interface ManagerApiRepository { getFinalFirmwareById(id: number): Promise; + /** + * Resolve applications details by hashes. + * Order of outputs matches order of inputs. + * If an application version is not found, a null is returned instead. + * If several versions match the same hash, only the latest one is returned. + * + * Given an array of hashes that we can obtain by either listInstalledApps in this same + * API (a websocket connection to a scriptrunner) or via direct apdus using hw/listApps.ts + * retrieve all the information needed from the backend for those applications. + */ getAppsByHash(hashes: string[]): Promise>; - // TODO: catalogForDevice + /** + * Return a list of App that are available for a given firmware version on a provider. + * Prevents the call to ManagerAPI.listApps which includes all versions of all apps and + * was causing slower access to the manager. + */ + catalogForDevice: (params: { + provider: number; + targetId: number | string; + firmwareVersion: string; + }) => Promise>; - // TODO: getLanguagePackagesForDevice + getLanguagePackagesForDevice: ( + deviceInfo: DeviceInfoEntity, + forceProvider?: number, + ) => Promise; } diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts index 48691bf2a68b..a2e06ccba8ec 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts @@ -1,5 +1,7 @@ +import { DeviceInfoEntity } from "../entities/DeviceInfoEntity"; import { DeviceVersionEntity } from "../entities/DeviceVersionEntity"; import { FinalFirmware, OsuFirmware } from "../entities/FirmwareUpdateContextEntity"; +import { LanguagePackageEntity } from "../entities/LanguagePackageEntity"; import { ManagerApiRepository } from "./ManagerApiRepository"; export class StubManagerApiRepository implements ManagerApiRepository { @@ -119,4 +121,12 @@ export class StubManagerApiRepository implements ManagerApiRepository { readonly getAppsByHash = () => { return Promise.resolve([]); }; + + readonly catalogForDevice = () => { + return Promise.resolve([]); + }; + + readonly getLanguagePackagesForDevice = () => { + return Promise.resolve([]); + }; } diff --git a/libs/ledger-live-common/src/device-core/managerApi/use-cases/getProviderIdUseCase.ts b/libs/ledger-live-common/src/device-core/managerApi/use-cases/getProviderIdUseCase.ts new file mode 100644 index 000000000000..ef25a59c0de8 --- /dev/null +++ b/libs/ledger-live-common/src/device-core/managerApi/use-cases/getProviderIdUseCase.ts @@ -0,0 +1,23 @@ +import { DeviceInfoEntity } from "../entities/DeviceInfoEntity"; + +export const PROVIDERS: Record = { + das: 2, + club: 3, + shitcoins: 4, + ee: 5, +}; + +export function getProviderIdUseCase({ + deviceInfo, + forceProvider, +}: { + deviceInfo: DeviceInfoEntity | undefined | null; + forceProvider?: number; +}): number { + if (forceProvider && forceProvider !== 1) return forceProvider; + if (!deviceInfo) return 1; + const { providerName } = deviceInfo; + const maybeProvider = providerName && PROVIDERS[providerName]; + if (maybeProvider) return maybeProvider; + return 1; +} diff --git a/libs/ledger-live-common/src/manager/api.ts b/libs/ledger-live-common/src/manager/api.ts index a686e2aa7a5c..b0715542a1d5 100644 --- a/libs/ledger-live-common/src/manager/api.ts +++ b/libs/ledger-live-common/src/manager/api.ts @@ -15,9 +15,7 @@ import { makeLRUCache } from "@ledgerhq/live-network/cache"; import network from "@ledgerhq/live-network/network"; import { log } from "@ledgerhq/logs"; import { - App, Application, - ApplicationV2, ApplicationVersion, Category, DeviceInfo, @@ -36,7 +34,6 @@ import { catchError, map } from "rxjs/operators"; import semver from "semver"; import URL from "url"; import { version as livecommonversion } from "../../package.json"; -import { mapApplicationV2ToApp } from "../apps/polyfill"; import { getEnv } from "@ledgerhq/live-env"; import { createDeviceSocket } from "../socket"; import { @@ -135,44 +132,6 @@ const applicationsByDevice: (params: { }`, ); -/** - * Return a list of App that are available for a given firmware version on a provider. - * Prevents the call to ManagerAPI.listApps which includes all versions of all apps and - * was causing slower access to the manager. - */ -const catalogForDevice: (params: { - provider: number; - targetId: number | string; - firmwareVersion: string; -}) => Promise> = makeLRUCache( - async params => { - const { provider, targetId, firmwareVersion } = params; - const { - data, - }: { - data: Array; - } = await network({ - method: "GET", - url: URL.format({ - pathname: `${getEnv("MANAGER_API_BASE")}/v2/apps/by-target`, - query: { - livecommonversion, - provider, - target_id: targetId, - firmware_version_name: firmwareVersion, - }, - }), - }); - - if (!data || !Array.isArray(data)) { - throw new NetworkDown(""); - } - - return data.map(mapApplicationV2ToApp); - }, - a => `${getEnv("MANAGER_API_BASE")}_${a.provider}_${a.targetId}_${a.firmwareVersion}`, -); - const listApps: () => Promise> = makeLRUCache( async () => { const { data } = await network({ @@ -559,7 +518,6 @@ function retrieveMcuVersion(finalFirmware: FinalFirmware): Promise = { - das: 2, - club: 3, - shitcoins: 4, - ee: 5, -}; - -export function getProviderIdPure({ - deviceInfo, - forceProvider, -}: { - deviceInfo: DeviceInfo | undefined | null; - forceProvider?: number; -}): number { - if (forceProvider && forceProvider !== 1) return forceProvider; - if (!deviceInfo) return 1; - const { providerName } = deviceInfo; - const maybeProvider = providerName && PROVIDERS[providerName]; - if (maybeProvider) return maybeProvider; - return 1; -} +import { getProviderIdUseCase } from "../device-core/managerApi/use-cases/getProviderIdUseCase"; +export { PROVIDERS } from "../device-core/managerApi/use-cases/getProviderIdUseCase"; export function getProviderId(deviceInfo: DeviceInfo | undefined | null) { - return getProviderIdPure({ deviceInfo, forceProvider: getEnv("FORCE_PROVIDER") }); + return getProviderIdUseCase({ deviceInfo, forceProvider: getEnv("FORCE_PROVIDER") }); } From 981595ae355cfbaa192f5b002f72cf67fa02d5df Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 15:44:53 +0100 Subject: [PATCH 10/19] test(getLatestFirmwareForDevice): replace mocks by stub api repository --- .../getLatestFirmwareForDevice.test.ts | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/libs/ledger-live-common/src/device-core/managerApi/use-cases/getLatestFirmwareForDevice.test.ts b/libs/ledger-live-common/src/device-core/managerApi/use-cases/getLatestFirmwareForDevice.test.ts index 073fa8d00cbc..e216f532ed84 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/use-cases/getLatestFirmwareForDevice.test.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/use-cases/getLatestFirmwareForDevice.test.ts @@ -3,26 +3,17 @@ import { ManagerApiRepository } from "../repositories/ManagerApiRepository"; import { aDeviceInfoBuilder } from "../../../mock/fixtures/aDeviceInfo"; import { DeviceVersion, OsuFirmware } from "@ledgerhq/types-live"; import { UnknownMCU } from "@ledgerhq/errors"; +import { StubManagerApiRepository } from "../repositories/StubManagerApiRepository"; jest.mock("../repositories/ManagerApiRepository"); -const mockedFetchMcus = jest.fn(); -const mockedGetDeviceVersion = jest.fn(); -const mockedGetCurrentOSU = jest.fn(); -const mockedManagerApiRepository: ManagerApiRepository = { - fetchLatestFirmware: jest.fn(), - fetchMcus: mockedFetchMcus, - getDeviceVersion: mockedGetDeviceVersion, - getCurrentOSU: mockedGetCurrentOSU, - getCurrentFirmware: jest.fn(), - getFinalFirmwareById: jest.fn(), - getAppsByHash: jest.fn(), -}; - // TODO: complete this test describe("getLatestFirmwareForDevice", () => { + let mockedManagerApiRepository: ManagerApiRepository; + beforeEach(() => { // Clear the methods we are using in our test - mockedFetchMcus.mockClear(); + jest.resetAllMocks(); + mockedManagerApiRepository = new StubManagerApiRepository(); }); test("Error throw for unknown mcu", async () => { @@ -30,9 +21,11 @@ describe("getLatestFirmwareForDevice", () => { // Using a fixture builder - we could create that for all our entities const deviceInfo = aDeviceInfoBuilder({ mcuVersion: "42", isOSU: true }); - mockedFetchMcus.mockResolvedValue([]); - mockedGetDeviceVersion.mockResolvedValue({ id: 42 } as DeviceVersion); - mockedGetCurrentOSU.mockResolvedValue({} as OsuFirmware); + jest.spyOn(mockedManagerApiRepository, "fetchMcus").mockResolvedValue([]); + jest + .spyOn(mockedManagerApiRepository, "getDeviceVersion") + .mockResolvedValue({ id: 42 } as DeviceVersion); + jest.spyOn(mockedManagerApiRepository, "getCurrentOSU").mockResolvedValue({} as OsuFirmware); const params = { deviceInfo, From e267fdda56e2a6e26b635035dee449c78ab01708 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 15:56:40 +0100 Subject: [PATCH 11/19] test(listAppsV2): cleanup --- .../src/apps/listApps/v2.test.ts | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/libs/ledger-live-common/src/apps/listApps/v2.test.ts b/libs/ledger-live-common/src/apps/listApps/v2.test.ts index 2597d9d691de..0e1e3f96ad83 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.test.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.test.ts @@ -31,13 +31,12 @@ describe("listApps v2", () => { it("should return an observable that errors if deviceInfo.isOSU is true", done => { const transport = aTransportBuilder(); const deviceInfo = aDeviceInfoBuilder({ isOSU: true, isBootloader: false }); - const result = listApps({ + + listApps({ transport, deviceInfo, managerApiRepository: mockedManagerApiRepository, - }); - - result.subscribe({ + }).subscribe({ error: err => { expect(err).toBeInstanceOf(UnexpectedBootloader); done(); @@ -46,19 +45,19 @@ describe("listApps v2", () => { fail("this observable should not complete"); }, }); + jest.advanceTimersByTime(1); }); it("should return an observable that errors if deviceInfo.isBootloader is true", done => { const transport = aTransportBuilder(); const deviceInfo = aDeviceInfoBuilder({ isOSU: false, isBootloader: true }); - const result = listApps({ + + listApps({ transport, deviceInfo, managerApiRepository: mockedManagerApiRepository, - }); - - result.subscribe({ + }).subscribe({ error: err => { expect(err).toBeInstanceOf(UnexpectedBootloader); done(); @@ -67,6 +66,7 @@ describe("listApps v2", () => { fail("this observable should not complete"); }, }); + jest.advanceTimersByTime(1); }); @@ -74,13 +74,11 @@ describe("listApps v2", () => { const transport = aTransportBuilder({ deviceModel: null }); const deviceInfo = aDeviceInfoBuilder({ isOSU: false, isBootloader: false, targetId: 0 }); - const result = listApps({ + listApps({ transport, deviceInfo, managerApiRepository: mockedManagerApiRepository, - }); - - result.subscribe({ + }).subscribe({ error: err => { expect(err.message).toBe("Bad usage of listAppsV2: missing deviceModelId"); done(); @@ -89,6 +87,7 @@ describe("listApps v2", () => { fail("this observable should not complete"); }, }); + jest.advanceTimersByTime(1); }); @@ -118,6 +117,7 @@ describe("listApps v2", () => { done(); }, }); + jest.advanceTimersByTime(1); expect(listAppsCommandSpy).toHaveBeenCalled(); @@ -176,6 +176,7 @@ describe("listApps v2", () => { fail("this observable should not complete"); }, }); + jest.advanceTimersByTime(1); }); @@ -206,6 +207,7 @@ describe("listApps v2", () => { fail("this observable should not complete"); }, }); + jest.advanceTimersByTime(1); }); @@ -238,6 +240,7 @@ describe("listApps v2", () => { fail("this observable should not complete"); }, }); + jest.advanceTimersByTime(1); }); }); From 2c1c7691432b47e21efc820c24a69e32173a37c9 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 16:09:23 +0100 Subject: [PATCH 12/19] refactor(mocks): move entities mocks to device-core --- .../entities/mocks}/aDeviceVersion.ts | 6 +++-- .../entities/mocks}/aFinalFirmware.ts | 2 +- .../managerApi/entities/mocks/aOsuFirmware.ts | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) rename libs/ledger-live-common/src/{mock/fixtures => device-core/managerApi/entities/mocks}/aDeviceVersion.ts (71%) rename libs/ledger-live-common/src/{mock/fixtures => device-core/managerApi/entities/mocks}/aFinalFirmware.ts (88%) create mode 100644 libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aOsuFirmware.ts diff --git a/libs/ledger-live-common/src/mock/fixtures/aDeviceVersion.ts b/libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aDeviceVersion.ts similarity index 71% rename from libs/ledger-live-common/src/mock/fixtures/aDeviceVersion.ts rename to libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aDeviceVersion.ts index bab8b34e7621..db216f55f93c 100644 --- a/libs/ledger-live-common/src/mock/fixtures/aDeviceVersion.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aDeviceVersion.ts @@ -1,6 +1,8 @@ -import { DeviceVersion } from "@ledgerhq/types-live"; +import { DeviceVersionEntity } from "../DeviceVersionEntity"; -export const aDeviceVersionBuilder = (props?: Partial): DeviceVersion => { +export const aDeviceVersionBuilder = ( + props?: Partial, +): DeviceVersionEntity => { return { name: "Ledger Nano S", device: 3, diff --git a/libs/ledger-live-common/src/mock/fixtures/aFinalFirmware.ts b/libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aFinalFirmware.ts similarity index 88% rename from libs/ledger-live-common/src/mock/fixtures/aFinalFirmware.ts rename to libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aFinalFirmware.ts index 397d4dfb6e36..6635994b1e50 100644 --- a/libs/ledger-live-common/src/mock/fixtures/aFinalFirmware.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aFinalFirmware.ts @@ -1,4 +1,4 @@ -import { FinalFirmware } from "@ledgerhq/types-live"; +import { FinalFirmware } from "../FirmwareUpdateContextEntity"; export const aFinalFirmwareBuilder = (props?: Partial): FinalFirmware => { return { diff --git a/libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aOsuFirmware.ts b/libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aOsuFirmware.ts new file mode 100644 index 000000000000..2ccab9d247fd --- /dev/null +++ b/libs/ledger-live-common/src/device-core/managerApi/entities/mocks/aOsuFirmware.ts @@ -0,0 +1,22 @@ +import { OsuFirmware } from "../FirmwareUpdateContextEntity"; + +export function aOsuFirmwareBuilder(props?: Partial) { + return { + id: 0, + name: "", + display_name: "", + notes: "", + perso: "", + firmware: "", + firmware_key: "", + hash: "", + device_versions: [], + next_se_firmware_final_version: 0, + providers: [], + date_creation: "", + date_last_modified: "", + description: "", + previous_se_firmware_final_version: [], + ...props, + }; +} From 0ce4c61469f9d16e9ce0f7f513d0fb8f77594b75 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 16:10:57 +0100 Subject: [PATCH 13/19] chore: changeset chore: changeset --- .changeset/young-books-enjoy.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/young-books-enjoy.md diff --git a/.changeset/young-books-enjoy.md b/.changeset/young-books-enjoy.md new file mode 100644 index 000000000000..3d312315f081 --- /dev/null +++ b/.changeset/young-books-enjoy.md @@ -0,0 +1,11 @@ +--- +"@ledgerhq/live-common": patch +--- + +Refactor list apps v2: + - move entrypoint to live-common/device/use-cases/listAppsUseCase.ts + - move more of the `manager/api.ts`` logic to `ManagerApiRepository` + - create `StubManagerApiRepository` for mocks + - implement some unit tests for `listApps/v2.ts` + +Implement `getProviderIdUseCase` that takes `forceProvider: number` as a parameter From 410260c057a9c7ee78af6b0d5bb6fa544d631418 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 17:10:43 +0100 Subject: [PATCH 14/19] refactor(StubManagerApiRepository): replace mocks --- .../repositories/StubManagerApiRepository.ts | 99 ++----------------- 1 file changed, 8 insertions(+), 91 deletions(-) diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts index a2e06ccba8ec..3b42b4a6899f 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts @@ -1,28 +1,13 @@ -import { DeviceInfoEntity } from "../entities/DeviceInfoEntity"; import { DeviceVersionEntity } from "../entities/DeviceVersionEntity"; import { FinalFirmware, OsuFirmware } from "../entities/FirmwareUpdateContextEntity"; -import { LanguagePackageEntity } from "../entities/LanguagePackageEntity"; +import { aDeviceVersionEntityBuilder } from "../entities/mocks/aDeviceVersionEntityBuilder"; +import { aFinalFirmwareBuilder } from "../entities/mocks/aFinalFirmware"; +import { aOsuFirmwareBuilder } from "../entities/mocks/aOsuFirmware"; import { ManagerApiRepository } from "./ManagerApiRepository"; export class StubManagerApiRepository implements ManagerApiRepository { readonly fetchLatestFirmware = () => { - const result: OsuFirmware = { - id: 0, - name: "", - display_name: "", - notes: "", - perso: "", - firmware: "", - firmware_key: "", - hash: "", - device_versions: [], - next_se_firmware_final_version: 0, - providers: [], - date_creation: "", - date_last_modified: "", - description: "", - previous_se_firmware_final_version: [], - }; + const result: OsuFirmware = aOsuFirmwareBuilder(); return Promise.resolve(result); }; @@ -31,90 +16,22 @@ export class StubManagerApiRepository implements ManagerApiRepository { }; readonly getDeviceVersion = () => { - const result: DeviceVersionEntity = { - name: "Ledger Nano S", - device: 3, - providers: [], - id: 5, - display_name: "Ledger Nano S", - target_id: "0x31100004", - description: "Ledger Nano S", - mcu_versions: [1.7], - se_firmware_final_versions: [1.2], - osu_versions: [], - application_versions: [], - date_creation: "2020-04-30T13:50:00.000Z", - date_last_modified: "2020-04-30T13:50:00.000Z", - }; + const result: DeviceVersionEntity = aDeviceVersionEntityBuilder(); return Promise.resolve(result); }; readonly getCurrentOSU = () => { - const result: OsuFirmware = { - id: 0, - name: "", - display_name: "", - notes: "", - perso: "", - firmware: "", - firmware_key: "", - hash: "", - device_versions: [], - next_se_firmware_final_version: 0, - providers: [], - date_creation: "", - date_last_modified: "", - description: "", - previous_se_firmware_final_version: [], - }; + const result: OsuFirmware = aOsuFirmwareBuilder(); return Promise.resolve(result); }; readonly getCurrentFirmware = () => { - const result: FinalFirmware = { - id: 1, - name: "FINAL", - description: null, - display_name: null, - notes: null, - perso: "", - firmware: "", - firmware_key: "", - hash: "", - date_creation: "", - date_last_modified: "", - device_versions: [], - providers: [], - version: "0", - se_firmware: 1, - osu_versions: [], - mcu_versions: [], - application_versions: [], - }; + const result: FinalFirmware = aFinalFirmwareBuilder(); return Promise.resolve(result); }; readonly getFinalFirmwareById = () => { - const result: FinalFirmware = { - id: 1, - name: "FINAL", - description: null, - display_name: null, - notes: null, - perso: "", - firmware: "", - firmware_key: "", - hash: "", - date_creation: "", - date_last_modified: "", - device_versions: [], - providers: [], - version: "0", - se_firmware: 1, - osu_versions: [], - mcu_versions: [], - application_versions: [], - }; + const result: FinalFirmware = aFinalFirmwareBuilder(); return Promise.resolve(result); }; From b1ddaa81eac6939495e48c85eeaa1e1d89fc093a Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 17:10:57 +0100 Subject: [PATCH 15/19] chore: cleanup --- .changeset/young-books-enjoy.md | 4 ++-- libs/ledger-live-common/src/apps/listApps/v1.ts | 5 ++++- libs/ledger-live-common/src/apps/listApps/v2.test.ts | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.changeset/young-books-enjoy.md b/.changeset/young-books-enjoy.md index 3d312315f081..74cde259a695 100644 --- a/.changeset/young-books-enjoy.md +++ b/.changeset/young-books-enjoy.md @@ -3,8 +3,8 @@ --- Refactor list apps v2: - - move entrypoint to live-common/device/use-cases/listAppsUseCase.ts - - move more of the `manager/api.ts`` logic to `ManagerApiRepository` + - move entrypoint to `live-common/src/device/use-cases/listAppsUseCase.ts` + - move more of the `manager/api.ts` logic to `ManagerApiRepository` - create `StubManagerApiRepository` for mocks - implement some unit tests for `listApps/v2.ts` diff --git a/libs/ledger-live-common/src/apps/listApps/v1.ts b/libs/ledger-live-common/src/apps/listApps/v1.ts index 15c5be38cf19..cab679fc2e3d 100644 --- a/libs/ledger-live-common/src/apps/listApps/v1.ts +++ b/libs/ledger-live-common/src/apps/listApps/v1.ts @@ -25,7 +25,10 @@ const emptyHashData = "000000000000000000000000000000000000000000000000000000000 //TODO if you are reading this, don't worry, a big rewrite is coming and we'll be able //to simplify this a lot. Stay calm. -export const listApps = (transport: Transport, deviceInfo: DeviceInfo): Observable => { +export const listApps = ( + transport: Transport, + deviceInfo: DeviceInfo, +): Observable => { const tracer = new LocalTracer("list-apps", { transport: transport.getTraceContext() }); tracer.trace("Using legacy version", { deviceInfo }); diff --git a/libs/ledger-live-common/src/apps/listApps/v2.test.ts b/libs/ledger-live-common/src/apps/listApps/v2.test.ts index 0e1e3f96ad83..c750b42bee15 100644 --- a/libs/ledger-live-common/src/apps/listApps/v2.test.ts +++ b/libs/ledger-live-common/src/apps/listApps/v2.test.ts @@ -1,11 +1,11 @@ +import { from } from "rxjs"; +import { UnexpectedBootloader } from "@ledgerhq/errors"; import { aTransportBuilder } from "@ledgerhq/hw-transport-mocker"; import { listApps } from "./v2"; -import { aDeviceInfoBuilder } from "../../mock/fixtures/aDeviceInfo"; import ManagerAPI from "../../manager/api"; +import { aDeviceInfoBuilder } from "../../mock/fixtures/aDeviceInfo"; import { ManagerApiRepository } from "../../device-core/managerApi/repositories/ManagerApiRepository"; import { StubManagerApiRepository } from "../../device-core/managerApi/repositories/StubManagerApiRepository"; -import { from } from "rxjs"; -import { UnexpectedBootloader } from "@ledgerhq/errors"; jest.useFakeTimers(); From 4ca659251ad8603bfafdffe64eac7a91b87bfc3a Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Thu, 1 Feb 2024 17:12:28 +0100 Subject: [PATCH 16/19] fix: lint & bad import fix: imports fix: lint & bad import fix(llm): bad import --- apps/cli/src/commands/managerListApps.ts | 7 +++++-- .../src/renderer/screens/manager/Dashboard.tsx | 2 +- apps/ledger-live-mobile/src/screens/Manager/shared.ts | 2 +- .../src/device-core/managerApi/entities/AppEntity.ts | 2 +- .../managerApi/repositories/HttpManagerApiRepository.ts | 2 +- .../managerApi/repositories/StubManagerApiRepository.ts | 4 ++-- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/cli/src/commands/managerListApps.ts b/apps/cli/src/commands/managerListApps.ts index dea19f71995a..509ebc8943c3 100644 --- a/apps/cli/src/commands/managerListApps.ts +++ b/apps/cli/src/commands/managerListApps.ts @@ -2,7 +2,10 @@ import { from } from "rxjs"; import { filter, map, mergeMap, repeat } from "rxjs/operators"; import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; -import { enableListAppsV2, listApps } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; +import { + enableListAppsV2, + listAppsUseCase, +} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import { deviceOpt } from "../scan"; export default { description: "List apps that can be installed on the device", @@ -44,7 +47,7 @@ export default { return withDevice(device || "")(t => from(getDeviceInfo(t)).pipe( mergeMap(deviceInfo => - listApps(t, deviceInfo).pipe( + listAppsUseCase(t, deviceInfo).pipe( filter(e => e.type === "result"), // @ts-expect-error we need better typings and safe guard to infer types map(e => e.result), diff --git a/apps/ledger-live-desktop/src/renderer/screens/manager/Dashboard.tsx b/apps/ledger-live-desktop/src/renderer/screens/manager/Dashboard.tsx index 310bfee908ca..119bb9bac360 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/manager/Dashboard.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/manager/Dashboard.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useState, useEffect, useRef, useContext } from "react"; import { useSelector } from "react-redux"; import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; -import { execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; import { App, DeviceInfo, FirmwareUpdateContext } from "@ledgerhq/types-live"; import { AppOp, ListAppsResult } from "@ledgerhq/live-common/apps/types"; import { distribute, initState } from "@ledgerhq/live-common/apps/logic"; diff --git a/apps/ledger-live-mobile/src/screens/Manager/shared.ts b/apps/ledger-live-mobile/src/screens/Manager/shared.ts index 65b6bcaac072..277ca4a1b503 100644 --- a/apps/ledger-live-mobile/src/screens/Manager/shared.ts +++ b/apps/ledger-live-mobile/src/screens/Manager/shared.ts @@ -1,8 +1,8 @@ import { useCallback } from "react"; import type { Exec, ListAppsResult } from "@ledgerhq/live-common/apps/index"; import { useAppsRunner } from "@ledgerhq/live-common/apps/react"; -import { execWithTransport } from "@ledgerhq/live-common/apps/hw"; import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase"; export function useApps(listAppsRes: ListAppsResult, deviceId: string, appsToRestore?: string[]) { const exec: Exec = useCallback( diff --git a/libs/ledger-live-common/src/device-core/managerApi/entities/AppEntity.ts b/libs/ledger-live-common/src/device-core/managerApi/entities/AppEntity.ts index 36fd4c546010..0a481fdd3354 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/entities/AppEntity.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/entities/AppEntity.ts @@ -63,4 +63,4 @@ export type ApplicationV2Entity = { bytes: number; warning: string | null; isDevTools: boolean; -}; \ No newline at end of file +}; diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts index 7ba9c3cc4743..e630abe82033 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts @@ -1,8 +1,8 @@ import { makeLRUCache } from "@ledgerhq/live-network/cache"; import network from "@ledgerhq/live-network/network"; +import { FirmwareNotRecognized, NetworkDown } from "@ledgerhq/errors"; import { getUserHashes } from "../../../user"; import URL from "url"; -import { FirmwareNotRecognized, NetworkDown } from "@ledgerhq/errors"; import { ManagerApiRepository } from "./ManagerApiRepository"; import { FinalFirmware, OsuFirmware } from "../entities/FirmwareUpdateContextEntity"; import { DeviceVersionEntity } from "../entities/DeviceVersionEntity"; diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts index 3b42b4a6899f..72b53046dafd 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/StubManagerApiRepository.ts @@ -1,6 +1,6 @@ import { DeviceVersionEntity } from "../entities/DeviceVersionEntity"; import { FinalFirmware, OsuFirmware } from "../entities/FirmwareUpdateContextEntity"; -import { aDeviceVersionEntityBuilder } from "../entities/mocks/aDeviceVersionEntityBuilder"; +import { aDeviceVersionBuilder } from "../entities/mocks/aDeviceVersion"; import { aFinalFirmwareBuilder } from "../entities/mocks/aFinalFirmware"; import { aOsuFirmwareBuilder } from "../entities/mocks/aOsuFirmware"; import { ManagerApiRepository } from "./ManagerApiRepository"; @@ -16,7 +16,7 @@ export class StubManagerApiRepository implements ManagerApiRepository { }; readonly getDeviceVersion = () => { - const result: DeviceVersionEntity = aDeviceVersionEntityBuilder(); + const result: DeviceVersionEntity = aDeviceVersionBuilder(); return Promise.resolve(result); }; From 67404bb85eddd411230ffd75e6383f0e67e38d33 Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Fri, 2 Feb 2024 13:09:36 +0100 Subject: [PATCH 17/19] refactor(common/getProviderIdUseCase): add a default value to PROVIDERS fix(getProviderIdUseCase): revert breaking change to an enum --- .../device-core/managerApi/use-cases/getProviderIdUseCase.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/ledger-live-common/src/device-core/managerApi/use-cases/getProviderIdUseCase.ts b/libs/ledger-live-common/src/device-core/managerApi/use-cases/getProviderIdUseCase.ts index ef25a59c0de8..77cd7ebfc970 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/use-cases/getProviderIdUseCase.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/use-cases/getProviderIdUseCase.ts @@ -1,6 +1,7 @@ import { DeviceInfoEntity } from "../entities/DeviceInfoEntity"; export const PROVIDERS: Record = { + default: 1, das: 2, club: 3, shitcoins: 4, @@ -14,8 +15,8 @@ export function getProviderIdUseCase({ deviceInfo: DeviceInfoEntity | undefined | null; forceProvider?: number; }): number { - if (forceProvider && forceProvider !== 1) return forceProvider; - if (!deviceInfo) return 1; + if (forceProvider && forceProvider !== PROVIDERS.default) return forceProvider; + if (!deviceInfo) return PROVIDERS.default; const { providerName } = deviceInfo; const maybeProvider = providerName && PROVIDERS[providerName]; if (maybeProvider) return maybeProvider; From 674f0b3ed77310163022730515ba9467a870cc7b Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Mon, 5 Feb 2024 18:17:25 +0100 Subject: [PATCH 18/19] fix(HttpManagerApiRepository): badly infered types for makeLRUCache fix(HttpManagerApiRepository): missing type + reword comment --- .../repositories/HttpManagerApiRepository.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts b/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts index e630abe82033..a2414e7a9b6f 100644 --- a/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts +++ b/libs/ledger-live-common/src/device-core/managerApi/repositories/HttpManagerApiRepository.ts @@ -23,7 +23,10 @@ export class HttpManagerApiRepository implements ManagerApiRepository { this.liveCommonVersion = liveCommonVersion; } - readonly fetchLatestFirmware = makeLRUCache( + // NB: we are explicitly specifying the type because TypeScript cannot infer + // properly the return type of `makeLRUCache` without using `any` for the + // parameters. + readonly fetchLatestFirmware: ManagerApiRepository["fetchLatestFirmware"] = makeLRUCache( async ({ current_se_firmware_final_version, device_version, providerId, userId }) => { const salt = getUserHashes(userId).firmwareSalt; const { @@ -58,7 +61,7 @@ export class HttpManagerApiRepository implements ManagerApiRepository { a => `${a.current_se_firmware_final_version}_${a.device_version}_${a.providerId}`, ); - readonly fetchMcus = makeLRUCache( + readonly fetchMcus: ManagerApiRepository["fetchMcus"] = makeLRUCache( async () => { const { data } = await network({ method: "GET", @@ -74,7 +77,7 @@ export class HttpManagerApiRepository implements ManagerApiRepository { () => "", ); - readonly getDeviceVersion = makeLRUCache( + readonly getDeviceVersion: ManagerApiRepository["getDeviceVersion"] = makeLRUCache( async ({ targetId, providerId }) => { const { data, @@ -108,7 +111,7 @@ export class HttpManagerApiRepository implements ManagerApiRepository { ({ targetId, providerId }) => `${targetId}_${providerId}`, ); - readonly getCurrentOSU = makeLRUCache( + readonly getCurrentOSU: ManagerApiRepository["getCurrentOSU"] = makeLRUCache( async input => { const { data } = await network({ method: "POST", @@ -129,7 +132,7 @@ export class HttpManagerApiRepository implements ManagerApiRepository { a => `${a.version}_${a.deviceId}_${a.providerId}`, ); - readonly getCurrentFirmware = makeLRUCache( + readonly getCurrentFirmware: ManagerApiRepository["getCurrentFirmware"] = makeLRUCache( async input => { const { data, @@ -154,7 +157,7 @@ export class HttpManagerApiRepository implements ManagerApiRepository { a => `${a.version}_${a.deviceId}_${a.providerId}`, ); - readonly getFinalFirmwareById = makeLRUCache( + readonly getFinalFirmwareById: ManagerApiRepository["getFinalFirmwareById"] = makeLRUCache( async id => { const { data, @@ -174,7 +177,7 @@ export class HttpManagerApiRepository implements ManagerApiRepository { id => String(id), ); - readonly getAppsByHash = makeLRUCache( + readonly getAppsByHash: ManagerApiRepository["getAppsByHash"] = makeLRUCache( async hashes => { const { data, @@ -200,7 +203,7 @@ export class HttpManagerApiRepository implements ManagerApiRepository { hashes => `${this.managerApiBase}_${hashes.join("-")}`, ); - readonly catalogForDevice = makeLRUCache( + readonly catalogForDevice: ManagerApiRepository["catalogForDevice"] = makeLRUCache( async params => { const { provider, targetId, firmwareVersion } = params; const { @@ -234,14 +237,14 @@ export class HttpManagerApiRepository implements ManagerApiRepository { forceProvider?: number, ) => { const deviceVersion = await this.getDeviceVersion({ - deviceInfo: deviceInfo.targetId, - provider: getProviderIdUseCase({ deviceInfo, forceProvider }), + targetId: deviceInfo.targetId, + providerId: getProviderIdUseCase({ deviceInfo, forceProvider }), }); const seFirmwareVersion = await this.getCurrentFirmware({ version: deviceInfo.version, deviceId: deviceVersion.id, - provider: getProviderIdUseCase({ deviceInfo, forceProvider }), + providerId: getProviderIdUseCase({ deviceInfo, forceProvider }), }); const { data }: { data: LanguagePackageResponseEntity[] } = await network({ From 049635e7e198d70cf8249f9233fa66c8c687254b Mon Sep 17 00:00:00 2001 From: Olivier Freyssinet Date: Mon, 5 Feb 2024 18:37:47 +0100 Subject: [PATCH 19/19] refactor(listAppsV1): pass managerApiRepository argument --- libs/ledger-live-common/src/apps/listApps/v1.ts | 7 ++++++- .../src/device/use-cases/listAppsUseCase.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/ledger-live-common/src/apps/listApps/v1.ts b/libs/ledger-live-common/src/apps/listApps/v1.ts index cab679fc2e3d..bef6f81b6743 100644 --- a/libs/ledger-live-common/src/apps/listApps/v1.ts +++ b/libs/ledger-live-common/src/apps/listApps/v1.ts @@ -18,6 +18,7 @@ import { getEnv } from "@ledgerhq/live-env"; import { calculateDependencies, polyfillApp, polyfillApplication } from "../polyfill"; import getDeviceName from "../../hw/getDeviceName"; import { getLatestFirmwareForDeviceUseCase } from "../../device/use-cases/getLatestFirmwareForDeviceUseCase"; +import { ManagerApiRepository } from "../../device-core/managerApi/repositories/ManagerApiRepository"; const appsThatKeepChangingHashes = ["Fido U2F", "Security Key"]; @@ -28,6 +29,7 @@ const emptyHashData = "000000000000000000000000000000000000000000000000000000000 export const listApps = ( transport: Transport, deviceInfo: DeviceInfo, + managerApiRepository: ManagerApiRepository, ): Observable => { const tracer = new LocalTracer("list-apps", { transport: transport.getTraceContext() }); tracer.trace("Using legacy version", { deviceInfo }); @@ -90,7 +92,10 @@ export const listApps = ( }), ); - const latestFirmwareForDeviceP = getLatestFirmwareForDeviceUseCase(deviceInfo); + const latestFirmwareForDeviceP = getLatestFirmwareForDeviceUseCase( + deviceInfo, + managerApiRepository, + ); const firmwareP = Promise.all([firmwareDataP, latestFirmwareForDeviceP]).then( ([firmwareData, updateAvailable]) => ({ diff --git a/libs/ledger-live-common/src/device/use-cases/listAppsUseCase.ts b/libs/ledger-live-common/src/device/use-cases/listAppsUseCase.ts index 11ffbf3bf8b8..a9f31ae5c4c6 100644 --- a/libs/ledger-live-common/src/device/use-cases/listAppsUseCase.ts +++ b/libs/ledger-live-common/src/device/use-cases/listAppsUseCase.ts @@ -38,7 +38,7 @@ export function listAppsUseCase( managerApiRepository: ManagerApiRepository = HttpManagerApiRepositoryFactory.getInstance(), ): Observable { return listAppsV2Enabled - ? listAppsV1(transport, deviceInfo) + ? listAppsV1(transport, deviceInfo, managerApiRepository) : listAppsV2({ transport, deviceInfo,