Skip to content

Commit

Permalink
✨ (lld): delete local app data on uninstall and uninstallAll
Browse files Browse the repository at this point in the history
  • Loading branch information
valpinkman committed Sep 4, 2024
1 parent 5a898af commit 938e6b6
Show file tree
Hide file tree
Showing 24 changed files with 172 additions and 86 deletions.
6 changes: 6 additions & 0 deletions .changeset/light-camels-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ledgerhq/live-common": minor
"ledger-live-desktop": patch
---

Delete local app data when uninstalling apps
6 changes: 2 additions & 4 deletions apps/cli/src/commands/device/appUninstallAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ 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 {
listAppsUseCase,
execWithTransport,
} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { listAppsUseCase } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/execWithTransport";
import { command as uninstallAllApps } from "@ledgerhq/live-common/hw/uninstallAllApps";
import { deviceOpt } from "../../scan";

Expand Down
6 changes: 2 additions & 4 deletions apps/cli/src/commands/device/appsCheckAllAppVersions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ 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 {
listAppsUseCase,
execWithTransport,
} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { listAppsUseCase } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/execWithTransport";
import { delay } from "@ledgerhq/live-common/promise";
import { getEnv } from "@ledgerhq/live-env";
import { getDependencies } from "@ledgerhq/live-common/apps/polyfill";
Expand Down
6 changes: 2 additions & 4 deletions apps/cli/src/commands/device/appsInstallAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ 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 {
listAppsUseCase,
execWithTransport,
} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { listAppsUseCase } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/execWithTransport";
import { deviceOpt } from "../../scan";
export default {
description: "test script to install and uninstall all apps",
Expand Down
6 changes: 2 additions & 4 deletions apps/cli/src/commands/device/appsUpdateTestAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ 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 {
listAppsUseCase,
execWithTransport,
} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { listAppsUseCase } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/execWithTransport";
import type { AppOp } from "@ledgerhq/live-common/apps/types";
import { deviceOpt } from "../../scan";

Expand Down
6 changes: 2 additions & 4 deletions apps/cli/src/commands/device/devDeviceAppsScenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ 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 {
listAppsUseCase,
execWithTransport,
} from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { listAppsUseCase } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/execWithTransport";
import installApp from "@ledgerhq/live-common/hw/installApp";
import { deviceOpt } from "../../scan";
import { Application } from "@ledgerhq/types-live";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ const StepPrepare = ({
// the firmware update payload to the device whereas now we are backing up the CLS too
// but only for stax or europa.
const deviceId = device?.deviceId ?? "";

// Back app data for installed apps before initiating the firmware update.
// TODO:

// This is the backup of the CLS for stax or flex devices.
const maybeCLSBackup =
deviceInfo.onboarded && isCustomLockScreenSupported(deviceModelId)
? customLockScreenFetch({
Expand Down
Original file line number Diff line number Diff line change
@@ -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/device/use-cases/listAppsUseCase";
import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/execWithTransport";
import { DeviceInfo, FirmwareUpdateContext } from "@ledgerhq/types-live";
import { ExecArgs, ListAppsResult } from "@ledgerhq/live-common/apps/types";
import { distribute, initState } from "@ledgerhq/live-common/apps/logic";
Expand Down Expand Up @@ -79,7 +79,7 @@ const Dashboard = ({
() =>
getEnv("MOCK")
? mockExecWithInstalledContext(result?.installed || [])
: ({ app, appOp, targetId }: ExecArgs) =>
: ({ app, appOp, targetId, deleteAppDataBackup }: ExecArgs) =>
withDevice(device.deviceId)(transport =>
execWithTransport(
transport,
Expand All @@ -90,6 +90,7 @@ const Dashboard = ({
app,
modelId: device.modelId,
storage,
deleteAppDataBackup,
}),
),
[device, result, appsBackupEnabled, storage],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const DeviceDashboard = ({
const reduxDispatch = useDispatch();
const lastSeenCustomImage = useSelector(lastSeenCustomImageSelector);
const isFirstCustomImageUpdate = useRef<boolean>(true);

useEffect(() => {
if (isFirstCustomImageUpdate.current) {
isFirstCustomImageUpdate.current = false;
Expand All @@ -111,8 +112,10 @@ const DeviceDashboard = ({
});
}
}, [dispatch, lastSeenCustomImage]);

const { installQueue, uninstallQueue, currentError } = state;
const jobInProgress = installQueue.length > 0 || uninstallQueue.length > 0;

const distribution = useMemo(() => {
const newState = installQueue.length
? predictOptimisticState(
Expand All @@ -124,21 +127,26 @@ const DeviceDashboard = ({
: state;
return distribute(newState);
}, [state, installQueue]);

const onCloseDepsInstallModal = useCallback(
() => setAppInstallDep(undefined),
[setAppInstallDep],
);

const onCloseDepsUninstallModal = useCallback(
() => setAppUninstallDep(undefined),
[setAppUninstallDep],
);

const installState =
installQueue.length > 0 ? (uninstallQueue.length > 0 ? "update" : "install") : "uninstall";

const onCloseError = useCallback(() => {
dispatch({
type: "recover",
});
}, [dispatch]);

useEffect(() => {
if (state.installed.length && !hasInstalledApps) {
reduxDispatch(setHasInstalledApps(true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export type FirmwareUpdateParams = {
export type UpdateStep =
| "start"
| "appsBackup"
// | "appsDataBackup"
| "imageBackup"
| "firmwareUpdate"
| "languageRestore"
Expand Down Expand Up @@ -234,6 +235,10 @@ export const useUpdateFirmwareAndRestoreSettings = ({
}
break;

// TODO: Implement apps data backup
// case "appsDataBackup":
// break;

case "imageBackup":
hasUnrecoverableError =
staxFetchImageState.error &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback } from "react";
import type { Exec, ListAppsResult } from "@ledgerhq/live-common/apps/index";
import { useAppsRunner } from "@ledgerhq/live-common/apps/react";
import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess";
import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/listAppsUseCase";
import { execWithTransport } from "@ledgerhq/live-common/device/use-cases/execWithTransport";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import {
AppStorageType,
Expand Down
3 changes: 2 additions & 1 deletion libs/ledger-live-common/src/apps/inlineAppInstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ 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 { listAppsUseCase, execWithTransport } from "../device/use-cases/listAppsUseCase";
import { listAppsUseCase } from "../device/use-cases/listAppsUseCase";
import { execWithTransport } from "../device/use-cases/execWithTransport";
import { reducer, initState, isOutOfMemoryState, predictOptimisticState } from "./logic";
import { runAllWithProgress } from "./runner";
import { InlineAppInstallEvent } from "./types";
Expand Down
13 changes: 11 additions & 2 deletions libs/ledger-live-common/src/apps/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const initState = (
currentError: null,
currentAppOp: null,
skippedAppOps: [],
deleteAppDataBackup: false,
};

if (appsToRestore) {
Expand Down Expand Up @@ -103,7 +104,7 @@ export const reducer = (state: State, action: Action): State => {
state.currentProgressSubject.complete();
}

let nextState;
let nextState: State;

if (appOp.type === "install") {
const app = state.apps.find(a => a.name === appOp.name);
Expand Down Expand Up @@ -197,6 +198,7 @@ export const reducer = (state: State, action: Action): State => {
return {
...state,
installed: [],
deleteAppDataBackup: false,
};
case "wipe":
return {
Expand All @@ -207,6 +209,7 @@ export const reducer = (state: State, action: Action): State => {
state.appByName,
state.installed.map(({ name }) => name),
),
deleteAppDataBackup: true,
};

case "updateAll": {
Expand Down Expand Up @@ -371,7 +374,13 @@ export const reducer = (state: State, action: Action): State => {
);
}

return { ...state, currentError: null, installQueue, uninstallQueue };
return {
...state,
currentError: null,
installQueue,
uninstallQueue,
deleteAppDataBackup: true,
};
}
}
};
Expand Down
1 change: 1 addition & 0 deletions libs/ledger-live-common/src/apps/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const runAppOp = ({
app,
modelId: deviceModel.id,
...(storage ? { storage } : {}),
...(state.deleteAppDataBackup ? { deleteAppDataBackup: true } : {}),
}),
).pipe(
throttleTime(100),
Expand Down
2 changes: 2 additions & 0 deletions libs/ledger-live-common/src/apps/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type ExecArgs = {
app: App;
modelId?: DeviceModelId;
storage?: StorageProvider<AppStorageType>;
deleteAppDataBackup?: boolean;
};

export type Exec = (args: ExecArgs) => Observable<{
Expand Down Expand Up @@ -102,6 +103,7 @@ export type State = {
recentlyInstalledApps: string[];
installQueue: string[];
uninstallQueue: string[];
deleteAppDataBackup: boolean;
skippedAppOps: SkippedAppOp[]; // Nb If an AppOp couldn't be completed, track why.
updateAllQueue: string[]; // queue saved at the time of a "updateAll" action
currentAppOp: AppOp | null | undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import Transport from "@ledgerhq/hw-transport";
import { Observable, firstValueFrom, lastValueFrom } from "rxjs";
import { RestoreAppDataEvent, RestoreAppDataEventType } from "./types";
import {
AppStorageType,
DeleteAppDataEvent,
RestoreAppDataEvent,
RestoreAppDataEventType,
StorageProvider,
} from "./types";
import { restoreAppData } from "./restoreAppData";
import { DeviceModelId } from "@ledgerhq/devices";

jest.mock("@ledgerhq/hw-transport");
jest.mock("@ledgerhq/device-core", () => ({
Expand All @@ -17,37 +24,49 @@ describe("restoreAppData", () => {
let transport: Transport;
let appName: string;
let appData: string;
let storageProvider: StorageProvider<AppStorageType>;
let deviceModelId: DeviceModelId;

beforeEach(() => {
// Initialize the transport, app name and app data before each test
transport = {} as unknown as Transport;
appName = "MyApp";
appData = Buffer.from(DECODED_STORED_DATA).toString("base64");
deviceModelId = DeviceModelId.stax;
storageProvider = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
};
});

afterEach(() => {
jest.clearAllMocks();
});

it("should restore the app data by emitting relative events sequentially when data size > 255", async () => {
const restoreObservable: Observable<RestoreAppDataEvent> = restoreAppData(
const restoreObservable: Observable<RestoreAppDataEvent | DeleteAppDataEvent> = restoreAppData(
transport,
appName,
deviceModelId,
storageProvider,
appData,
);
const events: RestoreAppDataEvent[] = [];
const events: (RestoreAppDataEvent | DeleteAppDataEvent)[] = [];

// Subscribe to the observable to receive the restore events
restoreObservable.subscribe((event: RestoreAppDataEvent) => {
restoreObservable.subscribe((event: RestoreAppDataEvent | DeleteAppDataEvent) => {
events.push(event);
});

const firstValue: RestoreAppDataEvent = await firstValueFrom(restoreObservable);
const firstValue: RestoreAppDataEvent | DeleteAppDataEvent =
await firstValueFrom(restoreObservable);
expect(firstValue).toEqual({
type: RestoreAppDataEventType.AppDataInitialized,
});

const lastValue: RestoreAppDataEvent = await lastValueFrom(restoreObservable);
const lastValue: RestoreAppDataEvent | DeleteAppDataEvent =
await lastValueFrom(restoreObservable);
expect(lastValue).toEqual({
type: RestoreAppDataEventType.AppDataRestored,
});
Expand All @@ -61,24 +80,28 @@ describe("restoreAppData", () => {
});

it("should restore the app data by emitting relative events sequentially when data size < 255", async () => {
const restoreObservable: Observable<RestoreAppDataEvent> = restoreAppData(
const restoreObservable: Observable<RestoreAppDataEvent | DeleteAppDataEvent> = restoreAppData(
transport,
appName,
"Ledger Flex",
deviceModelId,
storageProvider,
appData,
);
const events: RestoreAppDataEvent[] = [];
const events: (RestoreAppDataEvent | DeleteAppDataEvent)[] = [];

// Subscribe to the observable to receive the restore events
restoreObservable.subscribe((event: RestoreAppDataEvent) => {
restoreObservable.subscribe((event: RestoreAppDataEvent | DeleteAppDataEvent) => {
events.push(event);
});

const firstValue: RestoreAppDataEvent = await firstValueFrom(restoreObservable);
const firstValue: RestoreAppDataEvent | DeleteAppDataEvent =
await firstValueFrom(restoreObservable);
expect(firstValue).toEqual({
type: RestoreAppDataEventType.AppDataInitialized,
});

const lastValue: RestoreAppDataEvent = await lastValueFrom(restoreObservable);
const lastValue: RestoreAppDataEvent | DeleteAppDataEvent =
await lastValueFrom(restoreObservable);
expect(lastValue).toEqual({
type: RestoreAppDataEventType.AppDataRestored,
});
Expand Down
Loading

0 comments on commit 938e6b6

Please sign in to comment.