UNPKG

@ledgerhq/live-common

Version:
342 lines • 15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConnectAppEventMapper = void 0; const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const device_management_kit_1 = require("@ledgerhq/device-management-kit"); const errors_1 = require("@ledgerhq/errors"); const ids_1 = require("@ledgerhq/client-ids/ids"); const types_1 = require("../apps/types"); const getDeviceInfo_1 = require("../deviceSDK/tasks/getDeviceInfo"); const errors_2 = require("../errors"); class ConnectAppEventMapper { dmk; sessionId; appName; events; openAppRequested = false; permissionRequested = false; lastSeenDeviceSent = false; installPlan = null; deviceId = undefined; eventSubject = new rxjs_1.Subject(); constructor(dmk, sessionId, appName, events) { this.dmk = dmk; this.sessionId = sessionId; this.appName = appName; this.events = events; } map() { const cancelAction = this.events.cancel; const unsubscribe = new rxjs_1.Subject(); // Create a shared observable for device session state const deviceSessionState = this.dmk .getDeviceSessionState({ sessionId: this.sessionId }) .pipe((0, rxjs_1.share)()); // Subscribe to device action events this.events.observable .pipe((0, operators_1.withLatestFrom)(deviceSessionState), (0, operators_1.tap)(([event, deviceState]) => this.handleEvent(event, deviceState)), (0, rxjs_1.takeUntil)(unsubscribe), (0, operators_1.catchError)(error => this.handleError(error))) .subscribe(); // Subscribe to device session state events deviceSessionState .pipe((0, operators_1.tap)(deviceState => this.handleDeviceState(deviceState)), (0, rxjs_1.takeUntil)(unsubscribe)) .subscribe(); return new rxjs_1.Observable(observer => { const sub = this.eventSubject.subscribe(observer); return () => { sub.unsubscribe(); cancelAction(); unsubscribe.next(); }; }); } handleDeviceState(deviceState) { if (deviceState.sessionStateType === device_management_kit_1.DeviceSessionStateType.Connected) { return; } if (deviceState.firmwareVersion?.metadata && deviceState.firmwareUpdateContext && !this.lastSeenDeviceSent) { this.lastSeenDeviceSent = true; this.eventSubject.next({ type: "device-update-last-seen", deviceInfo: this.mapDeviceInfo(deviceState.firmwareVersion.metadata), latestFirmware: this.mapLatestFirmware(deviceState.firmwareUpdateContext), }); } } handleEvent(event, deviceState) { switch (event.status) { case device_management_kit_1.DeviceActionStatus.Pending: this.handlePendingEvent(event.intermediateValue); break; case device_management_kit_1.DeviceActionStatus.Completed: this.handleCompletedEvent(event.output, deviceState); break; case device_management_kit_1.DeviceActionStatus.Error: this.handleErrorEvent(event.error, deviceState); break; case device_management_kit_1.DeviceActionStatus.NotStarted: case device_management_kit_1.DeviceActionStatus.Stopped: this.eventSubject.error(new Error("Unexpected device action status")); break; } } handlePendingEvent(intermediateValue) { switch (intermediateValue.requiredUserInteraction) { case device_management_kit_1.UserInteractionRequired.ConfirmOpenApp: if (!this.openAppRequested) { this.openAppRequested = true; this.eventSubject.next({ type: "ask-open-app", appName: this.appName }); } break; case device_management_kit_1.UserInteractionRequired.AllowSecureConnection: if (!this.permissionRequested) { this.permissionRequested = true; this.eventSubject.next({ type: "device-permission-requested" }); } break; case device_management_kit_1.UserInteractionRequired.UnlockDevice: this.eventSubject.next({ type: "lockedDevice" }); break; case device_management_kit_1.UserInteractionRequired.None: if (this.openAppRequested) { this.openAppRequested = false; this.eventSubject.next({ type: "device-permission-granted" }); } if (this.permissionRequested) { this.permissionRequested = false; this.eventSubject.next({ type: "device-permission-granted" }); // Simulate apps listing (not systematic step in LDMK) this.eventSubject.next({ type: "listing-apps" }); } if (intermediateValue.installPlan !== null) { this.handleInstallPlan(intermediateValue.installPlan); } if (intermediateValue.deviceId) { const deviceIdString = Buffer.from(intermediateValue.deviceId).toString("hex"); if (deviceIdString !== this.deviceId) { this.deviceId = deviceIdString; this.eventSubject.next({ type: "device-id", deviceId: ids_1.DeviceId.fromString(deviceIdString), }); } } break; case "device-deprecation": if (intermediateValue.deviceDeprecation) { this.eventSubject.next({ type: "deprecation", deprecate: { ...intermediateValue.deviceDeprecation, }, }); } } } handleInstallPlan(installPlan) { // Handle install plan resolved events if (this.installPlan === null) { // Skipped applications const alreadyInstalled = this.mapSkippedApps(installPlan.alreadyInstalled, types_1.SkipReason.AppAlreadyInstalled); const missing = this.mapSkippedApps(installPlan.missingApplications, types_1.SkipReason.NoSuchAppOnProvider); const skippedAppOps = [...alreadyInstalled, ...missing]; if (skippedAppOps.length > 0) { this.eventSubject.next({ type: "some-apps-skipped", skippedAppOps, }); } // Install queue content this.eventSubject.next({ type: "listed-apps", installQueue: installPlan.installPlan.map(app => app.versionName), }); } // Handle ongoing install events this.eventSubject.next({ type: "inline-install", progress: installPlan.currentProgress, itemProgress: installPlan.currentIndex, currentAppOp: { type: "install", name: installPlan.installPlan[installPlan.currentIndex].versionName, }, installQueue: installPlan.installPlan .map(app => app.versionName) .slice(installPlan.currentIndex), }); this.installPlan = installPlan; } handleCompletedEvent(output, deviceState) { if (deviceState.sessionStateType !== device_management_kit_1.DeviceSessionStateType.Connected) { // Handle opened app const currentApp = deviceState.currentApp; let flags = 0; if (typeof currentApp.flags === "number") { flags = currentApp.flags; } else if (currentApp.flags !== undefined) { flags = Buffer.from(currentApp.flags); } this.eventSubject.next({ type: "opened", app: { name: currentApp.name, version: currentApp.version, flags, }, derivation: output.derivation ? { address: output.derivation } : undefined, }); } this.eventSubject.complete(); } handleErrorEvent(error, deviceState) { if (error instanceof device_management_kit_1.OutOfMemoryDAError && this.installPlan !== null) { const appNames = this.installPlan.installPlan.map(app => app.versionName); this.eventSubject.next({ type: "app-not-installed", appNames, appName: appNames[0], }); this.eventSubject.complete(); } else if ("_tag" in error && error._tag === "UnsupportedFirmwareDAError" && deviceState.sessionStateType !== device_management_kit_1.DeviceSessionStateType.Connected) { this.eventSubject.error(new errors_1.LatestFirmwareVersionRequired("LatestFirmwareVersionRequired", { current: deviceState.firmwareUpdateContext.currentFirmware.version, latest: deviceState.firmwareUpdateContext?.availableUpdate?.finalFirmware.version || deviceState.firmwareUpdateContext.currentFirmware.version, })); } else if ("_tag" in error && error._tag === "UnsupportedApplicationDAError" && deviceState.sessionStateType !== device_management_kit_1.DeviceSessionStateType.Connected) { if (deviceState.deviceModelId === device_management_kit_1.DeviceModelId.NANO_S) { // This will show an error modal with upsell link this.eventSubject.error(new errors_2.NoSuchAppOnProvider(`Ledger Nano S does not support this feature`, { appName: this.appName, })); return; } // This will show an error modal with contact support link this.eventSubject.error(new errors_1.UnsupportedFeatureError(`App ${this.appName} not supported on this device`, { appName: this.appName, deviceModelId: deviceState.deviceModelId, deviceVersion: deviceState.firmwareVersion?.os, })); } else if ("_tag" in error && error._tag === "DeviceLockedError") { this.eventSubject.next({ type: "lockedDevice" }); this.eventSubject.complete(); } else if ("_tag" in error && error._tag === "RefusedByUserDAError") { this.eventSubject.error(new errors_1.UserRefusedAllowManager()); } else if ("_tag" in error && error._tag === "DeviceDisconnectedWhileSendingError") { this.eventSubject.next({ type: "disconnected", expected: false }); } else if ("_tag" in error && error._tag === "WebHidSendReportError") { this.eventSubject.next({ type: "disconnected", expected: false }); this.eventSubject.complete(); } else if ("errorCode" in error && typeof error.errorCode === "string" && "_tag" in error) { this.eventSubject.error(this.mapDeviceError(error.errorCode, error._tag)); } else { this.eventSubject.error(error); } } handleError(error) { this.eventSubject.error(error); return (0, rxjs_1.of)(); } mapDeviceError(errorCode, defaultMessage) { switch (errorCode) { case "5501": return new errors_1.UserRefusedOnDevice(); default: return new Error(defaultMessage); } } mapSkippedApps(appNames, reason) { return appNames.map(name => ({ reason, appOp: { type: "install", name, }, })); } mapDeviceInfo(osVersion) { return (0, getDeviceInfo_1.parseDeviceInfo)({ isBootloader: osVersion.isBootloader, rawVersion: osVersion.seVersion, // Always the SE version since at this step we cannot be in bootloader mode targetId: osVersion.targetId, seVersion: osVersion.seVersion, seTargetId: osVersion.seTargetId, mcuBlVersion: undefined, // We cannot be in bootloader mode at this step mcuVersion: osVersion.mcuSephVersion, mcuTargetId: osVersion.mcuTargetId, flags: Buffer.from(osVersion.seFlags), bootloaderVersion: osVersion.mcuBootloaderVersion, hardwareVersion: parseInt(osVersion.hwVersion, 16), languageId: osVersion.langId, }); } mapLatestFirmware(updateContext) { if (!updateContext.availableUpdate) { return null; } const availableUpdate = updateContext.availableUpdate; const osu = availableUpdate.osuFirmware; const final = availableUpdate.finalFirmware; return { osu: { id: osu.id, perso: osu.perso, firmware: osu.firmware, firmware_key: osu.firmwareKey, hash: osu.hash || "", next_se_firmware_final_version: osu.nextFinalFirmware, // Following fields are inherited from dto, but unused description: undefined, display_name: undefined, notes: undefined, name: "", date_creation: "", date_last_modified: "", device_versions: [], providers: [], previous_se_firmware_final_version: [], }, final: { id: final.id, name: final.version, version: final.version, perso: final.perso, firmware: final.firmware || "", firmware_key: final.firmwareKey || "", hash: final.hash || "", bytes: final.bytes || 0, mcu_versions: final.mcuVersions, // Following fields are inherited from dto, but unused description: undefined, display_name: undefined, notes: undefined, se_firmware: 0, date_creation: "", date_last_modified: "", device_versions: [], application_versions: [], osu_versions: [], providers: [], }, shouldFlashMCU: availableUpdate.mcuUpdateRequired, }; } } exports.ConnectAppEventMapper = ConnectAppEventMapper; //# sourceMappingURL=connectAppEventMapper.js.map