@ledgerhq/live-common 34.55.0-nightly.20251213023821 → 34.55.0-nightly.20251215120904
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/lib/hw/installApp.d.ts.map +1 -1
- package/lib/hw/installApp.js +4 -2
- package/lib/hw/installApp.js.map +1 -1
- package/lib-es/hw/installApp.d.ts.map +1 -1
- package/lib-es/hw/installApp.js +5 -3
- package/lib-es/hw/installApp.js.map +1 -1
- package/package.json +73 -73
- package/src/families/cosmos/datasets/__snapshots__/babylon.integration.test.ts.snap +2 -2
- package/src/families/cosmos/datasets/__snapshots__/cosmos.integration.test.ts.snap +667 -10
- package/src/families/cosmos/datasets/__snapshots__/cryptoOrg.integration.test.ts.snap +2 -2
- package/src/families/cosmos/datasets/__snapshots__/mantra.integration.test.ts.snap +2 -2
- package/src/families/cosmos/datasets/__snapshots__/osmosis.integration.test.ts.snap +2 -2
- package/src/families/cosmos/datasets/__snapshots__/persistence.integration.test.ts.snap +4 -4
- package/src/hw/installApp.test.ts +82 -1
- package/src/hw/installApp.ts +58 -52
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
exports[`crypto_org currency bridge scanAccounts crypto_org seed 1 1`] = `
|
|
4
4
|
[
|
|
5
5
|
{
|
|
6
|
-
"balance": "
|
|
6
|
+
"balance": "10000010",
|
|
7
7
|
"cosmosResources": {
|
|
8
8
|
"delegatedBalance": "0",
|
|
9
9
|
"delegations": [],
|
|
@@ -23,7 +23,7 @@ exports[`crypto_org currency bridge scanAccounts crypto_org seed 1 1`] = `
|
|
|
23
23
|
"operationsCount": 0,
|
|
24
24
|
"pendingOperations": [],
|
|
25
25
|
"seedIdentifier": "0346289788c2f518a158232e7028e8e86b30cff0670326d3a2507183cb0d81cecb",
|
|
26
|
-
"spendableBalance": "
|
|
26
|
+
"spendableBalance": "10000010",
|
|
27
27
|
"swapHistory": [],
|
|
28
28
|
"syncHash": undefined,
|
|
29
29
|
"used": true,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
exports[`mantra currency bridge scanAccounts mantra seed 1 1`] = `
|
|
4
4
|
[
|
|
5
5
|
{
|
|
6
|
-
"balance": "
|
|
6
|
+
"balance": "714930",
|
|
7
7
|
"currencyId": "mantra",
|
|
8
8
|
"derivationMode": "",
|
|
9
9
|
"freshAddress": "mantra1gyauvl44q2apn3u3aujm36q8zrj74vry7n5nvn",
|
|
@@ -12,7 +12,7 @@ exports[`mantra currency bridge scanAccounts mantra seed 1 1`] = `
|
|
|
12
12
|
"index": 0,
|
|
13
13
|
"pendingOperations": [],
|
|
14
14
|
"seedIdentifier": "03d5e0ebb3f1ae2afe87e5d5a24b5029a59cc12f8fd1056840091b2f0b97e54e83",
|
|
15
|
-
"spendableBalance": "
|
|
15
|
+
"spendableBalance": "204930",
|
|
16
16
|
"swapHistory": [],
|
|
17
17
|
"syncHash": undefined,
|
|
18
18
|
"used": true,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
exports[`osmo currency bridge scanAccounts osmo seed 1 1`] = `
|
|
4
4
|
[
|
|
5
5
|
{
|
|
6
|
-
"balance": "
|
|
6
|
+
"balance": "124348",
|
|
7
7
|
"currencyId": "osmo",
|
|
8
8
|
"derivationMode": "",
|
|
9
9
|
"freshAddress": "osmo17gmcxyc5ccd5kwqqatpgfdgh380w2hc77zm0zw",
|
|
@@ -12,7 +12,7 @@ exports[`osmo currency bridge scanAccounts osmo seed 1 1`] = `
|
|
|
12
12
|
"index": 0,
|
|
13
13
|
"pendingOperations": [],
|
|
14
14
|
"seedIdentifier": "02dc75cfe8137450ae2259dc7f29fd8767951956cc943adb4387e91c37a058a9f0",
|
|
15
|
-
"spendableBalance": "
|
|
15
|
+
"spendableBalance": "124348",
|
|
16
16
|
"swapHistory": [],
|
|
17
17
|
"syncHash": undefined,
|
|
18
18
|
"used": true,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
exports[`persistence currency bridge scanAccounts persistence seed 1 1`] = `
|
|
4
4
|
[
|
|
5
5
|
{
|
|
6
|
-
"balance": "
|
|
6
|
+
"balance": "2636577",
|
|
7
7
|
"currencyId": "persistence",
|
|
8
8
|
"derivationMode": "",
|
|
9
9
|
"freshAddress": "persistence1gyauvl44q2apn3u3aujm36q8zrj74vrym5cypd",
|
|
@@ -12,14 +12,14 @@ exports[`persistence currency bridge scanAccounts persistence seed 1 1`] = `
|
|
|
12
12
|
"index": 0,
|
|
13
13
|
"pendingOperations": [],
|
|
14
14
|
"seedIdentifier": "03d5e0ebb3f1ae2afe87e5d5a24b5029a59cc12f8fd1056840091b2f0b97e54e83",
|
|
15
|
-
"spendableBalance": "
|
|
15
|
+
"spendableBalance": "1202573",
|
|
16
16
|
"swapHistory": [],
|
|
17
17
|
"syncHash": undefined,
|
|
18
18
|
"used": true,
|
|
19
19
|
"xpub": "persistence1gyauvl44q2apn3u3aujm36q8zrj74vrym5cypd",
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
"balance": "
|
|
22
|
+
"balance": "230008",
|
|
23
23
|
"currencyId": "persistence",
|
|
24
24
|
"derivationMode": "",
|
|
25
25
|
"freshAddress": "persistence1v2mp0m7k96dm9qv60fkspcqlzpkzrwnevf47z2",
|
|
@@ -28,7 +28,7 @@ exports[`persistence currency bridge scanAccounts persistence seed 1 1`] = `
|
|
|
28
28
|
"index": 1,
|
|
29
29
|
"pendingOperations": [],
|
|
30
30
|
"seedIdentifier": "03d5e0ebb3f1ae2afe87e5d5a24b5029a59cc12f8fd1056840091b2f0b97e54e83",
|
|
31
|
-
"spendableBalance": "
|
|
31
|
+
"spendableBalance": "230008",
|
|
32
32
|
"swapHistory": [],
|
|
33
33
|
"syncHash": undefined,
|
|
34
34
|
"used": true,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TestScheduler } from "rxjs/testing";
|
|
2
2
|
import installApp from "./installApp";
|
|
3
3
|
import ManagerAPI from "../manager/api";
|
|
4
|
-
import { defer } from "rxjs";
|
|
4
|
+
import { defer, of, throwError } from "rxjs";
|
|
5
5
|
import { anAppBuilder } from "../mock/fixtures/anApp";
|
|
6
6
|
import { aTransportBuilder } from "@ledgerhq/hw-transport-mocker";
|
|
7
7
|
import {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
ManagerDeviceLockedError,
|
|
10
10
|
UnresponsiveDeviceError,
|
|
11
11
|
} from "@ledgerhq/errors";
|
|
12
|
+
import { quitApp } from "../deviceSDK/commands/quitApp";
|
|
12
13
|
|
|
13
14
|
// Mocking ManagerAPI
|
|
14
15
|
jest.mock("../manager/api", () => {
|
|
@@ -19,7 +20,13 @@ jest.mock("../manager/api", () => {
|
|
|
19
20
|
install: jest.fn(),
|
|
20
21
|
};
|
|
21
22
|
});
|
|
23
|
+
|
|
24
|
+
jest.mock("../deviceSDK/commands/quitApp", () => ({
|
|
25
|
+
quitApp: jest.fn(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
22
28
|
const mockManagerApiInstall = jest.mocked(ManagerAPI.install);
|
|
29
|
+
const mockQuitApp = jest.mocked(quitApp);
|
|
23
30
|
|
|
24
31
|
describe("installApp", () => {
|
|
25
32
|
let testScheduler: TestScheduler;
|
|
@@ -29,6 +36,11 @@ describe("installApp", () => {
|
|
|
29
36
|
// Makes TestScheduler assertions works with our test framework Jest
|
|
30
37
|
expect(actual).toEqual(expected);
|
|
31
38
|
});
|
|
39
|
+
|
|
40
|
+
mockManagerApiInstall.mockReset();
|
|
41
|
+
mockQuitApp.mockReset();
|
|
42
|
+
|
|
43
|
+
mockQuitApp.mockReturnValue(of<void>(undefined));
|
|
32
44
|
});
|
|
33
45
|
|
|
34
46
|
test("On one error followed by a successfully emitted event, it should retry after the given delay", () => {
|
|
@@ -149,4 +161,73 @@ describe("installApp", () => {
|
|
|
149
161
|
});
|
|
150
162
|
}
|
|
151
163
|
});
|
|
164
|
+
|
|
165
|
+
test("it should run quit app at the beginning then keep going with manager", () => {
|
|
166
|
+
const transport = aTransportBuilder();
|
|
167
|
+
const app = anAppBuilder();
|
|
168
|
+
|
|
169
|
+
const fakeEvent = {
|
|
170
|
+
type: "bulk-progress",
|
|
171
|
+
progress: 0.5,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
mockManagerApiInstall.mockReturnValue(of(fakeEvent));
|
|
175
|
+
|
|
176
|
+
const results: Array<{ progress: number }> = [];
|
|
177
|
+
|
|
178
|
+
installApp(transport, "target-test", app).subscribe({
|
|
179
|
+
next: v => results.push(v),
|
|
180
|
+
error: e => {
|
|
181
|
+
throw e;
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// quitApp called once, with the same transport
|
|
186
|
+
expect(mockQuitApp).toHaveBeenCalledTimes(1);
|
|
187
|
+
expect(mockQuitApp).toHaveBeenCalledWith(transport);
|
|
188
|
+
|
|
189
|
+
// ManagerAPI.install called once, with correct params
|
|
190
|
+
expect(mockManagerApiInstall).toHaveBeenCalledTimes(1);
|
|
191
|
+
expect(mockManagerApiInstall).toHaveBeenCalledWith(
|
|
192
|
+
transport,
|
|
193
|
+
"install-app",
|
|
194
|
+
expect.objectContaining({
|
|
195
|
+
targetId: "target-test",
|
|
196
|
+
perso: app.perso,
|
|
197
|
+
deleteKey: app.delete_key,
|
|
198
|
+
firmware: app.firmware,
|
|
199
|
+
firmwareKey: app.firmware_key,
|
|
200
|
+
hash: app.hash,
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// quitApp must be invoked before ManagerAPI.install
|
|
205
|
+
expect(mockQuitApp.mock.invocationCallOrder[0]).toBeLessThan(
|
|
206
|
+
mockManagerApiInstall.mock.invocationCallOrder[0],
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(results).toEqual([{ progress: 0.5 }]);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("it should return error if quit app fail", done => {
|
|
213
|
+
const transport = aTransportBuilder();
|
|
214
|
+
const app = anAppBuilder();
|
|
215
|
+
const error = new Error("quit failed");
|
|
216
|
+
|
|
217
|
+
mockQuitApp.mockReturnValueOnce(throwError(() => error));
|
|
218
|
+
|
|
219
|
+
const observable = installApp(transport, "target-test", app);
|
|
220
|
+
|
|
221
|
+
observable.subscribe({
|
|
222
|
+
next: () => {
|
|
223
|
+
done(new Error("Expected an error, but got a next notification"));
|
|
224
|
+
},
|
|
225
|
+
error: err => {
|
|
226
|
+
expect(err).toBe(error);
|
|
227
|
+
// ManagerAPI.install should never be called if quitApp fails
|
|
228
|
+
expect(mockManagerApiInstall).not.toHaveBeenCalled();
|
|
229
|
+
done();
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
});
|
|
152
233
|
});
|
package/src/hw/installApp.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Observable, throwError, timer } from "rxjs";
|
|
2
|
-
import { throttleTime, filter, map, catchError, retry } from "rxjs/operators";
|
|
2
|
+
import { throttleTime, filter, map, catchError, retry, switchMap } from "rxjs/operators";
|
|
3
3
|
import {
|
|
4
4
|
LockedDeviceError,
|
|
5
5
|
ManagerAppDepInstallRequired,
|
|
@@ -12,6 +12,7 @@ import ManagerAPI from "../manager/api";
|
|
|
12
12
|
import { getDependencies } from "../apps/polyfill";
|
|
13
13
|
import { LocalTracer } from "@ledgerhq/logs";
|
|
14
14
|
import { LOG_TYPE } from ".";
|
|
15
|
+
import { quitApp } from "../deviceSDK/commands/quitApp";
|
|
15
16
|
|
|
16
17
|
const APP_INSTALL_RETRY_DELAY = 500;
|
|
17
18
|
const APP_INSTALL_RETRY_LIMIT = 5;
|
|
@@ -53,60 +54,65 @@ export default function installApp(
|
|
|
53
54
|
retryDelayMs,
|
|
54
55
|
});
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
error
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
57
|
+
// Run quitApp just before ManagerAPI.install
|
|
58
|
+
return quitApp(transport).pipe(
|
|
59
|
+
switchMap(() =>
|
|
60
|
+
ManagerAPI.install(transport, "install-app", {
|
|
61
|
+
targetId,
|
|
62
|
+
perso: app.perso,
|
|
63
|
+
deleteKey: app.delete_key,
|
|
64
|
+
firmware: app.firmware,
|
|
65
|
+
firmwareKey: app.firmware_key,
|
|
66
|
+
hash: app.hash,
|
|
67
|
+
}).pipe(
|
|
68
|
+
retry({
|
|
69
|
+
count: retryLimit,
|
|
70
|
+
delay: (error: unknown, retryCount: number) => {
|
|
71
|
+
// Not retrying on locked device errors
|
|
72
|
+
if (
|
|
73
|
+
error instanceof LockedDeviceError ||
|
|
74
|
+
error instanceof ManagerDeviceLockedError ||
|
|
75
|
+
error instanceof UnresponsiveDeviceError
|
|
76
|
+
) {
|
|
77
|
+
tracer.trace(`Not retrying on error: ${error}`, {
|
|
78
|
+
error,
|
|
79
|
+
});
|
|
80
|
+
return throwError(() => error);
|
|
81
|
+
}
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
tracer.trace(`Retrying (${retryCount}/${retryLimit}) on error: ${error}`, {
|
|
84
|
+
error,
|
|
85
|
+
retryLimit,
|
|
86
|
+
retryDelayMs,
|
|
87
|
+
});
|
|
88
|
+
return timer(retryDelayMs);
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
filter((e: any) => e.type === "bulk-progress"), // only bulk progress interests the UI
|
|
92
|
+
throttleTime(100), // throttle to only emit 10 event/s max, to not spam the UI
|
|
93
|
+
map((e: any) => ({
|
|
94
|
+
progress: e.progress,
|
|
95
|
+
})), // extract a stream of progress percentage
|
|
96
|
+
catchError((e: Error) => {
|
|
97
|
+
tracer.trace(`Error: ${e}`, { error: e });
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
if (!e || !e.message) return throwError(() => e);
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
const status = e.message.slice(e.message.length - 4);
|
|
102
|
+
if (status === "6a83" || status === "6811") {
|
|
103
|
+
const dependencies = getDependencies(app.name);
|
|
104
|
+
return throwError(
|
|
105
|
+
() =>
|
|
106
|
+
new ManagerAppDepInstallRequired("", {
|
|
107
|
+
appName: app.name,
|
|
108
|
+
dependency: dependencies.join(", "),
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
108
112
|
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
return throwError(() => e);
|
|
114
|
+
}),
|
|
115
|
+
),
|
|
116
|
+
),
|
|
111
117
|
);
|
|
112
118
|
}
|