@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
111 lines (102 loc) • 3.43 kB
text/typescript
import { log } from "@ledgerhq/logs";
import { Observable, from, of, EMPTY, concat, throwError } from "rxjs";
import { concatMap, delay, scan, distinctUntilChanged, throttleTime } from "rxjs/operators";
import { CantOpenDevice, DeviceInOSUExpected } from "@ledgerhq/errors";
import { withDevicePolling, withDevice } from "./deviceAccess";
import getDeviceInfo from "./getDeviceInfo";
import flash from "./flash";
import installFinalFirmware from "./installFinalFirmware";
import { hasFinalFirmware } from "./hasFinalFirmware";
import type { FirmwareUpdateContext } from "@ledgerhq/types-live";
const wait2s = of({
type: "wait",
}).pipe(delay(2000));
type Res = {
installing: string | null | undefined;
progress: number;
};
const main = (
deviceId: string,
{ final, shouldFlashMCU }: FirmwareUpdateContext,
): Observable<Res> => {
log("hw", "firmwareUpdate-main started");
const withFinal = hasFinalFirmware(final);
const withDeviceInfo = withDevicePolling(deviceId)(
transport => from(getDeviceInfo(transport)),
() => true, // accept all errors. we're waiting forever condition that make getDeviceInfo work
);
const withDeviceInstall = install =>
withDevicePolling(deviceId)(
install,
e => e instanceof CantOpenDevice, // this can happen if withDevicePolling was still seeing the device but it was then interrupted by a device reboot
);
const waitForBootloader = withDeviceInfo.pipe(
concatMap(deviceInfo => (deviceInfo.isBootloader ? EMPTY : concat(wait2s, waitForBootloader))),
);
const potentialAutoFlash = withDeviceInfo.pipe(
concatMap(deviceInfo =>
deviceInfo.isOSU
? EMPTY
: withDevice(deviceId)(
transport =>
new Observable(o => {
const timeout = setTimeout(() => {
log("firmware", "potentialAutoFlash timeout");
o.complete();
}, 20000);
const disconnect = () => {
log("firmware", "potentialAutoFlash disconnect");
o.complete();
};
transport.on("disconnect", disconnect);
return () => {
clearTimeout(timeout);
transport.off("disconnect", disconnect);
};
}),
),
),
);
const bootloaderLoop = withDeviceInfo.pipe(
concatMap(deviceInfo =>
deviceInfo.isBootloader
? concat(withDeviceInstall(flash(final)), wait2s, bootloaderLoop)
: EMPTY,
),
);
const finalStep = !withFinal
? EMPTY
: withDeviceInfo.pipe(
concatMap(deviceInfo =>
!deviceInfo.isOSU
? throwError(() => new DeviceInOSUExpected())
: withDeviceInstall(installFinalFirmware),
),
);
const all = shouldFlashMCU
? concat(waitForBootloader, bootloaderLoop, finalStep)
: concat(potentialAutoFlash, finalStep);
return all.pipe(
scan(
(acc: Res, e: any): Res => {
if (e.type === "install") {
return {
installing: e.step,
progress: 0,
};
}
if (e.type === "bulk-progress") {
return { ...acc, progress: e.progress };
}
return acc;
},
{
progress: 0,
installing: null,
},
),
distinctUntilChanged(),
throttleTime(100),
);
};
export default main;