UNPKG

@ledgerhq/live-common

Version:
228 lines • 9.46 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.findAppCandidate = exports.findLatestAppCandidate = exports.createSpeculosDevice = exports.releaseSpeculosDevice = exports.closeAllSpeculosDevices = void 0; exports.listAppCandidates = listAppCandidates; exports.appCandidatesMatches = appCandidatesMatches; exports.createImplicitSpeculos = createImplicitSpeculos; // Ledger internal speculos testing framework. // loading this file have side effects and is only for Node. const sample_1 = __importDefault(require("lodash/sample")); const invariant_1 = __importDefault(require("invariant")); const path_1 = __importDefault(require("path")); const semver_1 = __importDefault(require("semver")); const fs_1 = require("fs"); const logs_1 = require("@ledgerhq/logs"); const hw_1 = require("../hw"); const live_env_1 = require("@ledgerhq/live-env"); const polyfill_1 = require("../apps/polyfill"); const currencies_1 = require("../currencies"); const formatters_1 = require("../bot/formatters"); const apps_1 = require("../apps"); const speculos_transport_1 = require("@ledgerhq/speculos-transport"); Object.defineProperty(exports, "closeAllSpeculosDevices", { enumerable: true, get: function () { return speculos_transport_1.closeAllSpeculosDevices; } }); Object.defineProperty(exports, "releaseSpeculosDevice", { enumerable: true, get: function () { return speculos_transport_1.releaseSpeculosDevice; } }); Object.defineProperty(exports, "createSpeculosDevice", { enumerable: true, get: function () { return speculos_transport_1.createSpeculosDevice; } }); const modelMapPriority = { apex_p: 7, flex: 6, stax: 5, nanos: 4, "nanos+": 3, nanox: 2, blue: 1, }; const defaultFirmware = {}; function hackBadSemver(str) { const split = str.split("."); const [x, y, , ...rest] = split; let [, , z] = split; if (rest.length) { z += "-" + rest.join("-"); } return [x, y, z].filter(Boolean).join("."); } // list all possible apps. sorted by latest first async function listAppCandidates(cwd) { let candidates = []; const models = (await fs_1.promises.readdir(cwd)) .map(modelName => [modelName, modelMapPriority[modelName.toLowerCase()]]) .filter(([, priority]) => priority) .sort((a, b) => b[1] - a[1]) .map(a => a[0]); for (const modelName of models) { const model = speculos_transport_1.modelMap[modelName.toLowerCase()]; const p1 = path_1.default.join(cwd, modelName); const firmwares = await fs_1.promises.readdir(p1); firmwares.sort((a, b) => semver_1.default.compare(hackBadSemver(a), hackBadSemver(b))); firmwares.reverse(); for (const firmware of firmwares) { const p2 = path_1.default.join(p1, firmware); const appNames = await fs_1.promises.readdir(p2); for (const appName of appNames) { const p3 = path_1.default.join(p2, appName); const elfs = await fs_1.promises.readdir(p3); const c = []; for (const elf of elfs) { if (elf.startsWith("app_") && elf.endsWith(".elf")) { const p4 = path_1.default.join(p3, elf); const appVersion = elf.slice(4, elf.length - 4); if (semver_1.default.valid(appVersion) && !(0, apps_1.shouldUpgrade)(appName, appVersion) && !(0, apps_1.mustUpgrade)(appName, appVersion)) { c.push({ path: p4, model, firmware, appName, appVersion, }); } } } c.sort((a, b) => semver_1.default.compare(a.appVersion, b.appVersion)); c.reverse(); candidates = candidates.concat(c); } } } return candidates; } function appCandidatesMatches(appCandidate, search) { const searchFirmware = search.firmware || defaultFirmware[appCandidate.model]; return !!((!search.model || search.model === appCandidate.model) && (!search.appName || search.appName.replace(/ /g, "").toLowerCase() === appCandidate.appName.replace(/ /g, "").toLowerCase()) && ((!searchFirmware && !appCandidate.firmware.includes("rc")) || appCandidate.firmware === searchFirmware || (searchFirmware && semver_1.default.satisfies(hackBadSemver(appCandidate.firmware), searchFirmware))) && (appCandidate.appVersion === search.appVersion || (!search.appVersion && !appCandidate.appVersion.includes("-")) || (search.appVersion && semver_1.default.satisfies(appCandidate.appVersion, search.appVersion)))); } const findLatestAppCandidate = (appCandidates, search) => { search.firmware = process.env.SPECULOS_FIRMWARE_VERSION; let apps = appCandidates.filter(c => appCandidatesMatches(c, search)); if (apps.length === 0) { return null; } apps = apps.sort((a, b) => semver_1.default.compare(b.appVersion, a.appVersion)); return apps[0]; }; exports.findLatestAppCandidate = findLatestAppCandidate; const findAppCandidate = (appCandidates, search, picker = sample_1.default) => { let apps = appCandidates.filter(c => appCandidatesMatches(c, search)); if (!search.appVersion && apps.length > 0) { const appVersion = apps[0].appVersion; apps = apps.filter(a => a.appVersion === appVersion); } const app = picker(apps); if (apps.length > 1) { (0, logs_1.log)("speculos", apps.length + " app candidates (out of " + appCandidates.length + "):\n" + apps.map((a, i) => " [" + i + "] " + (0, formatters_1.formatAppCandidate)(a)).join("\n")); } return app; }; exports.findAppCandidate = findAppCandidate; function eatDevice(parts) { if (parts.length > 0) { const [modelQ, firmware] = parts[0].split("@"); const model = speculos_transport_1.modelMap[(modelQ || "").toLowerCase()]; if (model) { parts.shift(); if (firmware) { return { model, firmware, }; } return { model, }; } } return {}; } function parseAppSearch(query) { const parts = query.slice(9).split(":"); const { model, firmware } = eatDevice(parts); if (parts.length === 0) return; const [nameQ, versionQ] = parts[0].split("@"); const currency = (0, currencies_1.findCryptoCurrencyByKeyword)(nameQ); const appName = currency ? currency.managerAppName : nameQ; const appVersion = versionQ || undefined; let dependency; if (currency) { dependency = (0, polyfill_1.getDependencies)(currency.managerAppName)[0]?.replace(/ /g, ""); } return { search: { model, firmware, appName, appVersion, }, appName, dependency, }; } async function createImplicitSpeculos(query) { const coinapps = (0, live_env_1.getEnv)("COINAPPS"); (0, invariant_1.default)(coinapps, "COINAPPS folder is missing!"); const seed = (0, live_env_1.getEnv)("SEED"); (0, invariant_1.default)(seed, "SEED is missing!"); const apps = await listAppCandidates(coinapps); const match = parseAppSearch(query); (0, invariant_1.default)(match, "speculos: invalid format of '%s'. Usage example: speculos:nanoS:bitcoin@1.3.x", query); const { search, dependency, appName } = match; const appCandidate = (0, exports.findAppCandidate)(apps, search); (0, invariant_1.default)(appCandidate, "could not find an app that matches '%s'", query); (0, logs_1.log)("speculos", "using app " + (0, formatters_1.formatAppCandidate)(appCandidate)); return appCandidate ? { device: await (0, speculos_transport_1.createSpeculosDevice)({ ...appCandidate, coinapps, appName, dependency, seed, }), appCandidate, } : null; } async function openImplicitSpeculos(query) { const r = await createImplicitSpeculos(query); return r?.device.transport; } (0, hw_1.registerTransportModule)({ id: "speculos", open: (id) => { if (!id) return; if (id.startsWith("speculosID")) { const obj = (0, speculos_transport_1.getMemorySpeculosDeviceInternal)(id); if (!obj) { throw new Error("speculos transport was destroyed"); } return Promise.resolve(obj.transport); } if (id.startsWith("speculos:")) { return openImplicitSpeculos(id); } }, close: (transport, id) => { if (id.startsWith("speculos")) { return Promise.resolve(); } // todo close the speculos: case }, disconnect: speculos_transport_1.releaseSpeculosDevice, }); //# sourceMappingURL=speculos.js.map