@ledgerhq/live-common 26.0.0 → 26.1.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/__tests__/hw/getDeviceInfo.js +152 -0
- package/lib/__tests__/hw/getDeviceInfo.js.map +1 -1
- package/lib/api/Manager.d.ts +2 -1
- package/lib/api/Manager.d.ts.map +1 -1
- package/lib/api/Manager.js +37 -2
- package/lib/api/Manager.js.map +1 -1
- package/lib/ble/hooks/useBleDevicePairing.d.ts +18 -0
- package/lib/ble/hooks/useBleDevicePairing.d.ts.map +1 -0
- package/lib/ble/hooks/useBleDevicePairing.js +57 -0
- package/lib/ble/hooks/useBleDevicePairing.js.map +1 -0
- package/lib/ble/hooks/useBleDevicePairing.test.d.ts +2 -0
- package/lib/ble/hooks/useBleDevicePairing.test.d.ts.map +1 -0
- package/lib/ble/hooks/useBleDevicePairing.test.js +159 -0
- package/lib/ble/hooks/useBleDevicePairing.test.js.map +1 -0
- package/lib/ble/hooks/useBleDevicesScanning.d.ts +25 -0
- package/lib/ble/hooks/useBleDevicesScanning.d.ts.map +1 -0
- package/lib/ble/hooks/useBleDevicesScanning.js +117 -0
- package/lib/ble/hooks/useBleDevicesScanning.js.map +1 -0
- package/lib/ble/hooks/useBleDevicesScanning.test.d.ts +2 -0
- package/lib/ble/hooks/useBleDevicesScanning.test.d.ts.map +1 -0
- package/lib/ble/hooks/useBleDevicesScanning.test.js +281 -0
- package/lib/ble/hooks/useBleDevicesScanning.test.js.map +1 -0
- package/lib/ble/types.d.ts +21 -0
- package/lib/ble/types.d.ts.map +1 -0
- package/lib/ble/types.js +3 -0
- package/lib/ble/types.js.map +1 -0
- package/lib/errors.d.ts +2 -0
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +4 -2
- package/lib/errors.js.map +1 -1
- package/lib/families/cardano/js-estimateMaxSpendable.d.ts.map +1 -1
- package/lib/families/cardano/js-estimateMaxSpendable.js +2 -0
- package/lib/families/cardano/js-estimateMaxSpendable.js.map +1 -1
- package/lib/families/cosmos/specs.js +2 -2
- package/lib/families/cosmos/specs.js.map +1 -1
- package/lib/families/elrond/specs.js +1 -1
- package/lib/families/elrond/specs.js.map +1 -1
- package/lib/families/polkadot/bridge.integration.test.js +3 -3
- package/lib/families/polkadot/bridge.integration.test.js.map +1 -1
- package/lib/families/polkadot/specs.d.ts.map +1 -1
- package/lib/families/polkadot/specs.js +4 -3
- package/lib/families/polkadot/specs.js.map +1 -1
- package/lib/featureFlags/provider.d.ts +5 -1
- package/lib/featureFlags/provider.d.ts.map +1 -1
- package/lib/featureFlags/provider.js +6 -4
- package/lib/featureFlags/provider.js.map +1 -1
- package/lib/featureFlags/useFeature.d.ts.map +1 -1
- package/lib/featureFlags/useFeature.js +3 -1
- package/lib/featureFlags/useFeature.js.map +1 -1
- package/lib/generated/account.d.ts.map +1 -1
- package/lib/generated/account.js.map +1 -1
- package/lib/generated/bridge/js.d.ts.map +1 -1
- package/lib/generated/bridge/js.js.map +1 -1
- package/lib/generated/bridge/mock.d.ts.map +1 -1
- package/lib/generated/bridge/mock.js.map +1 -1
- package/lib/generated/cli-transaction.d.ts.map +1 -1
- package/lib/generated/cli-transaction.js.map +1 -1
- package/lib/generated/deviceTransactionConfig.d.ts.map +1 -1
- package/lib/generated/deviceTransactionConfig.js.map +1 -1
- package/lib/generated/exchange.d.ts.map +1 -1
- package/lib/generated/exchange.js.map +1 -1
- package/lib/generated/hw-getAddress.d.ts.map +1 -1
- package/lib/generated/hw-getAddress.js.map +1 -1
- package/lib/generated/hw-signMessage.d.ts.map +1 -1
- package/lib/generated/hw-signMessage.js.map +1 -1
- package/lib/generated/mock.d.ts.map +1 -1
- package/lib/generated/mock.js.map +1 -1
- package/lib/generated/platformAdapter.d.ts.map +1 -1
- package/lib/generated/platformAdapter.js.map +1 -1
- package/lib/generated/presync.d.ts.map +1 -1
- package/lib/generated/presync.js.map +1 -1
- package/lib/generated/specs.d.ts.map +1 -1
- package/lib/generated/specs.js.map +1 -1
- package/lib/generated/speculos-deviceActions.d.ts.map +1 -1
- package/lib/generated/speculos-deviceActions.js.map +1 -1
- package/lib/generated/transaction.d.ts.map +1 -1
- package/lib/generated/transaction.js.map +1 -1
- package/lib/generated/types.d.ts.map +1 -1
- package/lib/hw/actions/installLanguage.d.ts +21 -0
- package/lib/hw/actions/installLanguage.d.ts.map +1 -0
- package/lib/hw/actions/installLanguage.js +260 -0
- package/lib/hw/actions/installLanguage.js.map +1 -0
- package/lib/hw/attemptToQuitApp.d.ts +10 -0
- package/lib/hw/attemptToQuitApp.d.ts.map +1 -0
- package/lib/hw/attemptToQuitApp.js +22 -0
- package/lib/hw/attemptToQuitApp.js.map +1 -0
- package/lib/hw/connectManager.d.ts +2 -5
- package/lib/hw/connectManager.d.ts.map +1 -1
- package/lib/hw/connectManager.js +2 -14
- package/lib/hw/connectManager.js.map +1 -1
- package/lib/hw/getDeviceInfo.d.ts.map +1 -1
- package/lib/hw/getDeviceInfo.js +6 -3
- package/lib/hw/getDeviceInfo.js.map +1 -1
- package/lib/hw/getVersion.d.ts +3 -3
- package/lib/hw/getVersion.d.ts.map +1 -1
- package/lib/hw/getVersion.js +53 -5
- package/lib/hw/getVersion.js.map +1 -1
- package/lib/hw/installLanguage.d.ts +17 -0
- package/lib/hw/installLanguage.d.ts.map +1 -0
- package/lib/hw/installLanguage.js +173 -0
- package/lib/hw/installLanguage.js.map +1 -0
- package/lib/hw/uninstallLanguage.d.ts +15 -0
- package/lib/hw/uninstallLanguage.d.ts.map +1 -0
- package/lib/hw/uninstallLanguage.js +121 -0
- package/lib/hw/uninstallLanguage.js.map +1 -0
- package/lib/manager/hooks.d.ts +2 -1
- package/lib/manager/hooks.d.ts.map +1 -1
- package/lib/manager/hooks.js +13 -1
- package/lib/manager/hooks.js.map +1 -1
- package/lib/manager/index.d.ts +2 -1
- package/lib/manager/index.d.ts.map +1 -1
- package/lib/manager/index.js +16 -0
- package/lib/manager/index.js.map +1 -1
- package/lib/manager/localization.d.ts +3 -0
- package/lib/manager/localization.d.ts.map +1 -0
- package/lib/manager/localization.js +15 -0
- package/lib/manager/localization.js.map +1 -0
- package/lib/platform/react.d.ts.map +1 -1
- package/lib/platform/react.js +6 -3
- package/lib/platform/react.js.map +1 -1
- package/package.json +18 -18
- package/src/__tests__/hw/getDeviceInfo.ts +137 -0
- package/src/api/Manager.ts +50 -2
- package/src/ble/hooks/useBleDevicePairing.test.ts +91 -0
- package/src/ble/hooks/useBleDevicePairing.ts +51 -0
- package/src/ble/hooks/useBleDevicesScanning.test.ts +252 -0
- package/src/ble/hooks/useBleDevicesScanning.ts +147 -0
- package/src/ble/types.ts +42 -0
- package/src/errors.ts +9 -0
- package/src/families/cardano/js-estimateMaxSpendable.ts +2 -0
- package/src/families/cosmos/specs.ts +2 -2
- package/src/families/elrond/specs.ts +1 -1
- package/src/families/polkadot/bridge.integration.test.ts +3 -3
- package/src/families/polkadot/specs.ts +4 -3
- package/src/featureFlags/provider.tsx +11 -1
- package/src/featureFlags/useFeature.ts +6 -2
- package/src/generated/account.ts +0 -8
- package/src/generated/bridge/js.ts +0 -18
- package/src/generated/bridge/mock.ts +0 -8
- package/src/generated/cli-transaction.ts +0 -17
- package/src/generated/deviceTransactionConfig.ts +15 -31
- package/src/generated/exchange.ts +0 -5
- package/src/generated/hw-getAddress.ts +0 -18
- package/src/generated/hw-signMessage.ts +0 -3
- package/src/generated/mock.ts +0 -3
- package/src/generated/platformAdapter.ts +0 -4
- package/src/generated/presync.ts +0 -1
- package/src/generated/specs.ts +0 -17
- package/src/generated/speculos-deviceActions.ts +0 -17
- package/src/generated/transaction.ts +0 -18
- package/src/generated/types.ts +7 -4
- package/src/hw/actions/installLanguage.ts +343 -0
- package/src/hw/attemptToQuitApp.ts +32 -0
- package/src/hw/connectManager.ts +5 -26
- package/src/hw/getDeviceInfo.ts +6 -0
- package/src/hw/getVersion.ts +77 -3
- package/src/hw/installLanguage.ts +172 -0
- package/src/hw/uninstallLanguage.ts +127 -0
- package/src/manager/hooks.ts +21 -1
- package/src/manager/index.ts +16 -0
- package/src/manager/localization.ts +19 -0
- package/src/platform/react.ts +7 -3
package/src/hw/getVersion.ts
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
|
+
import { DeviceModelId, identifyTargetId } from "@ledgerhq/devices";
|
|
1
2
|
import Transport from "@ledgerhq/hw-transport";
|
|
2
3
|
import type { FirmwareInfo } from "@ledgerhq/types-live";
|
|
4
|
+
import { satisfies as versionSatisfies } from "semver";
|
|
5
|
+
import { isDeviceLocalizationSupported } from "../manager/localization";
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
const deviceVersionRangesForBootloaderVersion: {
|
|
8
|
+
[key in DeviceModelId]?: string;
|
|
9
|
+
} = {
|
|
10
|
+
nanoS: ">=2.0.0",
|
|
11
|
+
nanoX: ">=2.0.0 || =2.1.0-lo2 || =2.1.0-lo4 || =2.1.0-lo5", // TODO: remove pre-release version
|
|
12
|
+
nanoSP: ">=1.0.0",
|
|
13
|
+
};
|
|
14
|
+
export const isBootloaderVersionSupported = (
|
|
15
|
+
seVersion: string,
|
|
16
|
+
modelId?: DeviceModelId
|
|
17
|
+
) =>
|
|
18
|
+
modelId &&
|
|
19
|
+
deviceVersionRangesForBootloaderVersion[modelId] &&
|
|
20
|
+
versionSatisfies(
|
|
21
|
+
seVersion,
|
|
22
|
+
deviceVersionRangesForBootloaderVersion[modelId] as string
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const deviceVersionRangesForHardwareVersion: {
|
|
26
|
+
[key in DeviceModelId]?: string;
|
|
27
|
+
} = {
|
|
28
|
+
nanoX: ">=2.0.0 || =2.1.0-lo2 || =2.1.0-lo4 || =2.1.0-lo5", // TODO: remove pre-release version
|
|
29
|
+
};
|
|
30
|
+
export const isHardwareVersionSupported = (
|
|
31
|
+
seVersion: string,
|
|
32
|
+
modelId?: DeviceModelId
|
|
33
|
+
) =>
|
|
34
|
+
modelId &&
|
|
35
|
+
deviceVersionRangesForHardwareVersion[modelId] &&
|
|
36
|
+
versionSatisfies(
|
|
37
|
+
seVersion,
|
|
38
|
+
deviceVersionRangesForHardwareVersion[modelId] as string
|
|
39
|
+
);
|
|
7
40
|
|
|
8
41
|
export default async function getVersion(
|
|
9
42
|
transport: Transport
|
|
@@ -35,8 +68,11 @@ export default async function getVersion(
|
|
|
35
68
|
let mcuVersion = "";
|
|
36
69
|
let mcuBlVersion: string | undefined;
|
|
37
70
|
let seVersion: string | undefined;
|
|
71
|
+
let bootloaderVersion: string | undefined;
|
|
72
|
+
let hardwareVersion: number | undefined;
|
|
38
73
|
let mcuTargetId: number | undefined;
|
|
39
74
|
let seTargetId: number | undefined;
|
|
75
|
+
let languageId: number | undefined;
|
|
40
76
|
|
|
41
77
|
const isBootloader = (targetId & 0xf0000000) !== 0x30000000;
|
|
42
78
|
|
|
@@ -77,6 +113,41 @@ export default async function getVersion(
|
|
|
77
113
|
mcuVersionBuf = mcuVersionBuf.slice(0, mcuVersionBuf.length - 1);
|
|
78
114
|
}
|
|
79
115
|
mcuVersion = mcuVersionBuf.toString();
|
|
116
|
+
|
|
117
|
+
const isOSU = rawVersion.includes("-osu");
|
|
118
|
+
|
|
119
|
+
if (!isOSU) {
|
|
120
|
+
const deviceModel = identifyTargetId(targetId);
|
|
121
|
+
|
|
122
|
+
if (isBootloaderVersionSupported(seVersion, deviceModel?.id)) {
|
|
123
|
+
const bootloaderVersionLength = data[i++];
|
|
124
|
+
let bootloaderVersionBuf: Buffer = Buffer.from(
|
|
125
|
+
data.slice(i, i + bootloaderVersionLength)
|
|
126
|
+
);
|
|
127
|
+
i += bootloaderVersionLength;
|
|
128
|
+
|
|
129
|
+
if (bootloaderVersionBuf[bootloaderVersionBuf.length - 1] === 0) {
|
|
130
|
+
bootloaderVersionBuf = bootloaderVersionBuf.slice(
|
|
131
|
+
0,
|
|
132
|
+
bootloaderVersionBuf.length - 1
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
bootloaderVersion = bootloaderVersionBuf.toString();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (isHardwareVersionSupported(seVersion, deviceModel?.id)) {
|
|
139
|
+
const hardwareVersionLength = data[i++];
|
|
140
|
+
hardwareVersion = data
|
|
141
|
+
.slice(i, i + hardwareVersionLength)
|
|
142
|
+
.readUIntBE(0, 1); // ?? string? number?
|
|
143
|
+
i += hardwareVersionLength;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (isDeviceLocalizationSupported(seVersion, deviceModel?.id)) {
|
|
147
|
+
const languageIdLength = data[i++];
|
|
148
|
+
languageId = data.slice(i, i + languageIdLength).readUIntBE(0, 1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
80
151
|
}
|
|
81
152
|
|
|
82
153
|
return {
|
|
@@ -89,5 +160,8 @@ export default async function getVersion(
|
|
|
89
160
|
mcuTargetId,
|
|
90
161
|
seTargetId,
|
|
91
162
|
flags,
|
|
163
|
+
bootloaderVersion,
|
|
164
|
+
hardwareVersion,
|
|
165
|
+
languageId,
|
|
92
166
|
};
|
|
93
167
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Observable, from, of, throwError } from "rxjs";
|
|
2
|
+
import { catchError, concatMap, delay, mergeMap } from "rxjs/operators";
|
|
3
|
+
import {
|
|
4
|
+
DeviceOnDashboardExpected,
|
|
5
|
+
TransportError,
|
|
6
|
+
TransportStatusError,
|
|
7
|
+
LanguageNotFound,
|
|
8
|
+
} from "@ledgerhq/errors";
|
|
9
|
+
|
|
10
|
+
import ManagerAPI from "../api/Manager";
|
|
11
|
+
import { withDevice } from "./deviceAccess";
|
|
12
|
+
import getDeviceInfo from "./getDeviceInfo";
|
|
13
|
+
import { Language, LanguagePackage } from "@ledgerhq/types-live";
|
|
14
|
+
import network from "../network";
|
|
15
|
+
import { LanguageInstallRefusedOnDevice } from "../errors";
|
|
16
|
+
import getAppAndVersion from "./getAppAndVersion";
|
|
17
|
+
import { isDashboardName } from "./isDashboardName";
|
|
18
|
+
import Transport from "@ledgerhq/hw-transport";
|
|
19
|
+
import attemptToQuitApp, { AttemptToQuitAppEvent } from "./attemptToQuitApp";
|
|
20
|
+
|
|
21
|
+
export type InstallLanguageEvent =
|
|
22
|
+
| AttemptToQuitAppEvent
|
|
23
|
+
| {
|
|
24
|
+
type: "progress";
|
|
25
|
+
progress: number;
|
|
26
|
+
}
|
|
27
|
+
| {
|
|
28
|
+
type: "devicePermissionRequested";
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
type: "languageInstalled";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type InstallLanguageRequest = {
|
|
35
|
+
deviceId: string;
|
|
36
|
+
language: Language;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default function installLanguage({
|
|
40
|
+
deviceId,
|
|
41
|
+
language,
|
|
42
|
+
}: InstallLanguageRequest): Observable<InstallLanguageEvent> {
|
|
43
|
+
const sub = withDevice(deviceId)(
|
|
44
|
+
(transport) =>
|
|
45
|
+
new Observable((subscriber) => {
|
|
46
|
+
const timeoutSub = of<InstallLanguageEvent>({
|
|
47
|
+
type: "unresponsiveDevice",
|
|
48
|
+
})
|
|
49
|
+
.pipe(delay(1000))
|
|
50
|
+
.subscribe((e) => subscriber.next(e));
|
|
51
|
+
|
|
52
|
+
const sub = from(getDeviceInfo(transport))
|
|
53
|
+
.pipe(
|
|
54
|
+
mergeMap(async (deviceInfo) => {
|
|
55
|
+
timeoutSub.unsubscribe();
|
|
56
|
+
|
|
57
|
+
if (language === "english") {
|
|
58
|
+
await uninstallAllLanguages(transport);
|
|
59
|
+
|
|
60
|
+
subscriber.next({
|
|
61
|
+
type: "languageInstalled",
|
|
62
|
+
});
|
|
63
|
+
subscriber.complete();
|
|
64
|
+
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const languages = await ManagerAPI.getLanguagePackagesForDevice(
|
|
69
|
+
deviceInfo
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const packs: LanguagePackage[] = languages.filter(
|
|
73
|
+
(l: any) => l.language === language
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!packs.length)
|
|
77
|
+
return subscriber.error(new LanguageNotFound(language));
|
|
78
|
+
|
|
79
|
+
const pack = packs[0];
|
|
80
|
+
const { apdu_install_url } = pack;
|
|
81
|
+
const url = apdu_install_url;
|
|
82
|
+
|
|
83
|
+
const { data: rawApdus } = await network({
|
|
84
|
+
method: "GET",
|
|
85
|
+
url,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const apdus = rawApdus.split(/\r?\n/).filter(Boolean);
|
|
89
|
+
|
|
90
|
+
await uninstallAllLanguages(transport);
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < apdus.length; i++) {
|
|
93
|
+
if (apdus[i].startsWith("e030")) {
|
|
94
|
+
subscriber.next({
|
|
95
|
+
type: "devicePermissionRequested",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const response = await transport.exchange(
|
|
100
|
+
Buffer.from(apdus[i], "hex")
|
|
101
|
+
);
|
|
102
|
+
const status = response.readUInt16BE(response.length - 2);
|
|
103
|
+
const statusStr = status.toString(16);
|
|
104
|
+
|
|
105
|
+
// Some error handling
|
|
106
|
+
if (status === 0x5501) {
|
|
107
|
+
return subscriber.error(
|
|
108
|
+
new LanguageInstallRefusedOnDevice(statusStr)
|
|
109
|
+
);
|
|
110
|
+
} else if (status !== 0x9000) {
|
|
111
|
+
return subscriber.error(
|
|
112
|
+
new TransportError("Unexpected device response", statusStr)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
subscriber.next({
|
|
117
|
+
type: "progress",
|
|
118
|
+
progress: (i + 1) / apdus.length,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
subscriber.next({
|
|
123
|
+
type: "languageInstalled",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
subscriber.complete();
|
|
127
|
+
}),
|
|
128
|
+
catchError((e: Error) => {
|
|
129
|
+
if (
|
|
130
|
+
e instanceof DeviceOnDashboardExpected ||
|
|
131
|
+
(e &&
|
|
132
|
+
e instanceof TransportStatusError &&
|
|
133
|
+
[0x6e00, 0x6d00, 0x6e01, 0x6d01, 0x6d02].includes(
|
|
134
|
+
// @ts-expect-error typescript not checking agains the instanceof
|
|
135
|
+
e.statusCode
|
|
136
|
+
))
|
|
137
|
+
) {
|
|
138
|
+
return from(getAppAndVersion(transport)).pipe(
|
|
139
|
+
concatMap((appAndVersion) => {
|
|
140
|
+
return !isDashboardName(appAndVersion.name)
|
|
141
|
+
? attemptToQuitApp(transport, appAndVersion)
|
|
142
|
+
: of<InstallLanguageEvent>({
|
|
143
|
+
type: "appDetected",
|
|
144
|
+
});
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return throwError(e);
|
|
149
|
+
})
|
|
150
|
+
)
|
|
151
|
+
.subscribe(subscriber);
|
|
152
|
+
|
|
153
|
+
return () => {
|
|
154
|
+
timeoutSub.unsubscribe();
|
|
155
|
+
sub.unsubscribe();
|
|
156
|
+
};
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return sub as Observable<InstallLanguageEvent>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const uninstallAllLanguages = async (transport: Transport) => {
|
|
164
|
+
await transport.send(
|
|
165
|
+
0xe0,
|
|
166
|
+
0x33,
|
|
167
|
+
0xff,
|
|
168
|
+
0x00,
|
|
169
|
+
undefined,
|
|
170
|
+
[0x9000, 0x5501] // Expected responses when uninstalling.
|
|
171
|
+
);
|
|
172
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Observable, from, of, throwError, EMPTY } from "rxjs";
|
|
2
|
+
import { catchError, concatMap, delay, mergeMap } from "rxjs/operators";
|
|
3
|
+
import {
|
|
4
|
+
DeviceOnDashboardExpected,
|
|
5
|
+
TransportStatusError,
|
|
6
|
+
} from "@ledgerhq/errors";
|
|
7
|
+
|
|
8
|
+
import { withDevice } from "./deviceAccess";
|
|
9
|
+
import getDeviceInfo from "./getDeviceInfo";
|
|
10
|
+
import { Language, languageIds } from "@ledgerhq/types-live";
|
|
11
|
+
import { AppAndVersion } from "./connectApp";
|
|
12
|
+
import appSupportsQuitApp from "../appSupportsQuitApp";
|
|
13
|
+
import quitApp from "./quitApp";
|
|
14
|
+
import getAppAndVersion from "./getAppAndVersion";
|
|
15
|
+
import { isDashboardName } from "./isDashboardName";
|
|
16
|
+
|
|
17
|
+
export type UninstallLanguageEvent =
|
|
18
|
+
| {
|
|
19
|
+
type: "appDetected";
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
type: "unresponsiveDevice";
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
type: "languageUninstalled";
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const attemptToQuitApp = (
|
|
29
|
+
transport,
|
|
30
|
+
appAndVersion?: AppAndVersion
|
|
31
|
+
): Observable<UninstallLanguageEvent> =>
|
|
32
|
+
appAndVersion && appSupportsQuitApp(appAndVersion)
|
|
33
|
+
? from(quitApp(transport)).pipe(
|
|
34
|
+
concatMap(() =>
|
|
35
|
+
of(<UninstallLanguageEvent>{
|
|
36
|
+
type: "unresponsiveDevice",
|
|
37
|
+
})
|
|
38
|
+
),
|
|
39
|
+
catchError((e) => throwError(e))
|
|
40
|
+
)
|
|
41
|
+
: of({
|
|
42
|
+
type: "appDetected",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export type UninstallLanguageRequest = {
|
|
46
|
+
deviceId: string;
|
|
47
|
+
language: Language;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default function unistallLanguage({
|
|
51
|
+
deviceId,
|
|
52
|
+
language,
|
|
53
|
+
}: UninstallLanguageRequest): Observable<UninstallLanguageEvent> {
|
|
54
|
+
const sub = withDevice(deviceId)(
|
|
55
|
+
(transport) =>
|
|
56
|
+
new Observable<UninstallLanguageEvent>((subscriber) => {
|
|
57
|
+
const timeoutSub = of<UninstallLanguageEvent>({
|
|
58
|
+
type: "unresponsiveDevice",
|
|
59
|
+
})
|
|
60
|
+
.pipe(delay(1000))
|
|
61
|
+
.subscribe((e) => subscriber.next(e));
|
|
62
|
+
|
|
63
|
+
const sub = from(getDeviceInfo(transport))
|
|
64
|
+
.pipe(
|
|
65
|
+
mergeMap(async (_) => {
|
|
66
|
+
timeoutSub.unsubscribe();
|
|
67
|
+
|
|
68
|
+
const id = languageIds[language];
|
|
69
|
+
|
|
70
|
+
await transport.send(
|
|
71
|
+
0xe0,
|
|
72
|
+
0x33,
|
|
73
|
+
id,
|
|
74
|
+
0x00,
|
|
75
|
+
undefined,
|
|
76
|
+
[0x9000, 0x5501] // Expected responses when uninstalling.
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
subscriber.next({
|
|
80
|
+
type: "languageUninstalled",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
subscriber.complete();
|
|
84
|
+
}),
|
|
85
|
+
catchError((e: Error) => {
|
|
86
|
+
if (
|
|
87
|
+
e instanceof DeviceOnDashboardExpected ||
|
|
88
|
+
(e &&
|
|
89
|
+
e instanceof TransportStatusError &&
|
|
90
|
+
[0x6e00, 0x6d00, 0x6e01, 0x6d01, 0x6d02].includes(
|
|
91
|
+
// @ts-expect-error typescript not checking agains the instanceof
|
|
92
|
+
e.statusCode
|
|
93
|
+
))
|
|
94
|
+
) {
|
|
95
|
+
const quitAppObservable = from(
|
|
96
|
+
getAppAndVersion(transport)
|
|
97
|
+
).pipe(
|
|
98
|
+
concatMap((appAndVersion) => {
|
|
99
|
+
return !isDashboardName(appAndVersion.name)
|
|
100
|
+
? attemptToQuitApp(transport, appAndVersion)
|
|
101
|
+
: of<UninstallLanguageEvent>({
|
|
102
|
+
type: "appDetected",
|
|
103
|
+
});
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
quitAppObservable.subscribe(
|
|
108
|
+
(event) => subscriber.next(event),
|
|
109
|
+
(error) => subscriber.error(error)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
subscriber.error(e);
|
|
114
|
+
return EMPTY;
|
|
115
|
+
})
|
|
116
|
+
)
|
|
117
|
+
.subscribe();
|
|
118
|
+
|
|
119
|
+
return () => {
|
|
120
|
+
timeoutSub.unsubscribe();
|
|
121
|
+
sub.unsubscribe();
|
|
122
|
+
};
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return sub;
|
|
127
|
+
}
|
package/src/manager/hooks.ts
CHANGED
|
@@ -4,7 +4,11 @@ import { useEnv } from "../env.react";
|
|
|
4
4
|
import manager from ".";
|
|
5
5
|
import { getProviderId } from "./provider";
|
|
6
6
|
import ManagerAPI from "../api/Manager";
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
DeviceModelInfo,
|
|
9
|
+
DeviceInfo,
|
|
10
|
+
Language,
|
|
11
|
+
} from "@ledgerhq/types-live";
|
|
8
12
|
|
|
9
13
|
async function hasOudatedApps({
|
|
10
14
|
deviceInfo,
|
|
@@ -71,3 +75,19 @@ export function useManagerBlueDot(
|
|
|
71
75
|
}, [dmi, forceProvider]);
|
|
72
76
|
return display;
|
|
73
77
|
}
|
|
78
|
+
|
|
79
|
+
export const useAvailableLanguagesForDevice = (
|
|
80
|
+
deviceInfo: DeviceInfo
|
|
81
|
+
): Language[] => {
|
|
82
|
+
const [availableLanguages, setAvailableLanguages] = useState<Language[]>([]);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (deviceInfo) {
|
|
86
|
+
manager
|
|
87
|
+
.getAvailableLanguagesDevice(deviceInfo)
|
|
88
|
+
.then(setAvailableLanguages);
|
|
89
|
+
}
|
|
90
|
+
}, [deviceInfo, setAvailableLanguages]);
|
|
91
|
+
|
|
92
|
+
return availableLanguages;
|
|
93
|
+
};
|
package/src/manager/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { listCryptoCurrencies } from "../currencies";
|
|
|
8
8
|
import ManagerAPI from "../api/Manager";
|
|
9
9
|
import { getProviderId } from "./provider";
|
|
10
10
|
import type {
|
|
11
|
+
Language,
|
|
11
12
|
ApplicationVersion,
|
|
12
13
|
DeviceInfo,
|
|
13
14
|
FirmwareUpdateContext,
|
|
@@ -158,6 +159,21 @@ const CacheAPI = {
|
|
|
158
159
|
shouldFlashMCU,
|
|
159
160
|
};
|
|
160
161
|
},
|
|
162
|
+
// get list of available languages for a given deviceInfo
|
|
163
|
+
getAvailableLanguagesDevice: async (
|
|
164
|
+
deviceInfo: DeviceInfo
|
|
165
|
+
): Promise<Language[]> => {
|
|
166
|
+
const languagePackages = await ManagerAPI.getLanguagePackagesForDevice(
|
|
167
|
+
deviceInfo
|
|
168
|
+
);
|
|
169
|
+
const languages = languagePackages.map((pack) => pack.language);
|
|
170
|
+
|
|
171
|
+
if (!languages.includes("english")) {
|
|
172
|
+
languages.push("english"); // english is always available
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return languages;
|
|
176
|
+
},
|
|
161
177
|
// get list of apps for a given deviceInfo
|
|
162
178
|
getAppsList: async (
|
|
163
179
|
deviceInfo: DeviceInfo,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DeviceModelId } from "@ledgerhq/devices";
|
|
2
|
+
import { satisfies as versionSatisfies } from "semver";
|
|
3
|
+
|
|
4
|
+
const deviceVersionRangesForLocalization: { [key in DeviceModelId]?: string } =
|
|
5
|
+
{
|
|
6
|
+
nanoX: ">=2.1.0 || =2.1.0-lo2 || =2.1.0-lo4 || =2.1.0-lo5",
|
|
7
|
+
nanoSP: ">=1.1.0 || =1.1.0-lo1",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const isDeviceLocalizationSupported = (
|
|
11
|
+
seVersion: string,
|
|
12
|
+
modelId?: DeviceModelId
|
|
13
|
+
) =>
|
|
14
|
+
modelId &&
|
|
15
|
+
deviceVersionRangesForLocalization[modelId] &&
|
|
16
|
+
versionSatisfies(
|
|
17
|
+
seVersion,
|
|
18
|
+
deviceVersionRangesForLocalization[modelId] as string
|
|
19
|
+
);
|
package/src/platform/react.ts
CHANGED
|
@@ -30,14 +30,18 @@ export function usePlatformUrl(
|
|
|
30
30
|
|
|
31
31
|
if (inputs) {
|
|
32
32
|
for (const key in inputs) {
|
|
33
|
-
if (
|
|
33
|
+
if (
|
|
34
|
+
Object.prototype.hasOwnProperty.call(inputs, key) &&
|
|
35
|
+
inputs[key] !== undefined
|
|
36
|
+
) {
|
|
34
37
|
url.searchParams.set(key, inputs[key]);
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
if (params.background)
|
|
43
|
+
url.searchParams.set("backgroundColor", params.background);
|
|
44
|
+
if (params.text) url.searchParams.set("textColor", params.text);
|
|
41
45
|
if (params.loadDate) {
|
|
42
46
|
url.searchParams.set("loadDate", params.loadDate.valueOf().toString());
|
|
43
47
|
}
|