Skip to content

Commit

Permalink
Bugfix: [Stax ESC][LLD] Infinite spinner if user locks Stax at the be…
Browse files Browse the repository at this point in the history
…ginning of ESC (#4705)

* fix(lld/earlysecuritychecks): fix when device locked on initialization

* fix(lld/earlysecuritychecks): properly handle error in bootloader check

* fix(lld/earlysecuritychecks): cleanup async use effect

* chore: changeset

* style: lint

* fix(llm/earlysecuritychecks): use proper logger for errors
  • Loading branch information
ofreyssinet-ledger authored Sep 19, 2023
1 parent a134f28 commit a4a6fda
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-doors-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ledger-live-desktop": patch
---

Fix Stax onboarding early security checks: infinite loading state for locked device
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ import TroubleshootingDrawer, {
Props as TroubleshootingDrawerProps,
} from "./TroubleshootingDrawer";
import LockedDeviceDrawer, { Props as LockedDeviceDrawerProps } from "./LockedDeviceDrawer";
import { LockedDeviceError } from "@ledgerhq/errors";
import { LockedDeviceError, UnexpectedBootloader } from "@ledgerhq/errors";
import ErrorDrawer from "./EarlySecurityChecks/ErrorDrawer";
import logger from "~/renderer/logger";

const POLLING_PERIOD_MS = 1000;
const DESYNC_TIMEOUT_MS = 20000;
Expand Down Expand Up @@ -67,7 +69,7 @@ const SyncOnboardingScreen: React.FC<SyncOnboardingScreenProps> = ({
const deviceModelId = stringToDeviceModelId(strDeviceModelId, DeviceModelId.stax);

const [mustRecoverIfBootloader, setMustRecoverIfBootloader] = useState(true);
const [isBootloader, setIsBootloader] = useState<boolean | null>(null);
const [isBootloader, setIsBootloader] = useState(false);
// Needed because `device` object can be null or changed if disconnected/reconnected
const [lastSeenDevice, setLastSeenDevice] = useState<Device | null>(device ?? null);
useEffect(() => {
Expand All @@ -92,26 +94,53 @@ const SyncOnboardingScreen: React.FC<SyncOnboardingScreenProps> = ({
const { onboardingState, allowedError, fatalError, lockedDevice } = useOnboardingStatePolling({
device: lastSeenDevice,
pollingPeriodMs: POLLING_PERIOD_MS,
stopPolling: !isPollingOn || isBootloader === null || isBootloader,
stopPolling: !isPollingOn || isBootloader,
});

const { state: toggleOnboardingEarlyCheckState } = useToggleOnboardingEarlyCheck({
deviceId: lastSeenDevice?.deviceId ?? "",
toggleType: toggleOnboardingEarlyCheckType,
});

const refreshIsBootloaderMode = useCallback(() => {
if (!device) return;
withDevice(device.deviceId)(transport => from(getDeviceInfo(transport)))
.toPromise()
.then((deviceInfo: DeviceInfo) => {
setIsBootloader(deviceInfo?.isBootloader);
});
}, [device]);

useEffect(() => {
let dead = false;
function refreshIsBootloaderMode() {
if (!device) return;
withDevice(device.deviceId)(transport => from(getDeviceInfo(transport)))
.toPromise()
.then(({ isBootloader }: DeviceInfo) => {
if (dead) return;
setIsBootloader(isBootloader);
})
.catch(error => {
if (dead) return;
if (error instanceof LockedDeviceError) {
// Here we just want to know if the device is in bootloader mode.
// It can't be locked in bootloader mode so we can just ignore the
// error, another LockedDeviceError error will be handled in the
// polling hook.
setIsBootloader(false);
return;
}
logger.error(error);
setDrawer(
ErrorDrawer,
{
onClickRetry: () => {
setDrawer();
refreshIsBootloaderMode();
},
error,
},
{ preventBackdropClick: true, forceDisableFocusTrap: true },
);
});
}
refreshIsBootloaderMode();
}, [device, refreshIsBootloaderMode]);
return () => {
dead = true;
};
}, [device]);

// Called when the ESC is complete
const notifyOnboardingEarlyCheckEnded = useCallback(() => {
Expand Down Expand Up @@ -186,7 +215,9 @@ const SyncOnboardingScreen: React.FC<SyncOnboardingScreenProps> = ({

// A fatal error during polling triggers directly an error message
useEffect(() => {
if (fatalError) {
if ((fatalError as unknown) instanceof UnexpectedBootloader) {
setIsBootloader(true);
} else if (fatalError) {
setIsPollingOn(false);
setTroubleshootingDrawerOpen(true);
}
Expand Down Expand Up @@ -307,8 +338,8 @@ const SyncOnboardingScreen: React.FC<SyncOnboardingScreenProps> = ({
);
}

const onDeviceActionResult = useCallback((result: Result) => {
setIsBootloader(result.deviceInfo.isBootloader);
const onDeviceActionResult = useCallback(({ deviceInfo: { isBootloader } }: Result) => {
setIsBootloader(isBootloader);
}, []);

return (
Expand Down

0 comments on commit a4a6fda

Please sign in to comment.