@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
153 lines • 5.3 kB
JavaScript
import axios from "axios";
import { conventionalAppSubpath, reverseModelMap, } from "@ledgerhq/speculos-transport";
import https from "https";
import { sanitizeError } from "./index";
import { v4 as uuid } from "uuid";
const { GITHUB_TOKEN, SPECULOS_IMAGE_TAG } = process.env;
const GIT_API_URL = "https://api.github.com/repos/LedgerHQ/actions/actions/";
const START_WORKFLOW_ID = "workflows/161487603/dispatches";
const STOP_WORKFLOW_ID = "workflows/161487604/dispatches";
const GITHUB_REF = "main";
const getSpeculosAddress = (runId) => `https://${runId}.speculos.aws.stg.ldg-tech.com`;
const speculosPort = 443;
function uniqueId() {
return uuid();
}
function slugify(name) {
return name
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
}
/**
* Helper function to make API requests with error handling
*/
async function githubApiRequest({ method = "POST", urlSuffix, data, params, }) {
const url = `${GIT_API_URL}${urlSuffix}`;
try {
const response = await axios({
method,
url,
headers: {
Authorization: `Bearer ${GITHUB_TOKEN}`,
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
data,
params,
});
return response.data;
}
catch (error) {
console.warn(`API Request failed: ${method} ${url}`, sanitizeError(error));
throw sanitizeError(error);
}
}
export function waitForSpeculosReady(deviceId, { interval = 2_000, timeout = 150_000 } = {}) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
let currentRequest = null;
const url = getSpeculosAddress(deviceId);
function cleanup() {
if (currentRequest) {
currentRequest.destroy();
currentRequest = null;
}
}
function check() {
cleanup();
currentRequest = https.get(url, { timeout: 10000 }, res => {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 400) {
process.env.SPECULOS_ADDRESS = url;
cleanup();
console.warn(`Speculos is ready at ${url}`);
resolve(true);
}
else {
console.warn(`Speculos not ready yet, status: ${res.statusCode}`);
retry();
}
});
currentRequest.on("error", error => {
console.error(`Request error: ${error.message}`);
retry();
});
currentRequest.on("timeout", () => {
console.error("Request timeout");
retry();
});
}
function retry() {
if (Date.now() - startTime >= timeout) {
cleanup();
reject(new Error(`Timeout: ${url} did not become available within ${timeout}ms`));
}
else {
setTimeout(check, interval);
}
}
check();
});
}
function createStartPayload(deviceParams, runId) {
const { model, firmware, appName, appVersion, dependencies } = deviceParams;
let additional_args = "-p";
if (dependencies?.length) {
additional_args = [
additional_args,
...new Set(dependencies.map(dep => `-l ${dep.name}:/apps/${conventionalAppSubpath(model, firmware, dep.name, dep.appVersion ?? appVersion)}`)),
].join(" ");
}
return {
ref: GITHUB_REF,
inputs: {
speculos_version: SPECULOS_IMAGE_TAG?.split(":")[1] || "master",
coin_app: appName,
coin_app_version: appVersion,
device: reverseModelMap[model],
device_os_version: firmware,
run_id: runId,
additional_args,
},
};
}
export async function createSpeculosDeviceCI(deviceParams) {
const runId = `${slugify(deviceParams.appName)}-${uniqueId()}`;
try {
const data = createStartPayload(deviceParams, runId);
await githubApiRequest({ urlSuffix: START_WORKFLOW_ID, data });
return {
id: runId,
port: speculosPort,
appName: deviceParams.appName,
appVersion: deviceParams.appVersion,
dependencies: deviceParams.dependencies,
};
}
catch (error) {
console.warn(`Failed to create remote Speculos ${deviceParams.appName}:${deviceParams.appVersion}:`, sanitizeError(error));
return {
id: runId,
port: 0,
appName: deviceParams.appName,
appVersion: deviceParams.appVersion,
dependencies: deviceParams.dependencies,
};
}
}
export async function releaseSpeculosDeviceCI(runId) {
const data = {
ref: GITHUB_REF,
inputs: {
run_id: runId.toString(),
},
};
try {
await githubApiRequest({ urlSuffix: STOP_WORKFLOW_ID, data });
}
catch (error) {
console.warn(`Failed to release remote Speculos ${runId}:`, sanitizeError(error));
}
}
//# sourceMappingURL=speculosCI.js.map