"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;
        }
    }
    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,
            targetId: osVersion.targetId,
            seVersion: osVersion.seVersion,
            seTargetId: osVersion.seTargetId,
            mcuBlVersion: undefined,
            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