@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
106 lines • 4.61 kB
JavaScript
import { Observable, concat, concatWith, from, of, throwError } from "rxjs";
import { concatMap, catchError, delay } from "rxjs/operators";
import { TransportStatusError, DeviceOnDashboardExpected, StatusCodes, LockedDeviceError, } from "@ledgerhq/errors";
import { isCharonSupported } from "@ledgerhq/device-core";
import { identifyTargetId } from "@ledgerhq/devices";
import { PrepareConnectManagerDeviceAction } from "@ledgerhq/live-dmk-shared";
import { listAppsUseCase } from "../device/use-cases/listAppsUseCase";
import { withDevice } from "./deviceAccess";
import getDeviceInfo from "./getDeviceInfo";
import getAppAndVersion from "./getAppAndVersion";
import { isDashboardName } from "./isDashboardName";
import { DeviceNotOnboarded } from "../errors";
import attemptToQuitApp from "./attemptToQuitApp";
import { PrepareConnectManagerEventMapper } from "./connectManagerEventMapper";
import { extractOnboardingState, OnboardingStep } from "./extractOnboardingState";
import { isDmkTransport } from "./dmkUtils";
const cmd = (transport, { request }) => new Observable(o => {
const timeoutSub = of({
type: "unresponsiveDevice",
})
.pipe(delay(1000))
.subscribe(e => o.next(e));
const sub = from(getDeviceInfo(transport))
.pipe(concatMap(deviceInfo => {
timeoutSub.unsubscribe();
if (!deviceInfo.onboarded && !deviceInfo.isRecoveryMode) {
throw new DeviceNotOnboarded();
}
if (deviceInfo.isBootloader) {
return of({
type: "bootloader",
deviceInfo,
});
}
if (deviceInfo.isOSU) {
return of({
type: "osu",
deviceInfo,
});
}
if (isCharonSupported(deviceInfo.seVersion ?? "", identifyTargetId(deviceInfo.seTargetId ?? 0)?.id)) {
const onboardingState = extractOnboardingState(deviceInfo.seFlags, deviceInfo.charonState);
if (onboardingState.currentOnboardingStep === OnboardingStep.BackupCharon) {
throw new DeviceNotOnboarded();
}
}
return concat(of({
type: "listingApps",
deviceInfo,
}), listAppsUseCase(transport, deviceInfo));
}), catchError((e) => {
if (e instanceof LockedDeviceError) {
return of({
type: "lockedDevice",
});
}
else if (e instanceof DeviceOnDashboardExpected ||
(e &&
e instanceof TransportStatusError &&
[
StatusCodes.CLA_NOT_SUPPORTED,
StatusCodes.INS_NOT_SUPPORTED,
StatusCodes.UNKNOWN_APDU,
0x6e01, // No StatusCodes definition
0x6d01, // No StatusCodes definition
].includes(e.statusCode))) {
return from(getAppAndVersion(transport)).pipe(concatMap(appAndVersion => {
return !request?.autoQuitAppDisabled && !isDashboardName(appAndVersion.name)
? attemptToQuitApp(transport, appAndVersion)
: of({
type: "appDetected",
});
}));
}
return throwError(() => e);
}))
.subscribe(o);
return () => {
timeoutSub.unsubscribe();
sub.unsubscribe();
};
});
export default function connectManagerFactory({ isLdmkConnectAppEnabled, } = { isLdmkConnectAppEnabled: false }) {
if (!isLdmkConnectAppEnabled) {
return ({ deviceId, deviceName, request }) => withDevice(deviceId, deviceName ? { matchDeviceByName: deviceName } : undefined)(transport => cmd(transport, { deviceId, deviceName, request }));
}
return ({ deviceId, deviceName, request }) => withDevice(deviceId, deviceName ? { matchDeviceByName: deviceName } : undefined)(transport => {
if (!isDmkTransport(transport)) {
return cmd(transport, { deviceId, deviceName, request });
}
const { dmk, sessionId } = transport;
const deviceAction = new PrepareConnectManagerDeviceAction({
input: {
unlockTimeout: 0, // Expect to fail immediately when device is locked
},
});
const observable = dmk.executeDeviceAction({
sessionId,
deviceAction,
});
return new PrepareConnectManagerEventMapper(observable)
.map()
.pipe(concatWith(cmd(transport, { deviceId, deviceName, request })));
});
}
//# sourceMappingURL=connectManager.js.map