@onekeyfe/hardware-cli 1.2.0-alpha.4 → 1.2.0-alpha.41
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/dist/cli.d.ts +86 -1
- package/dist/cli.js +364 -273
- package/dist/deviceSelection.d.ts +3 -0
- package/dist/deviceSelection.js +11 -0
- package/dist/deviceStateCommands.d.ts +19 -0
- package/dist/deviceStateCommands.js +62 -0
- package/dist/sdk.d.ts +2 -2
- package/dist/sdk.js +5 -5
- package/dist/session.d.ts +2 -9
- package/dist/session.js +3 -22
- package/dist/transports/nobleBlePlugin.js +113 -70
- package/package.json +7 -6
- package/src/__tests__/cli-version.test.ts +8 -0
- package/src/__tests__/device-selection.test.ts +29 -0
- package/src/__tests__/device-state-commands.test.ts +118 -0
- package/src/__tests__/firmware-update-legacy-command.test.ts +18 -0
- package/src/__tests__/firmware-update-v4-command.test.ts +100 -0
- package/src/__tests__/noble-ble-plugin.test.ts +370 -0
- package/src/__tests__/wallet-session.test.ts +55 -0
- package/src/__tests__/wallpaper-upload-command.test.ts +46 -0
- package/src/cli.ts +459 -321
- package/src/deviceSelection.ts +13 -0
- package/src/deviceStateCommands.ts +71 -0
- package/src/sdk.ts +6 -6
- package/src/session.ts +2 -24
- package/src/transports/nobleBlePlugin.ts +148 -76
package/dist/cli.js
CHANGED
|
@@ -1,42 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.program = exports.runFirmwareUpdateV4WithRetry = exports.buildWallpaperUploadMetrics = exports.prepareSession = exports.getLegacyFirmwareConnectTimeout = void 0;
|
|
4
5
|
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
5
7
|
const commander_1 = require("commander");
|
|
6
8
|
const hd_core_1 = require("@onekeyfe/hd-core");
|
|
7
9
|
const hd_shared_1 = require("@onekeyfe/hd-shared");
|
|
8
10
|
const chains_1 = require("./chains");
|
|
11
|
+
const deviceSelection_1 = require("./deviceSelection");
|
|
12
|
+
const deviceStateCommands_1 = require("./deviceStateCommands");
|
|
9
13
|
const sdk_1 = require("./sdk");
|
|
10
14
|
const session_1 = require("./session");
|
|
11
|
-
function extractPassphraseSession(payload) {
|
|
12
|
-
if (typeof payload === 'string') {
|
|
13
|
-
return { passphraseState: payload };
|
|
14
|
-
}
|
|
15
|
-
if (!payload || typeof payload !== 'object') {
|
|
16
|
-
return {};
|
|
17
|
-
}
|
|
18
|
-
const statePayload = payload;
|
|
19
|
-
let passphraseState;
|
|
20
|
-
if (typeof statePayload.passphrase_state === 'string') {
|
|
21
|
-
passphraseState = statePayload.passphrase_state;
|
|
22
|
-
}
|
|
23
|
-
else if (typeof statePayload.passphraseState === 'string') {
|
|
24
|
-
passphraseState = statePayload.passphraseState;
|
|
25
|
-
}
|
|
26
|
-
let sessionId;
|
|
27
|
-
if (typeof statePayload.session_id === 'string') {
|
|
28
|
-
sessionId = statePayload.session_id;
|
|
29
|
-
}
|
|
30
|
-
else if (typeof statePayload.sessionId === 'string') {
|
|
31
|
-
sessionId = statePayload.sessionId;
|
|
32
|
-
}
|
|
33
|
-
return { passphraseState, sessionId };
|
|
34
|
-
}
|
|
35
15
|
const program = new commander_1.Command();
|
|
16
|
+
exports.program = program;
|
|
17
|
+
const { version: cliVersion } = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(__dirname, '../package.json'), 'utf8'));
|
|
36
18
|
program
|
|
37
19
|
.name('onekey-hw')
|
|
38
20
|
.description('OneKey hardware wallet CLI for AI agent integration')
|
|
39
|
-
.version(
|
|
21
|
+
.version(cliVersion);
|
|
40
22
|
// ============================================================
|
|
41
23
|
// Global Options
|
|
42
24
|
// ============================================================
|
|
@@ -54,50 +36,103 @@ program
|
|
|
54
36
|
.description('Search for connected OneKey hardware wallet devices')
|
|
55
37
|
.action(() => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
56
38
|
const result = await sdk.searchDevices();
|
|
57
|
-
// USB 下自动读取 features 成本低;BLE 搜索阶段只做枚举,避免批量连接导致超时。
|
|
58
|
-
if (globalOpts.transport !== 'ble' && result?.success && Array.isArray(result.payload)) {
|
|
59
|
-
for (const device of result.payload) {
|
|
60
|
-
if (device.connectId) {
|
|
61
|
-
try {
|
|
62
|
-
const features = await sdk.getFeatures(device.connectId);
|
|
63
|
-
if (features?.success && features.payload) {
|
|
64
|
-
device.features = features.payload;
|
|
65
|
-
device.name = features.payload.label || features.payload.bleName || device.name;
|
|
66
|
-
const devType = features.payload.deviceType?.toLowerCase();
|
|
67
|
-
if (devType) {
|
|
68
|
-
device.deviceType = devType;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
// Features fetch failed — device may need PIN, continue with basic info
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
39
|
outputResult(globalOpts, result);
|
|
79
40
|
}));
|
|
80
41
|
program
|
|
81
42
|
.command('get-features')
|
|
82
43
|
.description('Get device features (firmware, unlock state, passphrase protection, etc.)')
|
|
83
44
|
.action(() => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
45
|
+
const result = await (0, deviceStateCommands_1.getCompatibleFeatures)(sdk, globalOpts.connectId);
|
|
46
|
+
outputResult(globalOpts, result);
|
|
47
|
+
}));
|
|
48
|
+
program
|
|
49
|
+
.command('get-state')
|
|
50
|
+
.description('Get canonical device state for Protocol V1 and Protocol V2 devices')
|
|
51
|
+
.option('--scope <scope>', 'State refresh scope: runtime, settings, or firmware', 'runtime')
|
|
52
|
+
.action((opts) => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
53
|
+
const supportedScopes = ['runtime', 'settings', 'firmware'];
|
|
54
|
+
if (!supportedScopes.includes(opts.scope)) {
|
|
55
|
+
const error = new Error(`Unsupported device state scope: ${opts.scope}`);
|
|
56
|
+
error.code = 'INVALID_DEVICE_STATE_SCOPE';
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
const result = await (0, deviceStateCommands_1.getCanonicalDeviceState)(sdk, globalOpts.connectId, opts.scope);
|
|
60
|
+
outputResult(globalOpts, result);
|
|
61
|
+
}));
|
|
62
|
+
program
|
|
63
|
+
.command('upload-wallpaper')
|
|
64
|
+
.description('Upload and activate a Pro2 wallpaper')
|
|
65
|
+
.requiredOption('--rgba <path>', '604x1024 raw RGBA file')
|
|
66
|
+
.option('--file-name <name>', 'Device wallpaper file name', 'wallpaper-cli')
|
|
67
|
+
.option('--chunk-size <bytes>', 'Transfer chunk size in bytes')
|
|
68
|
+
.action(opts => runCommand({}, async ({ sdk, globalOpts, params }) => {
|
|
69
|
+
const rgba = readBinaryParam(opts.rgba);
|
|
70
|
+
const expectedBytes = 604 * 1024 * 4;
|
|
71
|
+
if (rgba.byteLength !== expectedBytes) {
|
|
72
|
+
throw new Error(`Invalid RGBA size: expected ${expectedBytes} bytes, received ${rgba.byteLength}`);
|
|
73
|
+
}
|
|
74
|
+
let transferStartedAt;
|
|
75
|
+
let transferEndedAt;
|
|
76
|
+
let lastProgress = -1;
|
|
77
|
+
let lastPrintedProgress = -10;
|
|
78
|
+
let progressTotalBytes = 0;
|
|
79
|
+
let transferredBytes = 0;
|
|
80
|
+
const totalStartedAt = Date.now();
|
|
81
|
+
const onUiEvent = (message) => {
|
|
82
|
+
if (!message || typeof message !== 'object')
|
|
83
|
+
return;
|
|
84
|
+
const event = message;
|
|
85
|
+
if (event.type !== hd_core_1.UI_REQUEST.DEVICE_PROGRESS || !event.payload)
|
|
95
86
|
return;
|
|
87
|
+
const progress = Number(event.payload.progress);
|
|
88
|
+
if (!Number.isFinite(progress))
|
|
89
|
+
return;
|
|
90
|
+
transferStartedAt ?? (transferStartedAt = Date.now());
|
|
91
|
+
lastProgress = Math.max(lastProgress, progress);
|
|
92
|
+
const totalBytes = Number(event.payload.totalBytes);
|
|
93
|
+
if (Number.isFinite(totalBytes) && totalBytes > 0)
|
|
94
|
+
progressTotalBytes = totalBytes;
|
|
95
|
+
const confirmedBytes = Number(event.payload.transferredBytes);
|
|
96
|
+
if (Number.isFinite(confirmedBytes) && confirmedBytes >= 0) {
|
|
97
|
+
transferredBytes = Math.max(transferredBytes, confirmedBytes);
|
|
98
|
+
}
|
|
99
|
+
const printableProgress = Math.floor(progress / 10) * 10;
|
|
100
|
+
if (printableProgress > lastPrintedProgress || progress >= 100) {
|
|
101
|
+
const rate = Number(event.payload.rateBytesPerSecond);
|
|
102
|
+
const rateText = Number.isFinite(rate) && rate > 0 ? ` ${(rate / 1024).toFixed(2)} KiB/s` : '';
|
|
103
|
+
process.stderr.write(`[onekey-hw] Wallpaper transfer: ${Math.round(progress)}%${rateText}\n`);
|
|
104
|
+
lastPrintedProgress = progress >= 100 ? 100 : printableProgress;
|
|
96
105
|
}
|
|
97
|
-
|
|
106
|
+
if (progress >= 100)
|
|
107
|
+
transferEndedAt ?? (transferEndedAt = Date.now());
|
|
108
|
+
};
|
|
109
|
+
sdk.on(hd_core_1.UI_EVENT, onUiEvent);
|
|
110
|
+
let result;
|
|
111
|
+
try {
|
|
112
|
+
result = await sdk.deviceUploadWallpaper(globalOpts.connectId, {
|
|
113
|
+
...params,
|
|
114
|
+
width: 604,
|
|
115
|
+
height: 1024,
|
|
116
|
+
rgba,
|
|
117
|
+
fileName: opts.fileName,
|
|
118
|
+
chunkSize: opts.chunkSize ? safeParseInt(opts.chunkSize, '--chunk-size') : undefined,
|
|
119
|
+
});
|
|
98
120
|
}
|
|
99
|
-
|
|
100
|
-
|
|
121
|
+
finally {
|
|
122
|
+
sdk.off?.(hd_core_1.UI_EVENT, onUiEvent);
|
|
123
|
+
}
|
|
124
|
+
const endedAt = transferEndedAt ?? Date.now();
|
|
125
|
+
const totalBytes = Number(result?.payload?.size) || progressTotalBytes;
|
|
126
|
+
outputResult(globalOpts, {
|
|
127
|
+
...result,
|
|
128
|
+
metrics: buildWallpaperUploadMetrics({
|
|
129
|
+
totalBytes,
|
|
130
|
+
transferredBytes: result?.success ? totalBytes : transferredBytes,
|
|
131
|
+
startedAt: transferStartedAt ?? totalStartedAt,
|
|
132
|
+
endedAt,
|
|
133
|
+
lastProgress,
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
101
136
|
}));
|
|
102
137
|
// ============================================================
|
|
103
138
|
// Signing Commands
|
|
@@ -412,21 +447,45 @@ program
|
|
|
412
447
|
code: 'FIRMWARE_UPDATE_NOT_SUPPORTED',
|
|
413
448
|
},
|
|
414
449
|
}));
|
|
450
|
+
program
|
|
451
|
+
.command('firmware-update-legacy')
|
|
452
|
+
.description('Update Classic/Pure firmware through the legacy protocol')
|
|
453
|
+
.requiredOption('--binary <path>', 'Local firmware binary path')
|
|
454
|
+
.option('--device-name <name>', 'BLE advertising name, for example K1514')
|
|
455
|
+
.option('--update-type <type>', 'Firmware component: firmware or ble', 'firmware')
|
|
456
|
+
.option('--no-reboot', 'Do not reboot the device after a successful update')
|
|
457
|
+
.action(opts => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
458
|
+
if (opts.updateType !== 'firmware' && opts.updateType !== 'ble') {
|
|
459
|
+
throw new Error(`Unsupported --update-type: ${opts.updateType}. Use "firmware" or "ble".`);
|
|
460
|
+
}
|
|
461
|
+
const connectId = await resolveLegacyFirmwareConnectId(sdk, globalOpts.connectId, opts.deviceName);
|
|
462
|
+
const result = await sdk.firmwareUpdate(connectId, {
|
|
463
|
+
binary: readBinaryParam(opts.binary),
|
|
464
|
+
updateType: opts.updateType,
|
|
465
|
+
rebootOnSuccess: opts.reboot,
|
|
466
|
+
timeout: getLegacyFirmwareConnectTimeout(globalOpts.transport),
|
|
467
|
+
});
|
|
468
|
+
outputResult(globalOpts, result);
|
|
469
|
+
}));
|
|
470
|
+
function getLegacyFirmwareConnectTimeout(transport) {
|
|
471
|
+
return transport === 'usb' ? 90000 : undefined;
|
|
472
|
+
}
|
|
473
|
+
exports.getLegacyFirmwareConnectTimeout = getLegacyFirmwareConnectTimeout;
|
|
415
474
|
program
|
|
416
475
|
.command('firmware-update-ble')
|
|
417
476
|
.description('Run Protocol V2 firmware update over BLE')
|
|
418
477
|
.action(() => respondAndExit({
|
|
419
478
|
success: false,
|
|
420
479
|
payload: {
|
|
421
|
-
error: 'Use `onekey-hw --transport ble firmware-update-v4
|
|
422
|
-
code: '
|
|
480
|
+
error: 'Use `onekey-hw --transport ble firmware-update-v4` for BLE Protocol V2 firmware updates.',
|
|
481
|
+
code: 'USE_FIRMWARE_UPDATE_V4',
|
|
423
482
|
},
|
|
424
483
|
}));
|
|
425
484
|
program
|
|
426
|
-
.command('firmware-update-v4
|
|
427
|
-
.description('
|
|
485
|
+
.command('firmware-update-v4')
|
|
486
|
+
.description('Run Protocol V2 firmware update through sdk.firmwareUpdateV4')
|
|
428
487
|
.option('--chunk-size <bytes>', 'Transfer chunk size in bytes')
|
|
429
|
-
.option('--resource <
|
|
488
|
+
.option('--resource-bundle <spec...>', 'RESC bundle direct-write spec: <localPath>:<devicePath>, e.g. wallpaper.okpkg:vol0:/bundles/images/wallpaper.okpkg')
|
|
430
489
|
.option('--romloader <path>', 'FW_MGMT_TARGET_ROMLOADER binary path')
|
|
431
490
|
.option('--bootloader <path>', 'FW_MGMT_TARGET_BOOTLOADER binary path')
|
|
432
491
|
.option('--application-p1 <path>', 'FW_MGMT_TARGET_APPLICATION_P1 binary path')
|
|
@@ -439,8 +498,8 @@ program
|
|
|
439
498
|
.option('--forced-update-res', 'Force resource update')
|
|
440
499
|
.option('--retries <count>', 'Retry count for transient Protocol V2 USB probe failures')
|
|
441
500
|
.action(opts => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
442
|
-
const params =
|
|
443
|
-
const result = await
|
|
501
|
+
const params = buildFirmwareUpdateV4Params(opts);
|
|
502
|
+
const result = await runFirmwareUpdateV4WithRetry({
|
|
444
503
|
sdk,
|
|
445
504
|
globalOpts,
|
|
446
505
|
params,
|
|
@@ -563,7 +622,7 @@ program
|
|
|
563
622
|
const sessionCmd = program.command('session').description('Manage device passphrase session cache');
|
|
564
623
|
sessionCmd
|
|
565
624
|
.command('connect')
|
|
566
|
-
.description('Connect device and
|
|
625
|
+
.description('Connect device and select a hidden wallet for this invocation')
|
|
567
626
|
.action(() => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
568
627
|
// 1. Search for device
|
|
569
628
|
const searchResult = await sdk.searchDevices();
|
|
@@ -574,7 +633,14 @@ sessionCmd
|
|
|
574
633
|
});
|
|
575
634
|
return;
|
|
576
635
|
}
|
|
577
|
-
const device = searchResult.payload
|
|
636
|
+
const device = (0, deviceSelection_1.selectSearchDevice)(searchResult.payload, globalOpts.connectId);
|
|
637
|
+
if (!device) {
|
|
638
|
+
outputResult(globalOpts, {
|
|
639
|
+
success: false,
|
|
640
|
+
payload: { error: 'No matching device found', code: 'NO_DEVICE' },
|
|
641
|
+
});
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
578
644
|
const connectId = device.connectId || globalOpts.connectId;
|
|
579
645
|
// 2. Unlock if locked — getPassphraseState below talks to a live
|
|
580
646
|
// device session, which a locked device will reject with an obscure
|
|
@@ -583,56 +649,33 @@ sessionCmd
|
|
|
583
649
|
process.stderr.write('[onekey-hw] Device is locked. Unlocking (PIN required)...\n');
|
|
584
650
|
await unlockWithRetry(sdk, connectId);
|
|
585
651
|
}
|
|
586
|
-
// 3.
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
useEmptyPassphrase: false,
|
|
652
|
+
// 3. Open a hidden wallet session (triggers 1/2/3 selection).
|
|
653
|
+
const sessionResult = await sdk.openWalletSession(connectId, {
|
|
654
|
+
mode: 'select-hidden',
|
|
590
655
|
});
|
|
591
|
-
if (!
|
|
592
|
-
outputResult(globalOpts,
|
|
656
|
+
if (!sessionResult.success) {
|
|
657
|
+
outputResult(globalOpts, sessionResult);
|
|
593
658
|
return;
|
|
594
659
|
}
|
|
595
|
-
|
|
596
|
-
if (!passphraseState) {
|
|
660
|
+
if (sessionResult.payload.walletType !== 'hidden') {
|
|
597
661
|
outputResult(globalOpts, {
|
|
598
662
|
success: false,
|
|
599
|
-
payload: { error: '
|
|
663
|
+
payload: { error: 'Hidden wallet selection did not return a hidden wallet session' },
|
|
600
664
|
});
|
|
601
665
|
return;
|
|
602
666
|
}
|
|
667
|
+
const { deviceId, passphraseState } = sessionResult.payload;
|
|
603
668
|
// 4. Get address to verify + extract deviceId
|
|
604
|
-
const addrResult = await sdk.evmGetAddress(connectId,
|
|
669
|
+
const addrResult = await sdk.evmGetAddress(connectId, deviceId, {
|
|
605
670
|
path: "m/44'/60'/0'/0/0",
|
|
606
671
|
showOnOneKey: false,
|
|
607
672
|
passphraseState,
|
|
608
673
|
});
|
|
609
|
-
// 5. Fetch the now-active session_id via getFeatures.
|
|
610
|
-
//
|
|
611
|
-
// IMPORTANT: pass `passphraseState` here. Without it, the SDK's
|
|
612
|
-
// connectStateChange guard (core/index.ts) would see the payload's
|
|
613
|
-
// passphraseState flip from mnNy → undefined, clear the cached Device,
|
|
614
|
-
// and call Initialize again with no passphrase_state / no session_id.
|
|
615
|
-
// That Initialize resets the device to the standard wallet and returns
|
|
616
|
-
// a *standard-wallet* session_id — which we'd then save in the keychain
|
|
617
|
-
// paired with the hidden-wallet passphraseState. On the next CLI run
|
|
618
|
-
// the mismatch would trigger PassphraseRequest (1/2/3 again).
|
|
619
|
-
const featResult = await sdk.getFeatures(connectId, {
|
|
620
|
-
passphraseState,
|
|
621
|
-
skipPassphraseCheck: true,
|
|
622
|
-
});
|
|
623
|
-
const featPayload = featResult?.success ? featResult.payload : undefined;
|
|
624
|
-
const deviceId = featPayload?.deviceId || device.deviceId || '';
|
|
625
|
-
const sessionId = passphraseSessionId || featPayload?.sessionId || '';
|
|
626
|
-
// 6. Save to keychain
|
|
627
|
-
if (passphraseState && deviceId && sessionId) {
|
|
628
|
-
await (0, session_1.saveSessionToKeychain)(deviceId, passphraseState, sessionId);
|
|
629
|
-
}
|
|
630
674
|
outputResult(globalOpts, {
|
|
631
675
|
success: true,
|
|
632
676
|
payload: {
|
|
633
677
|
passphraseState,
|
|
634
678
|
deviceId,
|
|
635
|
-
...(sessionId ? { sessionId } : {}),
|
|
636
679
|
...(addrResult?.success ? { address: addrResult.payload.address } : {}),
|
|
637
680
|
},
|
|
638
681
|
});
|
|
@@ -642,8 +685,7 @@ sessionCmd
|
|
|
642
685
|
.description('Clear cached device session')
|
|
643
686
|
.action(() => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
644
687
|
const searchResult = await sdk.searchDevices();
|
|
645
|
-
const device =
|
|
646
|
-
searchResult?.payload?.[0];
|
|
688
|
+
const device = (0, deviceSelection_1.selectSearchDevice)(searchResult?.payload ?? [], globalOpts.connectId);
|
|
647
689
|
const deviceId = device?.deviceId || device?.features?.device_id;
|
|
648
690
|
if (deviceId) {
|
|
649
691
|
await (0, session_1.clearSessionFromKeychain)(deviceId);
|
|
@@ -720,8 +762,8 @@ async function unlockWithRetry(sdk, connectId, maxAttempts = 3) {
|
|
|
720
762
|
* Prepare passphrase session before SDK calls.
|
|
721
763
|
*
|
|
722
764
|
* 1. If --use-empty-passphrase or --passphrase-state provided → use as-is
|
|
723
|
-
* 2. Try keychain → preloadSessionCache → use cached session
|
|
724
|
-
* 3. Keychain miss →
|
|
765
|
+
* 2. Try a legacy keychain entry → preloadSessionCache → use cached session
|
|
766
|
+
* 3. Keychain miss → openWalletSession (triggers 1/2/3 prompt)
|
|
725
767
|
*
|
|
726
768
|
* After this, globalOpts.passphraseState is set and getCommonParams will include it.
|
|
727
769
|
*/
|
|
@@ -733,7 +775,7 @@ globalOpts) {
|
|
|
733
775
|
return globalOpts.passphraseState;
|
|
734
776
|
}
|
|
735
777
|
// Errors from the SDK calls below (PIN cancelled, transport broken,
|
|
736
|
-
//
|
|
778
|
+
// openWalletSession rejection) intentionally propagate to runCommand's
|
|
737
779
|
// catch block, which renders them as structured `{ success: false,
|
|
738
780
|
// payload: { error, code } }` output instead of silently falling through
|
|
739
781
|
// to a confusing downstream error 112 / 114.
|
|
@@ -744,18 +786,22 @@ globalOpts) {
|
|
|
744
786
|
searchResult.payload.length === 0) {
|
|
745
787
|
return undefined;
|
|
746
788
|
}
|
|
747
|
-
const device = searchResult.payload
|
|
748
|
-
|
|
789
|
+
const device = (0, deviceSelection_1.selectSearchDevice)(searchResult.payload, globalOpts.connectId);
|
|
790
|
+
if (!device) {
|
|
791
|
+
throw new Error(`未找到指定的 BLE 设备: ${globalOpts.connectId}`);
|
|
792
|
+
}
|
|
793
|
+
const selectedDevice = device;
|
|
794
|
+
const connectId = selectedDevice.connectId || globalOpts.connectId || '';
|
|
749
795
|
if (!globalOpts.connectId && connectId) {
|
|
750
796
|
globalOpts.connectId = connectId;
|
|
751
797
|
}
|
|
752
798
|
// ── Step 2: Get features if searchDevices didn't populate them ──
|
|
753
799
|
// getFeatures failures here are non-fatal — we fall through to Step 3
|
|
754
800
|
// which will fail with a clearer error if the device is truly unreachable.
|
|
755
|
-
let deviceId =
|
|
756
|
-
let deviceType =
|
|
757
|
-
let unlocked =
|
|
758
|
-
let passphraseProtection =
|
|
801
|
+
let deviceId = selectedDevice.features?.deviceId || selectedDevice.deviceId || '';
|
|
802
|
+
let deviceType = selectedDevice.features?.deviceType ?? selectedDevice.deviceType ?? hd_shared_1.EDeviceType.Unknown;
|
|
803
|
+
let unlocked = selectedDevice.features?.unlocked;
|
|
804
|
+
let passphraseProtection = selectedDevice.features?.passphraseProtection;
|
|
759
805
|
if (!deviceId || unlocked == null || passphraseProtection == null) {
|
|
760
806
|
try {
|
|
761
807
|
const featResult = await sdk.getFeatures(connectId);
|
|
@@ -790,7 +836,7 @@ globalOpts) {
|
|
|
790
836
|
if (passphraseProtection === false && deviceType !== hd_shared_1.EDeviceType.Pro2) {
|
|
791
837
|
return undefined;
|
|
792
838
|
}
|
|
793
|
-
// ── Step 5: Try keychain session reuse
|
|
839
|
+
// ── Step 5: Try legacy keychain session reuse ────────────────────
|
|
794
840
|
// Only attempt if device was already unlocked — locking invalidates
|
|
795
841
|
// all passphrase sessions, so cached session_id is useless after unlock.
|
|
796
842
|
if (!wasLocked && deviceId) {
|
|
@@ -800,38 +846,22 @@ globalOpts) {
|
|
|
800
846
|
return cached;
|
|
801
847
|
}
|
|
802
848
|
}
|
|
803
|
-
// ── Step 6: Keychain miss →
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
useEmptyPassphrase: false,
|
|
849
|
+
// ── Step 6: Keychain miss → openWalletSession (triggers 1/2/3 prompt) ──
|
|
850
|
+
const sessionResult = await sdk.openWalletSession(connectId, {
|
|
851
|
+
mode: 'select-hidden',
|
|
807
852
|
});
|
|
808
|
-
if (
|
|
809
|
-
|
|
810
|
-
if (!passphraseState) {
|
|
853
|
+
if (sessionResult.success && sessionResult.payload) {
|
|
854
|
+
if (sessionResult.payload.walletType !== 'hidden') {
|
|
811
855
|
return undefined;
|
|
812
856
|
}
|
|
857
|
+
const { deviceId: sessionDeviceId, passphraseState } = sessionResult.payload;
|
|
858
|
+
globalOpts.deviceId = sessionDeviceId;
|
|
813
859
|
globalOpts.passphraseState = passphraseState;
|
|
814
|
-
// Save session to keychain for next invocation.
|
|
815
|
-
//
|
|
816
|
-
// Pass passphraseState to keep connectStateChange=false — otherwise
|
|
817
|
-
// Initialize would be re-run without passphrase_state, resetting the
|
|
818
|
-
// device to the standard wallet and returning a mismatched session_id.
|
|
819
|
-
// See the matching comment in `session connect`.
|
|
820
|
-
if (deviceId) {
|
|
821
|
-
const featAfter = await sdk.getFeatures(connectId, {
|
|
822
|
-
passphraseState,
|
|
823
|
-
skipPassphraseCheck: true,
|
|
824
|
-
});
|
|
825
|
-
const sessionId = passphraseSessionId || (featAfter?.success ? featAfter.payload?.sessionId : undefined);
|
|
826
|
-
if (sessionId) {
|
|
827
|
-
await (0, session_1.saveSessionToKeychain)(deviceId, passphraseState, sessionId);
|
|
828
|
-
await (0, session_1.preloadSessionFromKeychain)(deviceId);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
860
|
return passphraseState;
|
|
832
861
|
}
|
|
833
862
|
return undefined;
|
|
834
863
|
}
|
|
864
|
+
exports.prepareSession = prepareSession;
|
|
835
865
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
836
866
|
function outputResult(_globalOpts, result) {
|
|
837
867
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -841,8 +871,8 @@ function outputResult(_globalOpts, result) {
|
|
|
841
871
|
!result.success) {
|
|
842
872
|
process.exitCode = 1;
|
|
843
873
|
}
|
|
844
|
-
// No process.exit here — runCommand()
|
|
845
|
-
//
|
|
874
|
+
// No process.exit here — runCommand() waits for SDK cleanup, then lets Node
|
|
875
|
+
// exit naturally so leaked USB handles remain observable.
|
|
846
876
|
}
|
|
847
877
|
async function runCommand(options, handler) {
|
|
848
878
|
const globalOpts = program.opts();
|
|
@@ -875,9 +905,7 @@ async function runCommand(options, handler) {
|
|
|
875
905
|
// promise reference. Idempotent, safe to call even if init failed.
|
|
876
906
|
await (0, sdk_1.disposeSDK)();
|
|
877
907
|
}
|
|
878
|
-
//
|
|
879
|
-
// setImmediate lets any trailing stdout/stderr writes flush first.
|
|
880
|
-
setImmediate(() => process.exit(process.exitCode ?? 0));
|
|
908
|
+
// disposeSDK awaits transport cleanup; let Node exit naturally so leaks remain visible.
|
|
881
909
|
}
|
|
882
910
|
/** For commands that don't touch the SDK at all (e.g. firmware-update stubs). */
|
|
883
911
|
function respondAndExit(result) {
|
|
@@ -902,9 +930,51 @@ function readBinaryParam(path) {
|
|
|
902
930
|
const buffer = (0, node_fs_1.readFileSync)(path);
|
|
903
931
|
return new Uint8Array(buffer).buffer;
|
|
904
932
|
}
|
|
905
|
-
function
|
|
933
|
+
async function resolveLegacyFirmwareConnectId(sdk, explicitConnectId, deviceName) {
|
|
934
|
+
if (explicitConnectId && !deviceName)
|
|
935
|
+
return explicitConnectId;
|
|
936
|
+
const searchResult = await sdk.searchDevices();
|
|
937
|
+
if (!searchResult?.success || !Array.isArray(searchResult.payload)) {
|
|
938
|
+
throw new Error('Unable to scan BLE devices');
|
|
939
|
+
}
|
|
940
|
+
const devices = searchResult.payload;
|
|
941
|
+
const normalizedName = deviceName?.trim().toLowerCase();
|
|
942
|
+
const matches = normalizedName
|
|
943
|
+
? devices.filter(device => device.name?.trim().toLowerCase() === normalizedName)
|
|
944
|
+
: devices.filter(device => device.deviceType?.toLowerCase() === 'classic');
|
|
945
|
+
if (matches.length === 0) {
|
|
946
|
+
throw new Error(normalizedName
|
|
947
|
+
? `BLE device not found by name: ${deviceName}`
|
|
948
|
+
: 'No Classic/Pure BLE device found');
|
|
949
|
+
}
|
|
950
|
+
if (matches.length > 1) {
|
|
951
|
+
throw new Error(normalizedName
|
|
952
|
+
? `Multiple BLE devices found by name: ${deviceName}`
|
|
953
|
+
: 'Multiple Classic/Pure BLE devices found; specify --device-name');
|
|
954
|
+
}
|
|
955
|
+
const [{ connectId, name }] = matches;
|
|
956
|
+
if (!connectId)
|
|
957
|
+
throw new Error(`BLE device has no connect ID: ${name}`);
|
|
958
|
+
return connectId;
|
|
959
|
+
}
|
|
960
|
+
function parseResourceBundleParam(spec) {
|
|
961
|
+
const sep = spec.indexOf(':');
|
|
962
|
+
if (sep <= 0 || sep === spec.length - 1) {
|
|
963
|
+
throw new Error(`Invalid --resource-bundle value: "${spec}". Expected <localPath>:<devicePath>`);
|
|
964
|
+
}
|
|
965
|
+
const localPath = spec.slice(0, sep);
|
|
966
|
+
const devicePath = spec.slice(sep + 1);
|
|
967
|
+
if (!devicePath.startsWith('vol')) {
|
|
968
|
+
throw new Error(`Invalid --resource-bundle device path: "${devicePath}". Expected a vol*:/... path`);
|
|
969
|
+
}
|
|
970
|
+
return {
|
|
971
|
+
binary: readBinaryParam(localPath),
|
|
972
|
+
devicePath,
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function getFirmwareUpdateV4TotalBytes(params) {
|
|
906
976
|
return [
|
|
907
|
-
params.
|
|
977
|
+
...(params.resourceBundleFiles?.map(item => item.binary) ?? []),
|
|
908
978
|
params.bootloaderBinary,
|
|
909
979
|
params.applicationP1Binary,
|
|
910
980
|
params.applicationP2Binary,
|
|
@@ -915,17 +985,17 @@ function getFirmwareUpdateV4DebugTotalBytes(params) {
|
|
|
915
985
|
params.se04Binary,
|
|
916
986
|
].reduce((total, binary) => total + (binary?.byteLength ?? 0), 0);
|
|
917
987
|
}
|
|
918
|
-
function
|
|
988
|
+
function getFirmwareUpdateV4ErrorText(result) {
|
|
919
989
|
if (!result || typeof result !== 'object')
|
|
920
990
|
return '';
|
|
921
|
-
const payload = result
|
|
991
|
+
const { payload } = result;
|
|
922
992
|
if (!payload || typeof payload !== 'object')
|
|
923
993
|
return '';
|
|
924
|
-
const error = payload
|
|
994
|
+
const { error } = payload;
|
|
925
995
|
return typeof error === 'string' ? error : '';
|
|
926
996
|
}
|
|
927
997
|
function isProtocolV2UsbProbeTransientResult(result) {
|
|
928
|
-
const error =
|
|
998
|
+
const error = getFirmwareUpdateV4ErrorText(result);
|
|
929
999
|
return (error.includes('Device protocol mismatch') &&
|
|
930
1000
|
error.includes('expected V2') &&
|
|
931
1001
|
error.includes('did not respond to expected protocol'));
|
|
@@ -933,22 +1003,33 @@ function isProtocolV2UsbProbeTransientResult(result) {
|
|
|
933
1003
|
function isSuccessResult(result) {
|
|
934
1004
|
return (!!result && typeof result === 'object' && result.success === true);
|
|
935
1005
|
}
|
|
936
|
-
function
|
|
1006
|
+
function getFirmwareUpdatePayload(message) {
|
|
937
1007
|
if (!message || typeof message !== 'object')
|
|
938
1008
|
return undefined;
|
|
939
1009
|
return message.payload;
|
|
940
1010
|
}
|
|
941
|
-
function
|
|
1011
|
+
function formatFirmwareProgress(progress) {
|
|
942
1012
|
if (!Number.isFinite(progress))
|
|
943
1013
|
return '0%';
|
|
944
1014
|
return `${Math.min(Math.max(Math.round(progress), 0), 100)}%`;
|
|
945
1015
|
}
|
|
946
|
-
function
|
|
1016
|
+
function formatFirmwareBytes(bytes) {
|
|
947
1017
|
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
948
1018
|
return '';
|
|
949
1019
|
return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
950
1020
|
}
|
|
951
|
-
function
|
|
1021
|
+
function buildWallpaperUploadMetrics({ totalBytes, transferredBytes, startedAt, endedAt, lastProgress, }) {
|
|
1022
|
+
const elapsedMs = Math.max(endedAt - startedAt, 0);
|
|
1023
|
+
return {
|
|
1024
|
+
totalBytes,
|
|
1025
|
+
transferredBytes,
|
|
1026
|
+
totalSeconds: Number((elapsedMs / 1000).toFixed(2)),
|
|
1027
|
+
transferKiBPerSecond: elapsedMs > 0 ? Number((transferredBytes / 1024 / (elapsedMs / 1000)).toFixed(2)) : null,
|
|
1028
|
+
lastProgress,
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
exports.buildWallpaperUploadMetrics = buildWallpaperUploadMetrics;
|
|
1032
|
+
function maybePrintFirmwareProgress({ progressType, progress, payload, lastPrintedProgress, }) {
|
|
952
1033
|
const printableProgress = Math.floor(progress / 10) * 10;
|
|
953
1034
|
if (printableProgress <= lastPrintedProgress && progress < 100) {
|
|
954
1035
|
return lastPrintedProgress;
|
|
@@ -957,15 +1038,15 @@ function maybePrintFirmwareDebugProgress({ progressType, progress, payload, last
|
|
|
957
1038
|
const totalBytes = Number(payload.totalBytes);
|
|
958
1039
|
const rateBytesPerSecond = Number(payload.rateBytesPerSecond);
|
|
959
1040
|
const sizeText = Number.isFinite(transferredBytes) && Number.isFinite(totalBytes) && totalBytes > 0
|
|
960
|
-
? ` ${
|
|
1041
|
+
? ` ${formatFirmwareBytes(transferredBytes)}/${formatFirmwareBytes(totalBytes)}`
|
|
961
1042
|
: '';
|
|
962
1043
|
const speedText = Number.isFinite(rateBytesPerSecond) && rateBytesPerSecond > 0
|
|
963
1044
|
? ` ${(rateBytesPerSecond / 1024).toFixed(2)} KiB/s`
|
|
964
1045
|
: '';
|
|
965
|
-
process.stderr.write(`[onekey-hw] Firmware ${progressType}: ${
|
|
1046
|
+
process.stderr.write(`[onekey-hw] Firmware ${progressType}: ${formatFirmwareProgress(progress)}${sizeText}${speedText}\n`);
|
|
966
1047
|
return progress >= 100 ? 100 : printableProgress;
|
|
967
1048
|
}
|
|
968
|
-
function
|
|
1049
|
+
function buildFirmwareUpdateV4Metrics({ attempt, maxAttempts, totalBytes, totalStartedAt, transferStartedAt, transferEndedAt, installStartedAt, installEndedAt, progressEvents, lastProgress, installProgressEvents, lastInstallProgress, retried, }) {
|
|
969
1050
|
const totalElapsedMs = Date.now() - totalStartedAt;
|
|
970
1051
|
const transferElapsedMs = transferStartedAt !== undefined && transferEndedAt !== undefined
|
|
971
1052
|
? transferEndedAt - transferStartedAt
|
|
@@ -990,137 +1071,145 @@ function buildFirmwareUpdateV4DebugMetrics({ attempt, maxAttempts, totalBytes, t
|
|
|
990
1071
|
totalSeconds: Number((totalElapsedMs / 1000).toFixed(2)),
|
|
991
1072
|
};
|
|
992
1073
|
}
|
|
993
|
-
async function
|
|
994
|
-
const totalBytes =
|
|
1074
|
+
async function runFirmwareUpdateV4WithRetry({ sdk, globalOpts, params, retries, }) {
|
|
1075
|
+
const totalBytes = getFirmwareUpdateV4TotalBytes(params);
|
|
995
1076
|
const maxAttempts = Math.max((retries ?? 2) + 1, 1);
|
|
996
1077
|
let currentSdk = sdk;
|
|
997
|
-
let lastResult;
|
|
998
1078
|
let retried = false;
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
? undefined
|
|
1013
|
-
: globalOpts.connectId;
|
|
1014
|
-
const onUiEvent = (message) => {
|
|
1015
|
-
if (!message || typeof message !== 'object')
|
|
1016
|
-
return;
|
|
1017
|
-
const messageType = message.type;
|
|
1018
|
-
const payload = getFirmwareDebugPayload(message);
|
|
1019
|
-
if (messageType === hd_core_1.UI_REQUEST.FIRMWARE_TIP) {
|
|
1020
|
-
const tipMessage = payload?.data?.message;
|
|
1021
|
-
if (typeof tipMessage === 'string') {
|
|
1022
|
-
process.stderr.write(`[onekey-hw] Firmware: ${tipMessage}\n`);
|
|
1023
|
-
}
|
|
1024
|
-
return;
|
|
1025
|
-
}
|
|
1026
|
-
if (messageType === hd_core_1.UI_REQUEST.REQUEST_BUTTON) {
|
|
1027
|
-
const code = typeof payload?.code === 'string' ? ` (${payload.code})` : '';
|
|
1028
|
-
process.stderr.write(`[onekey-hw] Please confirm the firmware update on your device${code}.\n`);
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
if (messageType !== hd_core_1.UI_REQUEST.FIRMWARE_PROGRESS || !payload)
|
|
1032
|
-
return;
|
|
1033
|
-
const progress = Number(payload.progress);
|
|
1034
|
-
if (!Number.isFinite(progress))
|
|
1035
|
-
return;
|
|
1036
|
-
if (payload.progressType === 'transferData') {
|
|
1037
|
-
progressEvents += 1;
|
|
1038
|
-
lastProgress = Math.max(lastProgress, progress);
|
|
1039
|
-
transferStartedAt ?? (transferStartedAt = Date.now());
|
|
1040
|
-
lastPrintedTransferProgress = maybePrintFirmwareDebugProgress({
|
|
1041
|
-
progressType: 'transfer',
|
|
1042
|
-
progress,
|
|
1043
|
-
payload,
|
|
1044
|
-
lastPrintedProgress: lastPrintedTransferProgress,
|
|
1045
|
-
});
|
|
1046
|
-
if (progress >= 100) {
|
|
1047
|
-
transferEndedAt ?? (transferEndedAt = Date.now());
|
|
1048
|
-
}
|
|
1049
|
-
return;
|
|
1050
|
-
}
|
|
1051
|
-
if (payload.progressType === 'installingFirmware') {
|
|
1052
|
-
installProgressEvents += 1;
|
|
1053
|
-
lastInstallProgress = Math.max(lastInstallProgress, progress);
|
|
1054
|
-
installStartedAt ?? (installStartedAt = Date.now());
|
|
1055
|
-
lastPrintedInstallProgress = maybePrintFirmwareDebugProgress({
|
|
1056
|
-
progressType: 'install',
|
|
1057
|
-
progress,
|
|
1058
|
-
payload,
|
|
1059
|
-
lastPrintedProgress: lastPrintedInstallProgress,
|
|
1060
|
-
});
|
|
1061
|
-
if (progress >= 100) {
|
|
1062
|
-
installEndedAt ?? (installEndedAt = Date.now());
|
|
1063
|
-
}
|
|
1079
|
+
let attempt = 1;
|
|
1080
|
+
let { connectId } = globalOpts;
|
|
1081
|
+
if (globalOpts.transport === 'usb') {
|
|
1082
|
+
for (; attempt <= maxAttempts; attempt += 1) {
|
|
1083
|
+
const probeResult = await currentSdk.getDeviceState(connectId, {
|
|
1084
|
+
scope: 'runtime',
|
|
1085
|
+
connectProtocol: 'V2',
|
|
1086
|
+
retryCount: 0,
|
|
1087
|
+
});
|
|
1088
|
+
if (isSuccessResult(probeResult))
|
|
1089
|
+
break;
|
|
1090
|
+
if (attempt >= maxAttempts || !isProtocolV2UsbProbeTransientResult(probeResult)) {
|
|
1091
|
+
return probeResult;
|
|
1064
1092
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
currentSdk
|
|
1093
|
+
retried = true;
|
|
1094
|
+
process.stderr.write(`[onekey-hw] Protocol V2 USB probe was transient; retrying read-only probe (${attempt}/${maxAttempts})...\n`);
|
|
1095
|
+
await (0, sdk_1.disposeSDK)();
|
|
1096
|
+
await new Promise(resolve => {
|
|
1097
|
+
setTimeout(resolve, 3000);
|
|
1098
|
+
});
|
|
1099
|
+
currentSdk = await (0, sdk_1.createSDK)(globalOpts);
|
|
1100
|
+
if (globalOpts.connectId)
|
|
1101
|
+
connectId = undefined;
|
|
1072
1102
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1103
|
+
}
|
|
1104
|
+
let progressEvents = 0;
|
|
1105
|
+
let lastProgress = -1;
|
|
1106
|
+
let transferStartedAt;
|
|
1107
|
+
let transferEndedAt;
|
|
1108
|
+
let installProgressEvents = 0;
|
|
1109
|
+
let lastInstallProgress = -1;
|
|
1110
|
+
let installStartedAt;
|
|
1111
|
+
let installEndedAt;
|
|
1112
|
+
let lastPrintedTransferProgress = -10;
|
|
1113
|
+
let lastPrintedInstallProgress = -10;
|
|
1114
|
+
const totalStartedAt = Date.now();
|
|
1115
|
+
const onUiEvent = (message) => {
|
|
1116
|
+
if (!message || typeof message !== 'object')
|
|
1117
|
+
return;
|
|
1118
|
+
const messageType = message.type;
|
|
1119
|
+
const payload = getFirmwareUpdatePayload(message);
|
|
1120
|
+
if (messageType === hd_core_1.UI_REQUEST.FIRMWARE_TIP) {
|
|
1121
|
+
const tipMessage = payload?.data?.message;
|
|
1122
|
+
if (typeof tipMessage === 'string') {
|
|
1123
|
+
process.stderr.write(`[onekey-hw] Firmware: ${tipMessage}\n`);
|
|
1124
|
+
}
|
|
1125
|
+
return;
|
|
1075
1126
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
totalStartedAt,
|
|
1081
|
-
transferStartedAt,
|
|
1082
|
-
transferEndedAt,
|
|
1083
|
-
installStartedAt,
|
|
1084
|
-
installEndedAt,
|
|
1085
|
-
progressEvents,
|
|
1086
|
-
lastProgress,
|
|
1087
|
-
installProgressEvents,
|
|
1088
|
-
lastInstallProgress,
|
|
1089
|
-
retried,
|
|
1090
|
-
});
|
|
1091
|
-
if (lastResult && typeof lastResult === 'object') {
|
|
1092
|
-
const payload = (lastResult.payload ?? {});
|
|
1093
|
-
lastResult = {
|
|
1094
|
-
...lastResult,
|
|
1095
|
-
payload: {
|
|
1096
|
-
...payload,
|
|
1097
|
-
_debug: debugMetrics,
|
|
1098
|
-
},
|
|
1099
|
-
};
|
|
1127
|
+
if (messageType === hd_core_1.UI_REQUEST.REQUEST_BUTTON) {
|
|
1128
|
+
const code = typeof payload?.code === 'string' ? ` (${payload.code})` : '';
|
|
1129
|
+
process.stderr.write(`[onekey-hw] Please confirm the firmware update on your device${code}.\n`);
|
|
1130
|
+
return;
|
|
1100
1131
|
}
|
|
1101
|
-
if (
|
|
1102
|
-
return
|
|
1132
|
+
if (messageType !== hd_core_1.UI_REQUEST.FIRMWARE_PROGRESS || !payload)
|
|
1133
|
+
return;
|
|
1134
|
+
const progress = Number(payload.progress);
|
|
1135
|
+
if (!Number.isFinite(progress))
|
|
1136
|
+
return;
|
|
1137
|
+
if (payload.progressType === 'transferData') {
|
|
1138
|
+
progressEvents += 1;
|
|
1139
|
+
lastProgress = Math.max(lastProgress, progress);
|
|
1140
|
+
transferStartedAt ?? (transferStartedAt = Date.now());
|
|
1141
|
+
lastPrintedTransferProgress = maybePrintFirmwareProgress({
|
|
1142
|
+
progressType: 'transfer',
|
|
1143
|
+
progress,
|
|
1144
|
+
payload,
|
|
1145
|
+
lastPrintedProgress: lastPrintedTransferProgress,
|
|
1146
|
+
});
|
|
1147
|
+
if (progress >= 100) {
|
|
1148
|
+
transferEndedAt ?? (transferEndedAt = Date.now());
|
|
1149
|
+
}
|
|
1150
|
+
return;
|
|
1103
1151
|
}
|
|
1104
|
-
if (
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1152
|
+
if (payload.progressType === 'installingFirmware') {
|
|
1153
|
+
installProgressEvents += 1;
|
|
1154
|
+
lastInstallProgress = Math.max(lastInstallProgress, progress);
|
|
1155
|
+
installStartedAt ?? (installStartedAt = Date.now());
|
|
1156
|
+
lastPrintedInstallProgress = maybePrintFirmwareProgress({
|
|
1157
|
+
progressType: 'install',
|
|
1158
|
+
progress,
|
|
1159
|
+
payload,
|
|
1160
|
+
lastPrintedProgress: lastPrintedInstallProgress,
|
|
1161
|
+
});
|
|
1162
|
+
if (progress >= 100) {
|
|
1163
|
+
installEndedAt ?? (installEndedAt = Date.now());
|
|
1164
|
+
}
|
|
1108
1165
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1166
|
+
};
|
|
1167
|
+
currentSdk.on(hd_core_1.UI_EVENT, onUiEvent);
|
|
1168
|
+
let result;
|
|
1169
|
+
try {
|
|
1170
|
+
result = await currentSdk.firmwareUpdateV4(connectId, params);
|
|
1171
|
+
}
|
|
1172
|
+
finally {
|
|
1173
|
+
currentSdk.off?.(hd_core_1.UI_EVENT, onUiEvent);
|
|
1174
|
+
}
|
|
1175
|
+
if (installStartedAt !== undefined && installEndedAt === undefined) {
|
|
1176
|
+
installEndedAt = Date.now();
|
|
1114
1177
|
}
|
|
1115
|
-
|
|
1178
|
+
const metrics = buildFirmwareUpdateV4Metrics({
|
|
1179
|
+
attempt,
|
|
1180
|
+
maxAttempts,
|
|
1181
|
+
totalBytes,
|
|
1182
|
+
totalStartedAt,
|
|
1183
|
+
transferStartedAt,
|
|
1184
|
+
transferEndedAt,
|
|
1185
|
+
installStartedAt,
|
|
1186
|
+
installEndedAt,
|
|
1187
|
+
progressEvents,
|
|
1188
|
+
lastProgress,
|
|
1189
|
+
installProgressEvents,
|
|
1190
|
+
lastInstallProgress,
|
|
1191
|
+
retried,
|
|
1192
|
+
});
|
|
1193
|
+
if (result && typeof result === 'object') {
|
|
1194
|
+
const payload = (result.payload ?? {});
|
|
1195
|
+
return {
|
|
1196
|
+
...result,
|
|
1197
|
+
payload: {
|
|
1198
|
+
...payload,
|
|
1199
|
+
metrics,
|
|
1200
|
+
},
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
return result;
|
|
1116
1204
|
}
|
|
1117
|
-
|
|
1205
|
+
exports.runFirmwareUpdateV4WithRetry = runFirmwareUpdateV4WithRetry;
|
|
1206
|
+
function buildFirmwareUpdateV4Params(opts) {
|
|
1118
1207
|
const params = {
|
|
1119
1208
|
platform: 'desktop',
|
|
1120
1209
|
connectProtocol: 'V2',
|
|
1121
1210
|
chunkSize: opts.chunkSize ? safeParseInt(opts.chunkSize, '--chunk-size') : undefined,
|
|
1122
1211
|
forcedUpdateRes: opts.forcedUpdateRes,
|
|
1123
|
-
|
|
1212
|
+
resourceBundleFiles: opts.resourceBundle?.map(parseResourceBundleParam),
|
|
1124
1213
|
romloaderBinary: opts.romloader ? readBinaryParam(opts.romloader) : undefined,
|
|
1125
1214
|
bootloaderBinary: opts.bootloader ? readBinaryParam(opts.bootloader) : undefined,
|
|
1126
1215
|
applicationP1Binary: opts.applicationP1 ? readBinaryParam(opts.applicationP1) : undefined,
|
|
@@ -1132,7 +1221,7 @@ function buildFirmwareUpdateV4DebugParams(opts) {
|
|
|
1132
1221
|
se04Binary: opts.se04 ? readBinaryParam(opts.se04) : undefined,
|
|
1133
1222
|
};
|
|
1134
1223
|
const hasPayload = [
|
|
1135
|
-
params.
|
|
1224
|
+
params.resourceBundleFiles,
|
|
1136
1225
|
params.romloaderBinary,
|
|
1137
1226
|
params.bootloaderBinary,
|
|
1138
1227
|
params.applicationP1Binary,
|
|
@@ -1144,7 +1233,7 @@ function buildFirmwareUpdateV4DebugParams(opts) {
|
|
|
1144
1233
|
params.se04Binary,
|
|
1145
1234
|
].some(Boolean);
|
|
1146
1235
|
if (!hasPayload) {
|
|
1147
|
-
const err = new Error('firmware-update-v4
|
|
1236
|
+
const err = new Error('firmware-update-v4 requires at least one binary path');
|
|
1148
1237
|
err.code = 'MISSING_FIRMWARE_BINARY';
|
|
1149
1238
|
throw err;
|
|
1150
1239
|
}
|
|
@@ -1160,4 +1249,6 @@ function safeParseInt(input, label) {
|
|
|
1160
1249
|
}
|
|
1161
1250
|
return num;
|
|
1162
1251
|
}
|
|
1163
|
-
|
|
1252
|
+
if (require.main === module) {
|
|
1253
|
+
program.parse();
|
|
1254
|
+
}
|