@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
319 lines • 11.6 kB
JavaScript
/* eslint-disable camelcase */
import { DeviceOnDashboardExpected, FirmwareNotRecognized, ManagerAppAlreadyInstalledError, ManagerDeviceLockedError, ManagerFirmwareNotEnoughSpaceError, ManagerNotEnoughSpaceError, NetworkDown, TransportStatusError, UserRefusedFirmwareUpdate, } from "@ledgerhq/errors";
import { makeLRUCache } from "@ledgerhq/live-network/cache";
import network from "@ledgerhq/live-network/network";
import { log } from "@ledgerhq/logs";
import invariant from "invariant";
import { throwError } from "rxjs";
import { catchError, map } from "rxjs/operators";
import semver from "semver";
import URL from "url";
import { version as livecommonversion } from "../../package.json";
import { getEnv } from "@ledgerhq/live-env";
import { createDeviceSocket } from "../socket";
import { bulkSocketMock, createMockSocket, resultMock, secureChannelMock, } from "../socket/socket.mock";
import { getProviderId } from "./provider";
import { fetchMcusUseCase } from "../device/use-cases/fetchMcusUseCase";
const remapSocketError = (context) => catchError((e) => {
if (!e || !e.message)
return throwError(() => e);
if (e.message.startsWith("invalid literal")) {
// hack to detect the case you're not in good condition (not in dashboard)
return throwError(() => new DeviceOnDashboardExpected());
}
const status = e instanceof TransportStatusError
? e.statusCode.toString(16)
: e.message.slice(e.message.length - 4);
// TODO use StatusCode instead of this.
switch (status) {
case "6a80":
case "6a81":
case "6a8e":
case "6a8f":
return throwError(() => new ManagerAppAlreadyInstalledError());
case "6982":
case "5303":
return throwError(() => new ManagerDeviceLockedError());
case "6a84":
case "5103":
if (context === "firmware" || context === "mcu") {
return throwError(() => new ManagerFirmwareNotEnoughSpaceError());
}
return throwError(() => new ManagerNotEnoughSpaceError());
case "6a85":
case "5102":
if (context === "firmware" || context === "mcu") {
return throwError(() => new UserRefusedFirmwareUpdate());
}
return throwError(() => new ManagerNotEnoughSpaceError());
case "6985":
case "5501":
if (context === "firmware" || context === "mcu") {
return throwError(() => new UserRefusedFirmwareUpdate());
}
return throwError(() => new ManagerNotEnoughSpaceError());
default:
return throwError(() => e);
}
});
/** @deprecated use getAppsCatalogForDevice (from ledger-live-common/src/device/use-cases) instead */
const listApps = makeLRUCache(async () => {
const { data } = await network({
method: "GET",
url: URL.format({
pathname: `${getEnv("MANAGER_API_BASE")}/applications`,
query: {
livecommonversion,
},
}),
});
if (!data || !Array.isArray(data)) {
throw new NetworkDown("");
}
return data;
}, () => getEnv("MANAGER_API_BASE"));
const listCategories = async () => {
const r = await network({
method: "GET",
url: URL.format({
pathname: `${getEnv("MANAGER_API_BASE")}/categories`,
query: {
livecommonversion,
},
}),
});
return r.data;
};
const compatibleMCUForDeviceInfo = (mcus, deviceInfo, provider) => mcus.filter(m => (deviceInfo.majMin === m.from_bootloader_version ||
deviceInfo.version === m.from_bootloader_version) &&
m.providers.includes(provider));
const findBestMCU = (compatibleMCU) => {
let best = compatibleMCU[0];
for (let i = 1; i < compatibleMCU.length; i++) {
if (semver.gt(semver.coerce(compatibleMCU[i].name) || "", semver.coerce(best.name) || "")) {
best = compatibleMCU[i];
}
}
return best;
};
const getLanguagePackagesForDevice = async (deviceInfo) => {
const deviceVersion = await getDeviceVersion(deviceInfo.targetId, getProviderId(deviceInfo));
const seFirmwareVersion = await getCurrentFirmware({
version: deviceInfo.version,
deviceId: deviceVersion.id,
provider: getProviderId(deviceInfo),
});
const { data } = await network({
method: "GET",
url: URL.format({
pathname: `${getEnv("MANAGER_API_BASE")}/language-package`,
query: {
livecommonversion,
},
}),
});
const allPackages = data.reduce((acc, response) => [
...acc,
...response.language_package_version.map(p => ({
...p,
language: response.language,
})),
], []);
const packages = allPackages.filter(pack => pack.device_versions.includes(deviceVersion.id) &&
pack.se_firmware_final_versions.includes(seFirmwareVersion.id));
return packages;
};
const getCurrentOSU = makeLRUCache(async (input) => {
const { data } = await network({
method: "GET",
url: URL.format({
pathname: `${getEnv("MANAGER_API_BASE")}/get_osu_version`,
query: {
livecommonversion,
device_version: input.deviceId,
version_name: `${input.version}-osu`,
provider: input.provider,
},
}),
});
return data;
}, a => `${getEnv("MANAGER_API_BASE")}_${a.version}_${a.deviceId}_${a.provider}`);
const getCurrentFirmware = makeLRUCache(async (input) => {
const { data, } = await network({
method: "GET",
url: URL.format({
pathname: `${getEnv("MANAGER_API_BASE")}/get_firmware_version`,
query: {
livecommonversion,
device_version: input.deviceId,
version_name: input.version,
provider: input.provider,
},
}),
}).catch(error => {
const status = error?.status || error?.response?.status;
if (status === 404)
throw new FirmwareNotRecognized();
throw error;
});
return data;
}, a => `${getEnv("MANAGER_API_BASE")}_${a.version}_${a.deviceId}_${a.provider}`);
const getFinalFirmwareById = makeLRUCache(async (id) => {
const { data, } = await network({
method: "GET",
url: URL.format({
pathname: `${getEnv("MANAGER_API_BASE")}/firmware_final_versions/${id}`,
query: {
livecommonversion,
},
}),
});
return data;
}, id => `${getEnv("MANAGER_API_BASE")}}_${String(id)}`);
const getDeviceVersion = makeLRUCache(async (targetId, provider) => {
const { data, } = await network({
method: "GET",
url: URL.format({
pathname: `${getEnv("MANAGER_API_BASE")}/get_device_version`,
query: {
livecommonversion,
provider,
target_id: targetId,
},
}),
}).catch(error => {
const status = error?.status || error?.response?.status;
if (status === 404)
throw new FirmwareNotRecognized("manager api did not recognize targetId=" + targetId, {
targetId,
});
throw error;
});
return data;
}, (targetId, provider) => `${getEnv("MANAGER_API_BASE")}_${targetId}_${provider}`);
const install = (transport, context, params, unresponsiveExpectedDuringBulk) => {
if (getEnv("MOCK")) {
return createMockSocket(secureChannelMock(true), bulkSocketMock(3000));
}
log("manager", "install " + context, params);
return createDeviceSocket(transport, {
url: URL.format({
pathname: `${getEnv("BASE_SOCKET_URL")}/install`,
query: { ...params, livecommonversion },
}),
unresponsiveExpectedDuringBulk,
}).pipe(remapSocketError(context));
};
const genuineCheck = (transport, { targetId, perso, }) => {
if (getEnv("MOCK")) {
return createMockSocket(secureChannelMock(false), resultMock("0000"));
}
log("manager", "genuineCheck", {
targetId,
perso,
});
return createDeviceSocket(transport, {
url: URL.format({
pathname: `${getEnv("BASE_SOCKET_URL")}/genuine`,
query: {
targetId,
perso,
livecommonversion,
},
}),
}).pipe(map(e => {
if (e.type === "result") {
return {
type: "result",
payload: String(e.payload || ""),
};
}
return e;
}));
};
const listInstalledApps = (transport, { targetId, perso, }) => {
if (getEnv("MOCK")) {
const result = global._listInstalledApps_mock_result;
invariant(result, "using MOCK, global._listInstalledApps_mock_result must be set");
return createMockSocket(secureChannelMock(false), resultMock(result));
}
log("manager", "listInstalledApps", {
targetId,
perso,
});
return createDeviceSocket(transport, {
url: URL.format({
pathname: `${getEnv("BASE_SOCKET_URL")}/apps/list`,
query: {
targetId,
perso,
livecommonversion,
},
}),
}).pipe(remapSocketError("listInstalledApps"), map(o => {
if (o.type === "result") {
return {
type: "result",
payload: [...o.payload].map(a => {
invariant(typeof a === "object" && a, "payload array item are objects");
const { hash, name, hash_code_data } = a;
invariant(typeof hash === "string", "hash is defined");
invariant(typeof name === "string", "name is defined");
return {
hash,
name,
hash_code_data,
};
}),
};
}
return o;
}));
};
const installMcu = (transport, context, { targetId, version, }) => {
if (getEnv("MOCK")) {
return createMockSocket(secureChannelMock(false), bulkSocketMock(5000));
}
log("manager", "installMCU " + context, {
targetId,
version,
});
return createDeviceSocket(transport, {
url: URL.format({
pathname: `${getEnv("BASE_SOCKET_URL")}/mcu`,
query: {
targetId,
version,
livecommonversion,
},
}),
unresponsiveExpectedDuringBulk: true,
}).pipe(remapSocketError(context));
};
async function retrieveMcuVersion({ mcu_versions, }) {
const mcus = await fetchMcusUseCase();
const provider = getProviderId(undefined);
const availableMcus = mcus.filter(({ id, providers, from_bootloader_version }) => providers.includes(provider) &&
from_bootloader_version !== "none" &&
mcu_versions.includes(id));
return findBestMCU(availableMcus);
}
const API = {
/** @deprecated use getAppsCatalogForDevice (from ledger-live-common/src/device/use-cases) instead */
listApps,
listInstalledApps,
listCategories,
getLanguagePackagesForDevice,
getCurrentOSU,
compatibleMCUForDeviceInfo,
findBestMCU,
getCurrentFirmware,
getFinalFirmwareById,
getDeviceVersion,
install,
genuineCheck,
installMcu,
retrieveMcuVersion,
};
export default API;
//# sourceMappingURL=api.js.map