@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
128 lines • 6.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isAllowedOnboardingStatePollingError = exports.getOnboardingStatePolling = void 0;
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const getVersionUseCase_1 = require("../device/use-cases/getVersionUseCase");
const deviceAccess_1 = require("./deviceAccess");
const errors_1 = require("@ledgerhq/errors");
const extractOnboardingState_1 = require("./extractOnboardingState");
const device_management_kit_1 = require("@ledgerhq/device-management-kit");
const quitApp_1 = require("../deviceSDK/commands/quitApp");
/**
* 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
*/
const getOnboardingStatePolling = ({ deviceId, deviceName, pollingPeriodMs, transportAbortTimeoutMs = pollingPeriodMs - 100, safeGuardTimeoutMs = pollingPeriodMs * 10, // Nb Empirical value
allowedErrorChecks = [], }) => {
let hasQuitAppAlreadyRun = false;
const getOnboardingStateOnce = () => {
return (0, deviceAccess_1.withDevice)(deviceId, {
openTimeoutMs: transportAbortTimeoutMs,
matchDeviceByName: deviceName ?? undefined,
})(t => {
const getVersionObs = (0, rxjs_1.defer)(() => (0, rxjs_1.from)((0, getVersionUseCase_1.getVersion)(t, { abortTimeoutMs: transportAbortTimeoutMs })));
return getVersionObs.pipe((0, operators_1.catchError)((error) => {
const isApduNotSupported = error instanceof errors_1.TransportStatusError &&
[errors_1.StatusCodes.CLA_NOT_SUPPORTED, errors_1.StatusCodes.INS_NOT_SUPPORTED].includes(error.statusCode);
if (isApduNotSupported && !hasQuitAppAlreadyRun) {
hasQuitAppAlreadyRun = true;
return (0, quitApp_1.quitApp)(t).pipe((0, operators_1.switchMap)(() => getVersionObs));
}
return (0, rxjs_1.throwError)(() => error);
}));
}).pipe((0, operators_1.timeout)(safeGuardTimeoutMs), // Throws a TimeoutError
(0, operators_1.first)(), (0, operators_1.catchError)((error) => {
if ((0, exports.isAllowedOnboardingStatePollingError)(error) ||
allowedErrorChecks?.some(fn => fn(error))) {
// Pushes the error to the next step to be processed (no retry from the beginning)
return (0, rxjs_1.of)(error);
}
return (0, rxjs_1.throwError)(() => error);
}), (0, operators_1.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 errors_1.UnexpectedBootloader("Device in bootloader during the polling");
}
try {
onboardingState = (0, extractOnboardingState_1.extractOnboardingState)(firmwareInfo.flags, firmwareInfo.charonState);
}
catch (error) {
if (error instanceof errors_1.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 errors_1.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 errors_1.LockedDeviceError,
};
}
}));
};
return getOnboardingStateOnce().pipe((0, operators_1.repeat)({
delay: _count => (0, rxjs_1.timer)(pollingPeriodMs),
}));
};
exports.getOnboardingStatePolling = getOnboardingStatePolling;
const isAllowedOnboardingStatePollingError = (error) => {
if (error &&
// Timeout error is thrown by rxjs's timeout
(error instanceof rxjs_1.TimeoutError ||
error instanceof errors_1.TransportExchangeTimeoutError ||
error instanceof errors_1.DisconnectedDevice ||
error instanceof errors_1.DisconnectedDeviceDuringOperation ||
error instanceof device_management_kit_1.DeviceDisconnectedWhileSendingError ||
error instanceof errors_1.CantOpenDevice ||
error instanceof errors_1.TransportRaceCondition ||
error instanceof errors_1.TransportStatusError ||
// A locked device is handled as an allowed error
error instanceof errors_1.LockedDeviceError)) {
return true;
}
return false;
};
exports.isAllowedOnboardingStatePollingError = isAllowedOnboardingStatePollingError;
//# sourceMappingURL=getOnboardingStatePolling.js.map