@ledgerhq/live-common 34.35.0-nightly.4 → 34.35.0-nightly.6
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/e2e/enum/Account.d.ts +64 -58
- package/lib/e2e/enum/Account.d.ts.map +1 -1
- package/lib/e2e/enum/Account.js +80 -59
- package/lib/e2e/enum/Account.js.map +1 -1
- package/lib/e2e/models/BuySell.d.ts +11 -0
- package/lib/e2e/models/BuySell.d.ts.map +1 -0
- package/lib/e2e/models/BuySell.js +3 -0
- package/lib/e2e/models/BuySell.js.map +1 -0
- package/lib/e2e/models/Transaction.d.ts +5 -5
- package/lib/e2e/models/Transaction.d.ts.map +1 -1
- package/lib/e2e/models/Transaction.js.map +1 -1
- package/lib/e2e/speculos.d.ts +4 -1
- package/lib/e2e/speculos.d.ts.map +1 -1
- package/lib/e2e/speculos.js +24 -8
- package/lib/e2e/speculos.js.map +1 -1
- package/lib/e2e/speculosCI.d.ts +5 -0
- package/lib/e2e/speculosCI.d.ts.map +1 -0
- package/lib/e2e/speculosCI.js +129 -0
- package/lib/e2e/speculosCI.js.map +1 -0
- package/lib/hw/connectApp.js +1 -1
- package/lib/hw/connectApp.js.map +1 -1
- package/lib-es/e2e/enum/Account.d.ts +64 -58
- package/lib-es/e2e/enum/Account.d.ts.map +1 -1
- package/lib-es/e2e/enum/Account.js +76 -58
- package/lib-es/e2e/enum/Account.js.map +1 -1
- package/lib-es/e2e/models/BuySell.d.ts +11 -0
- package/lib-es/e2e/models/BuySell.d.ts.map +1 -0
- package/lib-es/e2e/models/BuySell.js +2 -0
- package/lib-es/e2e/models/BuySell.js.map +1 -0
- package/lib-es/e2e/models/Transaction.d.ts +5 -5
- package/lib-es/e2e/models/Transaction.d.ts.map +1 -1
- package/lib-es/e2e/models/Transaction.js.map +1 -1
- package/lib-es/e2e/speculos.d.ts +4 -1
- package/lib-es/e2e/speculos.d.ts.map +1 -1
- package/lib-es/e2e/speculos.js +24 -8
- package/lib-es/e2e/speculos.js.map +1 -1
- package/lib-es/e2e/speculosCI.d.ts +5 -0
- package/lib-es/e2e/speculosCI.d.ts.map +1 -0
- package/lib-es/e2e/speculosCI.js +121 -0
- package/lib-es/e2e/speculosCI.js.map +1 -0
- package/lib-es/hw/connectApp.js +1 -1
- package/lib-es/hw/connectApp.js.map +1 -1
- package/package.json +43 -43
- package/src/e2e/enum/Account.ts +358 -357
- package/src/e2e/models/BuySell.ts +12 -0
- package/src/e2e/models/Transaction.ts +5 -5
- package/src/e2e/speculos.ts +29 -10
- package/src/e2e/speculosCI.ts +161 -0
- package/src/hw/connectApp.ts +1 -1
@@ -1,13 +1,13 @@
|
|
1
1
|
import { Fee } from "../enum/Fee";
|
2
|
-
import {
|
2
|
+
import { AccountType } from "../enum/Account";
|
3
3
|
import { Nft } from "../enum/Nft";
|
4
4
|
|
5
5
|
export type TransactionType = Transaction;
|
6
6
|
|
7
7
|
export class Transaction {
|
8
8
|
constructor(
|
9
|
-
public accountToDebit:
|
10
|
-
public accountToCredit:
|
9
|
+
public accountToDebit: AccountType,
|
10
|
+
public accountToCredit: AccountType,
|
11
11
|
public amount: string,
|
12
12
|
public speed?: Fee,
|
13
13
|
public memoTag?: string,
|
@@ -16,8 +16,8 @@ export class Transaction {
|
|
16
16
|
|
17
17
|
export class NFTTransaction extends Transaction {
|
18
18
|
constructor(
|
19
|
-
accountToDebit:
|
20
|
-
accountToCredit:
|
19
|
+
accountToDebit: AccountType,
|
20
|
+
accountToCredit: AccountType,
|
21
21
|
public nft: Nft,
|
22
22
|
speed?: Fee,
|
23
23
|
memoTag?: string,
|
package/src/e2e/speculos.ts
CHANGED
@@ -7,7 +7,7 @@ import {
|
|
7
7
|
findLatestAppCandidate,
|
8
8
|
SpeculosTransport,
|
9
9
|
} from "../load/speculos";
|
10
|
-
import {
|
10
|
+
import { createSpeculosDeviceCI, releaseSpeculosDeviceCI } from "./speculosCI";
|
11
11
|
import type { AppCandidate } from "@ledgerhq/coin-framework/bot/types";
|
12
12
|
import { DeviceModelId } from "@ledgerhq/devices";
|
13
13
|
import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
|
@@ -38,6 +38,8 @@ import { NFTTransaction, Transaction } from "./models/Transaction";
|
|
38
38
|
import { Delegate } from "./models/Delegate";
|
39
39
|
import { Swap } from "./models/Swap";
|
40
40
|
|
41
|
+
const isSpeculosRemote = process.env.REMOTE_SPECULOS === "true";
|
42
|
+
|
41
43
|
export type Spec = {
|
42
44
|
currency?: CryptoCurrency;
|
43
45
|
appQuery: {
|
@@ -51,6 +53,10 @@ export type Spec = {
|
|
51
53
|
};
|
52
54
|
|
53
55
|
export type Dependency = { name: string; appVersion?: string };
|
56
|
+
export type SpeculosDevice = {
|
57
|
+
id: string;
|
58
|
+
port: number;
|
59
|
+
};
|
54
60
|
|
55
61
|
export function setExchangeDependencies(dependencies: Dependency[]) {
|
56
62
|
const map = new Map<string, Dependency>();
|
@@ -376,9 +382,14 @@ export async function startSpeculos(
|
|
376
382
|
coinapps,
|
377
383
|
onSpeculosDeviceCreated,
|
378
384
|
};
|
379
|
-
|
380
385
|
try {
|
381
|
-
|
386
|
+
const device = isSpeculosRemote
|
387
|
+
? await createSpeculosDeviceCI(deviceParams)
|
388
|
+
: await createSpeculosDevice(deviceParams).then(device => {
|
389
|
+
invariant(device.ports.apiPort, "[E2E] Speculos apiPort is not defined");
|
390
|
+
return { id: device.id, port: device.ports.apiPort };
|
391
|
+
});
|
392
|
+
return device;
|
382
393
|
} catch (e: unknown) {
|
383
394
|
console.error(e);
|
384
395
|
log("engine", `test ${testName} failed with ${String(e)}`);
|
@@ -388,7 +399,9 @@ export async function startSpeculos(
|
|
388
399
|
export async function stopSpeculos(deviceId: string | undefined) {
|
389
400
|
if (deviceId) {
|
390
401
|
log("engine", `test ${deviceId} finished`);
|
391
|
-
|
402
|
+
isSpeculosRemote
|
403
|
+
? await releaseSpeculosDeviceCI(deviceId)
|
404
|
+
: await releaseSpeculosDevice(deviceId);
|
392
405
|
}
|
393
406
|
}
|
394
407
|
|
@@ -402,11 +415,12 @@ interface ResponseData {
|
|
402
415
|
|
403
416
|
export async function waitFor(text: string, maxAttempts: number = 10): Promise<string[]> {
|
404
417
|
const speculosApiPort = getEnv("SPECULOS_API_PORT");
|
418
|
+
const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
|
405
419
|
let attempts = 0;
|
406
420
|
let textFound: boolean = false;
|
407
421
|
while (attempts < maxAttempts && !textFound) {
|
408
422
|
const response = await axios.get<ResponseData>(
|
409
|
-
|
423
|
+
`${speculosAddress}:${speculosApiPort}/events?stream=false¤tscreenonly=true`,
|
410
424
|
);
|
411
425
|
const responseData = response.data;
|
412
426
|
const texts = responseData.events.map(event => event.text);
|
@@ -423,7 +437,8 @@ export async function waitFor(text: string, maxAttempts: number = 10): Promise<s
|
|
423
437
|
|
424
438
|
export async function pressBoth() {
|
425
439
|
const speculosApiPort = getEnv("SPECULOS_API_PORT");
|
426
|
-
|
440
|
+
const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
|
441
|
+
await axios.post(`${speculosAddress}:${speculosApiPort}/button/both`, {
|
427
442
|
action: "press-and-release",
|
428
443
|
});
|
429
444
|
}
|
@@ -451,22 +466,25 @@ export async function pressUntilTextFound(
|
|
451
466
|
}
|
452
467
|
|
453
468
|
async function fetchCurrentScreenTexts(speculosApiPort: number): Promise<string> {
|
469
|
+
const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
|
454
470
|
const response = await axios.get<ResponseData>(
|
455
|
-
|
471
|
+
`${speculosAddress}:${speculosApiPort}/events?stream=false¤tscreenonly=true`,
|
456
472
|
);
|
457
473
|
return response.data.events.map(event => event.text).join("");
|
458
474
|
}
|
459
475
|
|
460
476
|
async function fetchAllEvents(speculosApiPort: number): Promise<string[]> {
|
477
|
+
const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
|
461
478
|
const response = await axios.get<ResponseData>(
|
462
|
-
|
479
|
+
`${speculosAddress}:${speculosApiPort}/events?stream=false¤tscreenonly=false`,
|
463
480
|
);
|
464
481
|
return response.data.events.map(event => event.text);
|
465
482
|
}
|
466
483
|
|
467
484
|
export async function pressRightButton(): Promise<void> {
|
468
485
|
const speculosApiPort = getEnv("SPECULOS_API_PORT");
|
469
|
-
|
486
|
+
const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
|
487
|
+
await axios.post(`${speculosAddress}:${speculosApiPort}/button/right`, {
|
470
488
|
action: "press-and-release",
|
471
489
|
});
|
472
490
|
}
|
@@ -486,9 +504,10 @@ export function containsSubstringInEvent(targetString: string, events: string[])
|
|
486
504
|
}
|
487
505
|
|
488
506
|
export async function takeScreenshot(port?: number): Promise<Buffer | undefined> {
|
507
|
+
const speculosAddress = process.env.SPECULOS_ADDRESS || "http://127.0.0.1";
|
489
508
|
const speculosApiPort = port ?? getEnv("SPECULOS_API_PORT");
|
490
509
|
try {
|
491
|
-
const response = await axios.get(
|
510
|
+
const response = await axios.get(`${speculosAddress}:${speculosApiPort}/screenshot`, {
|
492
511
|
responseType: "arraybuffer",
|
493
512
|
});
|
494
513
|
return response.data;
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import axios from "axios";
|
2
|
+
import {
|
3
|
+
conventionalAppSubpath,
|
4
|
+
DeviceParams,
|
5
|
+
reverseModelMap,
|
6
|
+
} from "@ledgerhq/speculos-transport";
|
7
|
+
import { SpeculosDevice } from "./speculos";
|
8
|
+
import https from "https";
|
9
|
+
|
10
|
+
const { SEED, GITHUB_TOKEN, AWS_ROLE, CLUSTER } = process.env;
|
11
|
+
const GIT_API_URL = "https://api.github.com/repos/LedgerHQ/actions/actions/";
|
12
|
+
const START_WORKFLOW_ID = "workflows/161487603/dispatches";
|
13
|
+
const STOP_WORKFLOW_ID = "workflows/161487604/dispatches";
|
14
|
+
const GITHUB_REF = "main";
|
15
|
+
const getSpeculosAddress = (runId: string) => `https://${runId}.speculos.aws.stg.ldg-tech.com`;
|
16
|
+
const speculosPort = 443;
|
17
|
+
|
18
|
+
function uniqueId(): string {
|
19
|
+
const timestamp = Date.now().toString(36);
|
20
|
+
const randomString = Math.random().toString(36).slice(2, 7);
|
21
|
+
return timestamp + randomString;
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Helper function to make API requests with error handling
|
26
|
+
*/
|
27
|
+
async function githubApiRequest<T = unknown>({
|
28
|
+
method = "POST",
|
29
|
+
urlSuffix,
|
30
|
+
data,
|
31
|
+
params,
|
32
|
+
}: {
|
33
|
+
method?: "GET" | "POST";
|
34
|
+
urlSuffix: string;
|
35
|
+
data?: Record<string, unknown>;
|
36
|
+
params?: Record<string, unknown>;
|
37
|
+
}): Promise<T> {
|
38
|
+
const url = `${GIT_API_URL}${urlSuffix}`;
|
39
|
+
try {
|
40
|
+
const response = await axios({
|
41
|
+
method,
|
42
|
+
url,
|
43
|
+
headers: {
|
44
|
+
Authorization: `Bearer ${GITHUB_TOKEN}`,
|
45
|
+
Accept: "application/vnd.github+json",
|
46
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
47
|
+
},
|
48
|
+
data,
|
49
|
+
params,
|
50
|
+
});
|
51
|
+
return response.data;
|
52
|
+
} catch (error) {
|
53
|
+
console.warn(
|
54
|
+
`API Request failed: ${method} ${url}`,
|
55
|
+
axios.isAxiosError(error) ? error.response?.data : (error as Error).message,
|
56
|
+
);
|
57
|
+
throw error;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
function waitForSpeculosReady(url: string, { interval = 2000, timeout = 120_000 } = {}) {
|
62
|
+
return new Promise((resolve, reject) => {
|
63
|
+
const startTime = Date.now();
|
64
|
+
|
65
|
+
function check() {
|
66
|
+
https
|
67
|
+
.get(url, res => {
|
68
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 400) {
|
69
|
+
process.env.SPECULOS_ADDRESS = url;
|
70
|
+
resolve(true);
|
71
|
+
} else {
|
72
|
+
retry();
|
73
|
+
}
|
74
|
+
})
|
75
|
+
.on("error", retry);
|
76
|
+
}
|
77
|
+
|
78
|
+
function retry() {
|
79
|
+
if (Date.now() - startTime >= timeout) {
|
80
|
+
reject(new Error(`Timeout: ${url} did not become available within ${timeout}ms`));
|
81
|
+
} else {
|
82
|
+
setTimeout(check, interval);
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
check();
|
87
|
+
});
|
88
|
+
}
|
89
|
+
|
90
|
+
function createStartPayload(deviceParams: DeviceParams, runId: string) {
|
91
|
+
const { model, firmware, appName, appVersion, dependency, dependencies } = deviceParams;
|
92
|
+
|
93
|
+
let additional_args = "";
|
94
|
+
|
95
|
+
if (dependency) {
|
96
|
+
additional_args = `-l ${dependency}:/apps/${conventionalAppSubpath(model, firmware, dependency, appVersion)}`;
|
97
|
+
} else if (dependencies) {
|
98
|
+
additional_args = [
|
99
|
+
...new Set(
|
100
|
+
dependencies.map(
|
101
|
+
dep =>
|
102
|
+
`-l ${dep.name}:/apps/${conventionalAppSubpath(
|
103
|
+
model,
|
104
|
+
firmware,
|
105
|
+
dep.name,
|
106
|
+
dep.appVersion ?? "1.0.0",
|
107
|
+
)}`,
|
108
|
+
),
|
109
|
+
),
|
110
|
+
].join(" ");
|
111
|
+
}
|
112
|
+
|
113
|
+
return {
|
114
|
+
ref: GITHUB_REF,
|
115
|
+
inputs: {
|
116
|
+
coin_app: appName,
|
117
|
+
coin_app_version: appVersion,
|
118
|
+
device: reverseModelMap[model],
|
119
|
+
device_os_version: firmware,
|
120
|
+
aws_role: AWS_ROLE,
|
121
|
+
cluster: CLUSTER,
|
122
|
+
seed: SEED,
|
123
|
+
run_id: runId,
|
124
|
+
additional_args,
|
125
|
+
},
|
126
|
+
};
|
127
|
+
}
|
128
|
+
|
129
|
+
export async function createSpeculosDeviceCI(
|
130
|
+
deviceParams: DeviceParams,
|
131
|
+
): Promise<SpeculosDevice | undefined> {
|
132
|
+
try {
|
133
|
+
const runId = uniqueId();
|
134
|
+
console.warn("Creating remote speculos:", runId);
|
135
|
+
const data = createStartPayload(deviceParams, runId);
|
136
|
+
await githubApiRequest({ urlSuffix: START_WORKFLOW_ID, data });
|
137
|
+
await waitForSpeculosReady(getSpeculosAddress(runId));
|
138
|
+
|
139
|
+
return {
|
140
|
+
id: runId,
|
141
|
+
port: speculosPort,
|
142
|
+
};
|
143
|
+
} catch (e: unknown) {
|
144
|
+
console.error(e);
|
145
|
+
console.warn(
|
146
|
+
`Creating remote speculos ${deviceParams.appName}:${deviceParams.appVersion} failed with ${String(e)}`,
|
147
|
+
);
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
export async function releaseSpeculosDeviceCI(runId: string) {
|
152
|
+
const data = {
|
153
|
+
ref: GITHUB_REF,
|
154
|
+
inputs: {
|
155
|
+
run_id: runId.toString(),
|
156
|
+
aws_role: AWS_ROLE,
|
157
|
+
cluster: CLUSTER,
|
158
|
+
},
|
159
|
+
};
|
160
|
+
await githubApiRequest({ urlSuffix: STOP_WORKFLOW_ID, data });
|
161
|
+
}
|
package/src/hw/connectApp.ts
CHANGED
@@ -534,7 +534,7 @@ export default function connectAppFactory(
|
|
534
534
|
const deviceAction = new ConnectAppDeviceAction({
|
535
535
|
input: {
|
536
536
|
application: appNameToDependency(appName),
|
537
|
-
dependencies: dependencies ? dependencies.map(name =>
|
537
|
+
dependencies: dependencies ? dependencies.map(name => ({ name })) : [],
|
538
538
|
requireLatestFirmware,
|
539
539
|
allowMissingApplication: allowPartialDependencies,
|
540
540
|
unlockTimeout: 0, // Expect to fail immediately when device is locked
|