UNPKG

@ledgerhq/live-common

Version:
268 lines • 9.43 kB
"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