"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.statusObservable = exports.requiresSatStackReady = exports.checkDescriptorExists = exports.fetchSatStackStatus = exports.isSatStackEnabled = exports.editSatStackConfig = exports.stringifySatStackConfig = exports.parseSatStackConfig = exports.checkRPCNodeConfig = exports.validateRPCNodeConfig = exports.isValidHost = exports.setMockStatus = void 0;
const network_1 = __importDefault(require("@ledgerhq/live-network/network"));
const logs_1 = require("@ledgerhq/logs");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const semver_1 = __importDefault(require("semver"));
const currencies_1 = require("../../currencies");
const live_env_1 = require("@ledgerhq/live-env");
const errors_1 = require("../../errors");
const explorer_1 = require("@ledgerhq/coin-bitcoin/explorer");
const minVersionMatch = ">=0.11.1";
function isAcceptedVersion(version) {
    return !!version && semver_1.default.satisfies(semver_1.default.coerce(version) || "", minVersionMatch);
}
let mockStatus = {
    type: "ready",
};
function setMockStatus(s) {
    mockStatus = s;
}
exports.setMockStatus = setMockStatus;
function isValidHost(host) {
    const pattern = new RegExp("^" + // beginning of url
        "(((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,})|^[a-z\\d-]{2,}|" + // domain name
        "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
        "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*$"); // port and path
    return !!pattern.test(host);
}
exports.isValidHost = isValidHost;
// we would call this only during the user validation of the rpc node configuration
function validateRPCNodeConfig(config) {
    const errors = [];
    if (!config.host) {
        errors.push({
            field: "host",
            error: new errors_1.RPCHostRequired(),
        });
    }
    else if (!isValidHost(config.host)) {
        errors.push({
            field: "host",
            error: new errors_1.RPCHostInvalid(),
        });
    }
    if (!config.username) {
        errors.push({
            field: "username",
            error: new errors_1.RPCUserRequired(),
        });
    }
    if (!config.password) {
        errors.push({
            field: "password",
            error: new errors_1.RPCPassRequired(),
        });
    }
    return errors;
}
exports.validateRPCNodeConfig = validateRPCNodeConfig;
// we would call this only during the "Testing node connection" step
// Check if the node is accessible by RPC. promise fails if not.
async function checkRPCNodeConfig(config) {
    const errors = validateRPCNodeConfig(config);
    if (errors.length) {
        throw errors[0].error;
    }
    if ((0, live_env_1.getEnv)("MOCK")) {
        if (mockStatus.type === "node-disconnected") {
            throw new Error("mock disconnected");
        }
        return;
    }
    const { host, username, password, tls } = config;
    await (0, network_1.default)({
        url: `http${tls ? "s" : ""}://${host}`,
        method: "POST",
        data: {
            jsonrpc: "1.0",
            id: "ledger-live-full-node-check",
            method: "getblockchaininfo",
            params: [],
        },
        auth: {
            username,
            password,
        },
    });
}
exports.checkRPCNodeConfig = checkRPCNodeConfig;
// we would need to call this any time we would "Edit" the flow
function parseSatStackConfig(json) {
    const obj = JSON.parse(json);
    if (obj && typeof obj === "object") {
        const { accounts, rpcurl, rpcuser, rpcpass, notls, ...extra } = obj;
        if (!rpcurl || typeof rpcurl !== "string")
            return;
        if (!rpcuser || typeof rpcuser !== "string")
            return;
        if (!rpcpass || typeof rpcpass !== "string")
            return;
        const result = {
            node: {
                host: rpcurl,
                username: rpcuser,
                password: rpcpass,
                tls: !notls,
            },
            accounts: [],
            extra,
        };
        if (accounts && typeof accounts === "object" && Array.isArray(accounts)) {
            result.accounts = accounts
                .map(a => {
                const { external, internal, ...extra } = a;
                if (!external || typeof external !== "string")
                    return;
                if (!internal || typeof internal !== "string")
                    return;
                return {
                    descriptor: {
                        external,
                        internal,
                    },
                    extra,
                };
            })
                .filter(Boolean);
        }
        return result;
    }
}
exports.parseSatStackConfig = parseSatStackConfig;
// we would need this at the end of the setup flow, when we save to a jss.json configuration file
function stringifySatStackConfig(config) {
    return JSON.stringify({
        accounts: config.accounts.map(a => ({
            external: a.descriptor.external,
            internal: a.descriptor.internal,
            ...a.extra,
        })),
        rpcurl: config.node.host,
        rpcuser: config.node.username,
        rpcpass: config.node.password,
        notls: !config.node.tls,
        ...config.extra,
    }, null, 2);
}
exports.stringifySatStackConfig = stringifySatStackConfig;
// We would need it to apply an edition over an existing sats stack configuration (before saving it over)
function editSatStackConfig(existing, edit) {
    const accounts = existing.accounts.concat(
    // append accounts that would not already exist
    (edit.accounts || []).filter(a => !existing.accounts.some(existing => a.descriptor.internal === existing.descriptor.internal)));
    return {
        ...existing,
        ...edit,
        // edit is patching existing fields
        accounts,
    };
}
exports.editSatStackConfig = editSatStackConfig;
function isSatStackEnabled() {
    return Boolean((0, live_env_1.getEnv)("SATSTACK"));
}
exports.isSatStackEnabled = isSatStackEnabled;
// We would need it any time we want to check if the Sats Stack is up and what status is it at currently
// - during the configuration flow (on last step) (actively polling)
// - on the settings screen (actively polling)
// - before doing a sync
// - before doing an add account
// NB the promise is never rejected
async function fetchSatStackStatus() {
    if (!isSatStackEnabled()) {
        return {
            type: "satstack-disconnected",
        };
    }
    if ((0, live_env_1.getEnv)("MOCK")) {
        return mockStatus;
    }
    const ce = (0, explorer_1.getCurrencyExplorer)((0, currencies_1.getCryptoCurrencyById)("bitcoin"));
    const r = await (0, network_1.default)({
        method: "GET",
        url: `${ce.endpoint}/blockchain/${ce.version}/explorer/status`,
    }).catch(() => null);
    if (!r || !r.data) {
        return {
            type: "satstack-disconnected",
        };
    }
    const { chain, status, sync_progress, scan_progress, version } = r.data;
    if (!isAcceptedVersion(version)) {
        return {
            type: "satstack-outdated",
        };
    }
    if (chain !== "main") {
        return {
            type: "invalid-chain",
            found: chain,
        };
    }
    if (status === "initializing" || status === "pending-scan") {
        return {
            type: "initializing",
        };
    }
    if (status === "node-disconnected") {
        return {
            type: "node-disconnected",
        };
    }
    if (status === "syncing") {
        return {
            type: "syncing",
            progress: (sync_progress || 0) / 100,
        };
    }
    if (status === "scanning") {
        return {
            type: "scanning",
            progress: (scan_progress || 0) / 100,
        };
    }
    return {
        type: "ready",
    };
}
exports.fetchSatStackStatus = fetchSatStackStatus;
async function checkDescriptorExists(descriptor) {
    if ((0, live_env_1.getEnv)("MOCK")) {
        return true;
    }
    const r = await (0, network_1.default)({
        method: "POST",
        url: `${(0, live_env_1.getEnv)("EXPLORER_SATSTACK")}/control/descriptors/has`,
        data: {
            descriptor,
        },
    });
    (0, logs_1.log)("satstack", "checkDescriptorExists " + descriptor + " is " + r.data.exists);
    return Boolean(r.data.exists);
}
exports.checkDescriptorExists = checkDescriptorExists;
async function requiresSatStackReady() {
    if (isSatStackEnabled()) {
        const status = await fetchSatStackStatus();
        switch (status.type) {
            case "ready":
                return;
            case "syncing":
            case "scanning":
                throw new errors_1.SatStackStillSyncing();
            case "satstack-outdated":
                throw new errors_1.SatStackVersionTooOld();
            default:
                throw new errors_1.SatStackAccessDown();
        }
    }
}
exports.requiresSatStackReady = requiresSatStackReady;
exports.statusObservable = (0, rxjs_1.interval)(1000).pipe((0, operators_1.switchMap)(() => (0, rxjs_1.from)(fetchSatStackStatus())), (0, operators_1.filter)((e, i) => i > 4 || e.type !== "satstack-disconnected"), (0, operators_1.share)());
//# sourceMappingURL=satstack.js.map