UNPKG

@ledgerhq/live-common

Version:
107 lines 5.63 kB
import { from, of, throwError, TimeoutError, timer } from "rxjs"; import { map, catchError, first, timeout, repeat } from "rxjs/operators"; import { getVersion } from "../device/use-cases/getVersionUseCase"; import { withDevice } from "./deviceAccess"; import { TransportStatusError, DeviceOnboardingStatePollingError, DeviceExtractOnboardingStateError, DisconnectedDevice, CantOpenDevice, TransportRaceCondition, LockedDeviceError, UnexpectedBootloader, TransportExchangeTimeoutError, DisconnectedDeviceDuringOperation, } from "@ledgerhq/errors"; import { extractOnboardingState } from "./extractOnboardingState"; import { DeviceDisconnectedWhileSendingError } from "@ledgerhq/device-management-kit"; /** * Polls the device onboarding state at a given frequency * * @param deviceId A device id * @param pollingPeriodMs The period in ms after which the device onboarding state is fetched again * @param transportAbortTimeoutMs Depending on the transport implementation, an "abort timeout" will be set (and throw an error) when: * - opening a transport instance * - on called commands (where `getVersion`) * Default to (pollingPeriodMs - 100) ms * @param safeGuardTimeoutMs For Transport implementations not implementing an "abort timeout", a timeout will be triggered (and throw an error) at this function call level * @returns An Observable that polls the device onboarding state and pushes an object containing: * - onboardingState: the device state during the onboarding * - allowedError: any error that is allowed and does not stop the polling * - lockedDevice: a boolean set to true if the device is currently locked, false otherwise */ export const getOnboardingStatePolling = ({ deviceId, pollingPeriodMs, transportAbortTimeoutMs = pollingPeriodMs - 100, safeGuardTimeoutMs = pollingPeriodMs * 10, // Nb Empirical value allowedErrorChecks = [], }) => { const getOnboardingStateOnce = () => { return withDevice(deviceId, { openTimeoutMs: transportAbortTimeoutMs })(t => from(getVersion(t, { abortTimeoutMs: transportAbortTimeoutMs }))).pipe(timeout(safeGuardTimeoutMs), // Throws a TimeoutError first(), catchError((error) => { if (isAllowedOnboardingStatePollingError(error) || allowedErrorChecks?.some(fn => fn(error))) { // Pushes the error to the next step to be processed (no retry from the beginning) return of(error); } return throwError(() => error); }), map((event) => { if ("flags" in event) { const firmwareInfo = event; let onboardingState = null; if (firmwareInfo.isBootloader) { // Throws so it will be considered a fatal error throw new UnexpectedBootloader("Device in bootloader during the polling"); } try { onboardingState = extractOnboardingState(firmwareInfo.flags, firmwareInfo.charonState); } catch (error) { if (error instanceof DeviceExtractOnboardingStateError) { return { onboardingState: null, allowedError: error, lockedDevice: false, }; } else { let errorMessage = ""; if (error instanceof Error) { errorMessage = `${error.name}: ${error.message}`; } else { errorMessage = `${error}`; } return { onboardingState: null, allowedError: new DeviceOnboardingStatePollingError(`SyncOnboarding: Unknown error while extracting the onboarding state ${errorMessage}`), lockedDevice: false, }; } } return { onboardingState, allowedError: null, lockedDevice: false, }; } else { // If an error is caught previously, and this error is "allowed", // the value from the observable is not a FirmwareInfo but an Error const allowedError = event; return { onboardingState: null, allowedError: allowedError, lockedDevice: allowedError instanceof LockedDeviceError, }; } })); }; return getOnboardingStateOnce().pipe(repeat({ delay: _count => timer(pollingPeriodMs), })); }; export const isAllowedOnboardingStatePollingError = (error) => { if (error && // Timeout error is thrown by rxjs's timeout (error instanceof TimeoutError || error instanceof TransportExchangeTimeoutError || error instanceof DisconnectedDevice || error instanceof DisconnectedDeviceDuringOperation || error instanceof DeviceDisconnectedWhileSendingError || error instanceof CantOpenDevice || error instanceof TransportRaceCondition || error instanceof TransportStatusError || // A locked device is handled as an allowed error error instanceof LockedDeviceError)) { return true; } return false; }; //# sourceMappingURL=getOnboardingStatePolling.js.map