@onekeyfe/hardware-cli 1.2.0-alpha.9 → 1.2.0-alpha.91
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 +80 -0
- package/dist/cli.js +320 -266
- 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/pinentry.d.ts +1 -0
- package/dist/sdk.d.ts +10 -2
- package/dist/sdk.js +17 -8
- package/dist/session.d.ts +2 -9
- package/dist/session.js +3 -22
- package/dist/transports/nobleBlePlugin.js +31 -47
- 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 +83 -1
- package/src/__tests__/noble-ble-plugin.test.ts +258 -1
- package/src/__tests__/wallet-session.test.ts +64 -0
- package/src/__tests__/wallpaper-upload-command.test.ts +46 -0
- package/src/cli.ts +403 -314
- package/src/deviceSelection.ts +13 -0
- package/src/deviceStateCommands.ts +71 -0
- package/src/pinentry.ts +1 -0
- package/src/sdk.ts +18 -10
- package/src/session.ts +2 -24
- package/src/transports/nobleBlePlugin.ts +43 -54
package/dist/cli.js
CHANGED
|
@@ -1,44 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.program = void 0;
|
|
4
|
+
exports.program = exports.runFirmwareUpdateV4WithRetry = exports.buildWallpaperUploadMetrics = exports.prepareSession = exports.getLegacyFirmwareConnectTimeout = void 0;
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
6
7
|
const commander_1 = require("commander");
|
|
7
8
|
const hd_core_1 = require("@onekeyfe/hd-core");
|
|
8
9
|
const hd_shared_1 = require("@onekeyfe/hd-shared");
|
|
9
10
|
const chains_1 = require("./chains");
|
|
11
|
+
const deviceSelection_1 = require("./deviceSelection");
|
|
12
|
+
const deviceStateCommands_1 = require("./deviceStateCommands");
|
|
10
13
|
const sdk_1 = require("./sdk");
|
|
11
14
|
const session_1 = require("./session");
|
|
12
|
-
function extractPassphraseSession(payload) {
|
|
13
|
-
if (typeof payload === 'string') {
|
|
14
|
-
return { passphraseState: payload };
|
|
15
|
-
}
|
|
16
|
-
if (!payload || typeof payload !== 'object') {
|
|
17
|
-
return {};
|
|
18
|
-
}
|
|
19
|
-
const statePayload = payload;
|
|
20
|
-
let passphraseState;
|
|
21
|
-
if (typeof statePayload.passphrase_state === 'string') {
|
|
22
|
-
passphraseState = statePayload.passphrase_state;
|
|
23
|
-
}
|
|
24
|
-
else if (typeof statePayload.passphraseState === 'string') {
|
|
25
|
-
passphraseState = statePayload.passphraseState;
|
|
26
|
-
}
|
|
27
|
-
let sessionId;
|
|
28
|
-
if (typeof statePayload.session_id === 'string') {
|
|
29
|
-
sessionId = statePayload.session_id;
|
|
30
|
-
}
|
|
31
|
-
else if (typeof statePayload.sessionId === 'string') {
|
|
32
|
-
sessionId = statePayload.sessionId;
|
|
33
|
-
}
|
|
34
|
-
return { passphraseState, sessionId };
|
|
35
|
-
}
|
|
36
15
|
const program = new commander_1.Command();
|
|
37
16
|
exports.program = program;
|
|
17
|
+
const { version: cliVersion } = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(__dirname, '../package.json'), 'utf8'));
|
|
38
18
|
program
|
|
39
19
|
.name('onekey-hw')
|
|
40
20
|
.description('OneKey hardware wallet CLI for AI agent integration')
|
|
41
|
-
.version(
|
|
21
|
+
.version(cliVersion);
|
|
42
22
|
// ============================================================
|
|
43
23
|
// Global Options
|
|
44
24
|
// ============================================================
|
|
@@ -56,50 +36,103 @@ program
|
|
|
56
36
|
.description('Search for connected OneKey hardware wallet devices')
|
|
57
37
|
.action(() => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
58
38
|
const result = await sdk.searchDevices();
|
|
59
|
-
// USB 下自动读取 features 成本低;BLE 搜索阶段只做枚举,避免批量连接导致超时。
|
|
60
|
-
if (globalOpts.transport !== 'ble' && result?.success && Array.isArray(result.payload)) {
|
|
61
|
-
for (const device of result.payload) {
|
|
62
|
-
if (device.connectId) {
|
|
63
|
-
try {
|
|
64
|
-
const features = await sdk.getFeatures(device.connectId);
|
|
65
|
-
if (features?.success && features.payload) {
|
|
66
|
-
device.features = features.payload;
|
|
67
|
-
device.name = features.payload.label || features.payload.bleName || device.name;
|
|
68
|
-
const devType = features.payload.deviceType?.toLowerCase();
|
|
69
|
-
if (devType) {
|
|
70
|
-
device.deviceType = devType;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
// Features fetch failed — device may need PIN, continue with basic info
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
39
|
outputResult(globalOpts, result);
|
|
81
40
|
}));
|
|
82
41
|
program
|
|
83
42
|
.command('get-features')
|
|
84
43
|
.description('Get device features (firmware, unlock state, passphrase protection, etc.)')
|
|
85
44
|
.action(() => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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')
|
|
97
83
|
return;
|
|
84
|
+
const event = message;
|
|
85
|
+
if (event.type !== hd_core_1.UI_REQUEST.DEVICE_PROGRESS || !event.payload)
|
|
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
98
|
}
|
|
99
|
-
|
|
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;
|
|
105
|
+
}
|
|
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
|
+
});
|
|
100
120
|
}
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
});
|
|
103
136
|
}));
|
|
104
137
|
// ============================================================
|
|
105
138
|
// Signing Commands
|
|
@@ -414,6 +447,30 @@ program
|
|
|
414
447
|
code: 'FIRMWARE_UPDATE_NOT_SUPPORTED',
|
|
415
448
|
},
|
|
416
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;
|
|
417
474
|
program
|
|
418
475
|
.command('firmware-update-ble')
|
|
419
476
|
.description('Run Protocol V2 firmware update over BLE')
|
|
@@ -428,7 +485,6 @@ program
|
|
|
428
485
|
.command('firmware-update-v4')
|
|
429
486
|
.description('Run Protocol V2 firmware update through sdk.firmwareUpdateV4')
|
|
430
487
|
.option('--chunk-size <bytes>', 'Transfer chunk size in bytes')
|
|
431
|
-
.option('--resource-bundle <spec...>', 'RESC bundle direct-write spec: <localPath>:<devicePath>, e.g. wallpaper.okpkg:vol0:/bundles/images/wallpaper.okpkg')
|
|
432
488
|
.option('--romloader <path>', 'FW_MGMT_TARGET_ROMLOADER binary path')
|
|
433
489
|
.option('--bootloader <path>', 'FW_MGMT_TARGET_BOOTLOADER binary path')
|
|
434
490
|
.option('--application-p1 <path>', 'FW_MGMT_TARGET_APPLICATION_P1 binary path')
|
|
@@ -438,6 +494,7 @@ program
|
|
|
438
494
|
.option('--se02 <path>', 'FW_MGMT_TARGET_SE02 binary path')
|
|
439
495
|
.option('--se03 <path>', 'FW_MGMT_TARGET_SE03 binary path')
|
|
440
496
|
.option('--se04 <path>', 'FW_MGMT_TARGET_SE04 binary path')
|
|
497
|
+
.option('--resource-archive <path>', 'Complete signed Protocol V2 resource ZIP path')
|
|
441
498
|
.option('--forced-update-res', 'Force resource update')
|
|
442
499
|
.option('--retries <count>', 'Retry count for transient Protocol V2 USB probe failures')
|
|
443
500
|
.action(opts => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
@@ -565,7 +622,7 @@ program
|
|
|
565
622
|
const sessionCmd = program.command('session').description('Manage device passphrase session cache');
|
|
566
623
|
sessionCmd
|
|
567
624
|
.command('connect')
|
|
568
|
-
.description('Connect device and
|
|
625
|
+
.description('Connect device and select a hidden wallet for this invocation')
|
|
569
626
|
.action(() => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
570
627
|
// 1. Search for device
|
|
571
628
|
const searchResult = await sdk.searchDevices();
|
|
@@ -576,7 +633,14 @@ sessionCmd
|
|
|
576
633
|
});
|
|
577
634
|
return;
|
|
578
635
|
}
|
|
579
|
-
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
|
+
}
|
|
580
644
|
const connectId = device.connectId || globalOpts.connectId;
|
|
581
645
|
// 2. Unlock if locked — getPassphraseState below talks to a live
|
|
582
646
|
// device session, which a locked device will reject with an obscure
|
|
@@ -585,56 +649,33 @@ sessionCmd
|
|
|
585
649
|
process.stderr.write('[onekey-hw] Device is locked. Unlocking (PIN required)...\n');
|
|
586
650
|
await unlockWithRetry(sdk, connectId);
|
|
587
651
|
}
|
|
588
|
-
// 3.
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
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',
|
|
592
655
|
});
|
|
593
|
-
if (!
|
|
594
|
-
outputResult(globalOpts,
|
|
656
|
+
if (!sessionResult.success) {
|
|
657
|
+
outputResult(globalOpts, sessionResult);
|
|
595
658
|
return;
|
|
596
659
|
}
|
|
597
|
-
|
|
598
|
-
if (!passphraseState) {
|
|
660
|
+
if (sessionResult.payload.walletType !== 'hidden') {
|
|
599
661
|
outputResult(globalOpts, {
|
|
600
662
|
success: false,
|
|
601
|
-
payload: { error: '
|
|
663
|
+
payload: { error: 'Hidden wallet selection did not return a hidden wallet session' },
|
|
602
664
|
});
|
|
603
665
|
return;
|
|
604
666
|
}
|
|
667
|
+
const { deviceId, passphraseState } = sessionResult.payload;
|
|
605
668
|
// 4. Get address to verify + extract deviceId
|
|
606
|
-
const addrResult = await sdk.evmGetAddress(connectId,
|
|
669
|
+
const addrResult = await sdk.evmGetAddress(connectId, deviceId, {
|
|
607
670
|
path: "m/44'/60'/0'/0/0",
|
|
608
671
|
showOnOneKey: false,
|
|
609
672
|
passphraseState,
|
|
610
673
|
});
|
|
611
|
-
// 5. Fetch the now-active session_id via getFeatures.
|
|
612
|
-
//
|
|
613
|
-
// IMPORTANT: pass `passphraseState` here. Without it, the SDK's
|
|
614
|
-
// connectStateChange guard (core/index.ts) would see the payload's
|
|
615
|
-
// passphraseState flip from mnNy → undefined, clear the cached Device,
|
|
616
|
-
// and call Initialize again with no passphrase_state / no session_id.
|
|
617
|
-
// That Initialize resets the device to the standard wallet and returns
|
|
618
|
-
// a *standard-wallet* session_id — which we'd then save in the keychain
|
|
619
|
-
// paired with the hidden-wallet passphraseState. On the next CLI run
|
|
620
|
-
// the mismatch would trigger PassphraseRequest (1/2/3 again).
|
|
621
|
-
const featResult = await sdk.getFeatures(connectId, {
|
|
622
|
-
passphraseState,
|
|
623
|
-
skipPassphraseCheck: true,
|
|
624
|
-
});
|
|
625
|
-
const featPayload = featResult?.success ? featResult.payload : undefined;
|
|
626
|
-
const deviceId = featPayload?.deviceId || device.deviceId || '';
|
|
627
|
-
const sessionId = passphraseSessionId || featPayload?.sessionId || '';
|
|
628
|
-
// 6. Save to keychain
|
|
629
|
-
if (passphraseState && deviceId && sessionId) {
|
|
630
|
-
await (0, session_1.saveSessionToKeychain)(deviceId, passphraseState, sessionId);
|
|
631
|
-
}
|
|
632
674
|
outputResult(globalOpts, {
|
|
633
675
|
success: true,
|
|
634
676
|
payload: {
|
|
635
677
|
passphraseState,
|
|
636
678
|
deviceId,
|
|
637
|
-
...(sessionId ? { sessionId } : {}),
|
|
638
679
|
...(addrResult?.success ? { address: addrResult.payload.address } : {}),
|
|
639
680
|
},
|
|
640
681
|
});
|
|
@@ -644,8 +685,7 @@ sessionCmd
|
|
|
644
685
|
.description('Clear cached device session')
|
|
645
686
|
.action(() => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
646
687
|
const searchResult = await sdk.searchDevices();
|
|
647
|
-
const device =
|
|
648
|
-
searchResult?.payload?.[0];
|
|
688
|
+
const device = (0, deviceSelection_1.selectSearchDevice)(searchResult?.payload ?? [], globalOpts.connectId);
|
|
649
689
|
const deviceId = device?.deviceId || device?.features?.device_id;
|
|
650
690
|
if (deviceId) {
|
|
651
691
|
await (0, session_1.clearSessionFromKeychain)(deviceId);
|
|
@@ -722,8 +762,8 @@ async function unlockWithRetry(sdk, connectId, maxAttempts = 3) {
|
|
|
722
762
|
* Prepare passphrase session before SDK calls.
|
|
723
763
|
*
|
|
724
764
|
* 1. If --use-empty-passphrase or --passphrase-state provided → use as-is
|
|
725
|
-
* 2. Try keychain → preloadSessionCache → use cached session
|
|
726
|
-
* 3. Keychain miss →
|
|
765
|
+
* 2. Try a legacy keychain entry → preloadSessionCache → use cached session
|
|
766
|
+
* 3. Keychain miss → openWalletSession (triggers 1/2/3 prompt)
|
|
727
767
|
*
|
|
728
768
|
* After this, globalOpts.passphraseState is set and getCommonParams will include it.
|
|
729
769
|
*/
|
|
@@ -735,7 +775,7 @@ globalOpts) {
|
|
|
735
775
|
return globalOpts.passphraseState;
|
|
736
776
|
}
|
|
737
777
|
// Errors from the SDK calls below (PIN cancelled, transport broken,
|
|
738
|
-
//
|
|
778
|
+
// openWalletSession rejection) intentionally propagate to runCommand's
|
|
739
779
|
// catch block, which renders them as structured `{ success: false,
|
|
740
780
|
// payload: { error, code } }` output instead of silently falling through
|
|
741
781
|
// to a confusing downstream error 112 / 114.
|
|
@@ -746,18 +786,22 @@ globalOpts) {
|
|
|
746
786
|
searchResult.payload.length === 0) {
|
|
747
787
|
return undefined;
|
|
748
788
|
}
|
|
749
|
-
const device = searchResult.payload
|
|
750
|
-
|
|
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 || '';
|
|
751
795
|
if (!globalOpts.connectId && connectId) {
|
|
752
796
|
globalOpts.connectId = connectId;
|
|
753
797
|
}
|
|
754
798
|
// ── Step 2: Get features if searchDevices didn't populate them ──
|
|
755
799
|
// getFeatures failures here are non-fatal — we fall through to Step 3
|
|
756
800
|
// which will fail with a clearer error if the device is truly unreachable.
|
|
757
|
-
let deviceId =
|
|
758
|
-
let deviceType =
|
|
759
|
-
let unlocked =
|
|
760
|
-
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;
|
|
761
805
|
if (!deviceId || unlocked == null || passphraseProtection == null) {
|
|
762
806
|
try {
|
|
763
807
|
const featResult = await sdk.getFeatures(connectId);
|
|
@@ -792,7 +836,7 @@ globalOpts) {
|
|
|
792
836
|
if (passphraseProtection === false && deviceType !== hd_shared_1.EDeviceType.Pro2) {
|
|
793
837
|
return undefined;
|
|
794
838
|
}
|
|
795
|
-
// ── Step 5: Try keychain session reuse
|
|
839
|
+
// ── Step 5: Try legacy keychain session reuse ────────────────────
|
|
796
840
|
// Only attempt if device was already unlocked — locking invalidates
|
|
797
841
|
// all passphrase sessions, so cached session_id is useless after unlock.
|
|
798
842
|
if (!wasLocked && deviceId) {
|
|
@@ -802,38 +846,22 @@ globalOpts) {
|
|
|
802
846
|
return cached;
|
|
803
847
|
}
|
|
804
848
|
}
|
|
805
|
-
// ── Step 6: Keychain miss →
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
useEmptyPassphrase: false,
|
|
849
|
+
// ── Step 6: Keychain miss → openWalletSession (triggers 1/2/3 prompt) ──
|
|
850
|
+
const sessionResult = await sdk.openWalletSession(connectId, {
|
|
851
|
+
mode: 'select-hidden',
|
|
809
852
|
});
|
|
810
|
-
if (
|
|
811
|
-
|
|
812
|
-
if (!passphraseState) {
|
|
853
|
+
if (sessionResult.success && sessionResult.payload) {
|
|
854
|
+
if (sessionResult.payload.walletType !== 'hidden') {
|
|
813
855
|
return undefined;
|
|
814
856
|
}
|
|
857
|
+
const { deviceId: sessionDeviceId, passphraseState } = sessionResult.payload;
|
|
858
|
+
globalOpts.deviceId = sessionDeviceId;
|
|
815
859
|
globalOpts.passphraseState = passphraseState;
|
|
816
|
-
// Save session to keychain for next invocation.
|
|
817
|
-
//
|
|
818
|
-
// Pass passphraseState to keep connectStateChange=false — otherwise
|
|
819
|
-
// Initialize would be re-run without passphrase_state, resetting the
|
|
820
|
-
// device to the standard wallet and returning a mismatched session_id.
|
|
821
|
-
// See the matching comment in `session connect`.
|
|
822
|
-
if (deviceId) {
|
|
823
|
-
const featAfter = await sdk.getFeatures(connectId, {
|
|
824
|
-
passphraseState,
|
|
825
|
-
skipPassphraseCheck: true,
|
|
826
|
-
});
|
|
827
|
-
const sessionId = passphraseSessionId || (featAfter?.success ? featAfter.payload?.sessionId : undefined);
|
|
828
|
-
if (sessionId) {
|
|
829
|
-
await (0, session_1.saveSessionToKeychain)(deviceId, passphraseState, sessionId);
|
|
830
|
-
await (0, session_1.preloadSessionFromKeychain)(deviceId);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
860
|
return passphraseState;
|
|
834
861
|
}
|
|
835
862
|
return undefined;
|
|
836
863
|
}
|
|
864
|
+
exports.prepareSession = prepareSession;
|
|
837
865
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
838
866
|
function outputResult(_globalOpts, result) {
|
|
839
867
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -843,8 +871,8 @@ function outputResult(_globalOpts, result) {
|
|
|
843
871
|
!result.success) {
|
|
844
872
|
process.exitCode = 1;
|
|
845
873
|
}
|
|
846
|
-
// No process.exit here — runCommand()
|
|
847
|
-
//
|
|
874
|
+
// No process.exit here — runCommand() waits for SDK cleanup, then lets Node
|
|
875
|
+
// exit naturally so leaked USB handles remain observable.
|
|
848
876
|
}
|
|
849
877
|
async function runCommand(options, handler) {
|
|
850
878
|
const globalOpts = program.opts();
|
|
@@ -877,9 +905,7 @@ async function runCommand(options, handler) {
|
|
|
877
905
|
// promise reference. Idempotent, safe to call even if init failed.
|
|
878
906
|
await (0, sdk_1.disposeSDK)();
|
|
879
907
|
}
|
|
880
|
-
//
|
|
881
|
-
// setImmediate lets any trailing stdout/stderr writes flush first.
|
|
882
|
-
setImmediate(() => process.exit(process.exitCode ?? 0));
|
|
908
|
+
// disposeSDK awaits transport cleanup; let Node exit naturally so leaks remain visible.
|
|
883
909
|
}
|
|
884
910
|
/** For commands that don't touch the SDK at all (e.g. firmware-update stubs). */
|
|
885
911
|
function respondAndExit(result) {
|
|
@@ -904,24 +930,35 @@ function readBinaryParam(path) {
|
|
|
904
930
|
const buffer = (0, node_fs_1.readFileSync)(path);
|
|
905
931
|
return new Uint8Array(buffer).buffer;
|
|
906
932
|
}
|
|
907
|
-
function
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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');
|
|
911
939
|
}
|
|
912
|
-
const
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
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');
|
|
916
949
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
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;
|
|
921
959
|
}
|
|
922
960
|
function getFirmwareUpdateV4TotalBytes(params) {
|
|
923
961
|
return [
|
|
924
|
-
...(params.resourceBundleFiles?.map(item => item.binary) ?? []),
|
|
925
962
|
params.bootloaderBinary,
|
|
926
963
|
params.applicationP1Binary,
|
|
927
964
|
params.applicationP2Binary,
|
|
@@ -965,6 +1002,17 @@ function formatFirmwareBytes(bytes) {
|
|
|
965
1002
|
return '';
|
|
966
1003
|
return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
967
1004
|
}
|
|
1005
|
+
function buildWallpaperUploadMetrics({ totalBytes, transferredBytes, startedAt, endedAt, lastProgress, }) {
|
|
1006
|
+
const elapsedMs = Math.max(endedAt - startedAt, 0);
|
|
1007
|
+
return {
|
|
1008
|
+
totalBytes,
|
|
1009
|
+
transferredBytes,
|
|
1010
|
+
totalSeconds: Number((elapsedMs / 1000).toFixed(2)),
|
|
1011
|
+
transferKiBPerSecond: elapsedMs > 0 ? Number((transferredBytes / 1024 / (elapsedMs / 1000)).toFixed(2)) : null,
|
|
1012
|
+
lastProgress,
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
exports.buildWallpaperUploadMetrics = buildWallpaperUploadMetrics;
|
|
968
1016
|
function maybePrintFirmwareProgress({ progressType, progress, payload, lastPrintedProgress, }) {
|
|
969
1017
|
const printableProgress = Math.floor(progress / 10) * 10;
|
|
970
1018
|
if (printableProgress <= lastPrintedProgress && progress < 100) {
|
|
@@ -1011,135 +1059,140 @@ async function runFirmwareUpdateV4WithRetry({ sdk, globalOpts, params, retries,
|
|
|
1011
1059
|
const totalBytes = getFirmwareUpdateV4TotalBytes(params);
|
|
1012
1060
|
const maxAttempts = Math.max((retries ?? 2) + 1, 1);
|
|
1013
1061
|
let currentSdk = sdk;
|
|
1014
|
-
let lastResult;
|
|
1015
1062
|
let retried = false;
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
? undefined
|
|
1030
|
-
: globalOpts.connectId;
|
|
1031
|
-
const onUiEvent = (message) => {
|
|
1032
|
-
if (!message || typeof message !== 'object')
|
|
1033
|
-
return;
|
|
1034
|
-
const messageType = message.type;
|
|
1035
|
-
const payload = getFirmwareUpdatePayload(message);
|
|
1036
|
-
if (messageType === hd_core_1.UI_REQUEST.FIRMWARE_TIP) {
|
|
1037
|
-
const tipMessage = payload?.data?.message;
|
|
1038
|
-
if (typeof tipMessage === 'string') {
|
|
1039
|
-
process.stderr.write(`[onekey-hw] Firmware: ${tipMessage}\n`);
|
|
1040
|
-
}
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
if (messageType === hd_core_1.UI_REQUEST.REQUEST_BUTTON) {
|
|
1044
|
-
const code = typeof payload?.code === 'string' ? ` (${payload.code})` : '';
|
|
1045
|
-
process.stderr.write(`[onekey-hw] Please confirm the firmware update on your device${code}.\n`);
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
if (messageType !== hd_core_1.UI_REQUEST.FIRMWARE_PROGRESS || !payload)
|
|
1049
|
-
return;
|
|
1050
|
-
const progress = Number(payload.progress);
|
|
1051
|
-
if (!Number.isFinite(progress))
|
|
1052
|
-
return;
|
|
1053
|
-
if (payload.progressType === 'transferData') {
|
|
1054
|
-
progressEvents += 1;
|
|
1055
|
-
lastProgress = Math.max(lastProgress, progress);
|
|
1056
|
-
transferStartedAt ?? (transferStartedAt = Date.now());
|
|
1057
|
-
lastPrintedTransferProgress = maybePrintFirmwareProgress({
|
|
1058
|
-
progressType: 'transfer',
|
|
1059
|
-
progress,
|
|
1060
|
-
payload,
|
|
1061
|
-
lastPrintedProgress: lastPrintedTransferProgress,
|
|
1062
|
-
});
|
|
1063
|
-
if (progress >= 100) {
|
|
1064
|
-
transferEndedAt ?? (transferEndedAt = Date.now());
|
|
1065
|
-
}
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
if (payload.progressType === 'installingFirmware') {
|
|
1069
|
-
installProgressEvents += 1;
|
|
1070
|
-
lastInstallProgress = Math.max(lastInstallProgress, progress);
|
|
1071
|
-
installStartedAt ?? (installStartedAt = Date.now());
|
|
1072
|
-
lastPrintedInstallProgress = maybePrintFirmwareProgress({
|
|
1073
|
-
progressType: 'install',
|
|
1074
|
-
progress,
|
|
1075
|
-
payload,
|
|
1076
|
-
lastPrintedProgress: lastPrintedInstallProgress,
|
|
1077
|
-
});
|
|
1078
|
-
if (progress >= 100) {
|
|
1079
|
-
installEndedAt ?? (installEndedAt = Date.now());
|
|
1080
|
-
}
|
|
1063
|
+
let attempt = 1;
|
|
1064
|
+
let { connectId } = globalOpts;
|
|
1065
|
+
if (globalOpts.transport === 'usb') {
|
|
1066
|
+
for (; attempt <= maxAttempts; attempt += 1) {
|
|
1067
|
+
const probeResult = await currentSdk.getDeviceState(connectId, {
|
|
1068
|
+
scope: 'runtime',
|
|
1069
|
+
connectProtocol: 'V2',
|
|
1070
|
+
retryCount: 0,
|
|
1071
|
+
});
|
|
1072
|
+
if (isSuccessResult(probeResult))
|
|
1073
|
+
break;
|
|
1074
|
+
if (attempt >= maxAttempts || !isProtocolV2UsbProbeTransientResult(probeResult)) {
|
|
1075
|
+
return probeResult;
|
|
1081
1076
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
currentSdk
|
|
1077
|
+
retried = true;
|
|
1078
|
+
process.stderr.write(`[onekey-hw] Protocol V2 USB probe was transient; retrying read-only probe (${attempt}/${maxAttempts})...\n`);
|
|
1079
|
+
await (0, sdk_1.disposeSDK)();
|
|
1080
|
+
await new Promise(resolve => {
|
|
1081
|
+
setTimeout(resolve, 3000);
|
|
1082
|
+
});
|
|
1083
|
+
currentSdk = await (0, sdk_1.createSDK)(globalOpts);
|
|
1084
|
+
if (globalOpts.connectId)
|
|
1085
|
+
connectId = undefined;
|
|
1089
1086
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1087
|
+
}
|
|
1088
|
+
let progressEvents = 0;
|
|
1089
|
+
let lastProgress = -1;
|
|
1090
|
+
let transferStartedAt;
|
|
1091
|
+
let transferEndedAt;
|
|
1092
|
+
let installProgressEvents = 0;
|
|
1093
|
+
let lastInstallProgress = -1;
|
|
1094
|
+
let installStartedAt;
|
|
1095
|
+
let installEndedAt;
|
|
1096
|
+
let lastPrintedTransferProgress = -10;
|
|
1097
|
+
let lastPrintedInstallProgress = -10;
|
|
1098
|
+
const totalStartedAt = Date.now();
|
|
1099
|
+
const onUiEvent = (message) => {
|
|
1100
|
+
if (!message || typeof message !== 'object')
|
|
1101
|
+
return;
|
|
1102
|
+
const messageType = message.type;
|
|
1103
|
+
const payload = getFirmwareUpdatePayload(message);
|
|
1104
|
+
if (messageType === hd_core_1.UI_REQUEST.FIRMWARE_TIP) {
|
|
1105
|
+
const tipMessage = payload?.data?.message;
|
|
1106
|
+
if (typeof tipMessage === 'string') {
|
|
1107
|
+
process.stderr.write(`[onekey-hw] Firmware: ${tipMessage}\n`);
|
|
1108
|
+
}
|
|
1109
|
+
return;
|
|
1092
1110
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
totalStartedAt,
|
|
1098
|
-
transferStartedAt,
|
|
1099
|
-
transferEndedAt,
|
|
1100
|
-
installStartedAt,
|
|
1101
|
-
installEndedAt,
|
|
1102
|
-
progressEvents,
|
|
1103
|
-
lastProgress,
|
|
1104
|
-
installProgressEvents,
|
|
1105
|
-
lastInstallProgress,
|
|
1106
|
-
retried,
|
|
1107
|
-
});
|
|
1108
|
-
if (lastResult && typeof lastResult === 'object') {
|
|
1109
|
-
const payload = (lastResult.payload ?? {});
|
|
1110
|
-
lastResult = {
|
|
1111
|
-
...lastResult,
|
|
1112
|
-
payload: {
|
|
1113
|
-
...payload,
|
|
1114
|
-
metrics,
|
|
1115
|
-
},
|
|
1116
|
-
};
|
|
1111
|
+
if (messageType === hd_core_1.UI_REQUEST.REQUEST_BUTTON) {
|
|
1112
|
+
const code = typeof payload?.code === 'string' ? ` (${payload.code})` : '';
|
|
1113
|
+
process.stderr.write(`[onekey-hw] Please confirm the firmware update on your device${code}.\n`);
|
|
1114
|
+
return;
|
|
1117
1115
|
}
|
|
1118
|
-
if (
|
|
1119
|
-
return
|
|
1116
|
+
if (messageType !== hd_core_1.UI_REQUEST.FIRMWARE_PROGRESS || !payload)
|
|
1117
|
+
return;
|
|
1118
|
+
const progress = Number(payload.progress);
|
|
1119
|
+
if (!Number.isFinite(progress))
|
|
1120
|
+
return;
|
|
1121
|
+
if (payload.progressType === 'transferData') {
|
|
1122
|
+
progressEvents += 1;
|
|
1123
|
+
lastProgress = Math.max(lastProgress, progress);
|
|
1124
|
+
transferStartedAt ?? (transferStartedAt = Date.now());
|
|
1125
|
+
lastPrintedTransferProgress = maybePrintFirmwareProgress({
|
|
1126
|
+
progressType: 'transfer',
|
|
1127
|
+
progress,
|
|
1128
|
+
payload,
|
|
1129
|
+
lastPrintedProgress: lastPrintedTransferProgress,
|
|
1130
|
+
});
|
|
1131
|
+
if (progress >= 100) {
|
|
1132
|
+
transferEndedAt ?? (transferEndedAt = Date.now());
|
|
1133
|
+
}
|
|
1134
|
+
return;
|
|
1120
1135
|
}
|
|
1121
|
-
if (
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1136
|
+
if (payload.progressType === 'installingFirmware') {
|
|
1137
|
+
installProgressEvents += 1;
|
|
1138
|
+
lastInstallProgress = Math.max(lastInstallProgress, progress);
|
|
1139
|
+
installStartedAt ?? (installStartedAt = Date.now());
|
|
1140
|
+
lastPrintedInstallProgress = maybePrintFirmwareProgress({
|
|
1141
|
+
progressType: 'install',
|
|
1142
|
+
progress,
|
|
1143
|
+
payload,
|
|
1144
|
+
lastPrintedProgress: lastPrintedInstallProgress,
|
|
1145
|
+
});
|
|
1146
|
+
if (progress >= 100) {
|
|
1147
|
+
installEndedAt ?? (installEndedAt = Date.now());
|
|
1148
|
+
}
|
|
1125
1149
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1150
|
+
};
|
|
1151
|
+
currentSdk.on(hd_core_1.UI_EVENT, onUiEvent);
|
|
1152
|
+
let result;
|
|
1153
|
+
try {
|
|
1154
|
+
result = await currentSdk.firmwareUpdateV4(connectId, params);
|
|
1155
|
+
}
|
|
1156
|
+
finally {
|
|
1157
|
+
currentSdk.off?.(hd_core_1.UI_EVENT, onUiEvent);
|
|
1158
|
+
}
|
|
1159
|
+
if (installStartedAt !== undefined && installEndedAt === undefined) {
|
|
1160
|
+
installEndedAt = Date.now();
|
|
1161
|
+
}
|
|
1162
|
+
const metrics = buildFirmwareUpdateV4Metrics({
|
|
1163
|
+
attempt,
|
|
1164
|
+
maxAttempts,
|
|
1165
|
+
totalBytes,
|
|
1166
|
+
totalStartedAt,
|
|
1167
|
+
transferStartedAt,
|
|
1168
|
+
transferEndedAt,
|
|
1169
|
+
installStartedAt,
|
|
1170
|
+
installEndedAt,
|
|
1171
|
+
progressEvents,
|
|
1172
|
+
lastProgress,
|
|
1173
|
+
installProgressEvents,
|
|
1174
|
+
lastInstallProgress,
|
|
1175
|
+
retried,
|
|
1176
|
+
});
|
|
1177
|
+
if (result && typeof result === 'object') {
|
|
1178
|
+
const payload = (result.payload ?? {});
|
|
1179
|
+
return {
|
|
1180
|
+
...result,
|
|
1181
|
+
payload: {
|
|
1182
|
+
...payload,
|
|
1183
|
+
metrics,
|
|
1184
|
+
},
|
|
1185
|
+
};
|
|
1133
1186
|
}
|
|
1134
|
-
return
|
|
1187
|
+
return result;
|
|
1135
1188
|
}
|
|
1189
|
+
exports.runFirmwareUpdateV4WithRetry = runFirmwareUpdateV4WithRetry;
|
|
1136
1190
|
function buildFirmwareUpdateV4Params(opts) {
|
|
1137
1191
|
const params = {
|
|
1138
1192
|
platform: 'desktop',
|
|
1139
1193
|
connectProtocol: 'V2',
|
|
1140
1194
|
chunkSize: opts.chunkSize ? safeParseInt(opts.chunkSize, '--chunk-size') : undefined,
|
|
1141
1195
|
forcedUpdateRes: opts.forcedUpdateRes,
|
|
1142
|
-
resourceBundleFiles: opts.resourceBundle?.map(parseResourceBundleParam),
|
|
1143
1196
|
romloaderBinary: opts.romloader ? readBinaryParam(opts.romloader) : undefined,
|
|
1144
1197
|
bootloaderBinary: opts.bootloader ? readBinaryParam(opts.bootloader) : undefined,
|
|
1145
1198
|
applicationP1Binary: opts.applicationP1 ? readBinaryParam(opts.applicationP1) : undefined,
|
|
@@ -1149,9 +1202,9 @@ function buildFirmwareUpdateV4Params(opts) {
|
|
|
1149
1202
|
se02Binary: opts.se02 ? readBinaryParam(opts.se02) : undefined,
|
|
1150
1203
|
se03Binary: opts.se03 ? readBinaryParam(opts.se03) : undefined,
|
|
1151
1204
|
se04Binary: opts.se04 ? readBinaryParam(opts.se04) : undefined,
|
|
1205
|
+
resourceArchiveBinary: opts.resourceArchive ? readBinaryParam(opts.resourceArchive) : undefined,
|
|
1152
1206
|
};
|
|
1153
1207
|
const hasPayload = [
|
|
1154
|
-
params.resourceBundleFiles,
|
|
1155
1208
|
params.romloaderBinary,
|
|
1156
1209
|
params.bootloaderBinary,
|
|
1157
1210
|
params.applicationP1Binary,
|
|
@@ -1161,9 +1214,10 @@ function buildFirmwareUpdateV4Params(opts) {
|
|
|
1161
1214
|
params.se02Binary,
|
|
1162
1215
|
params.se03Binary,
|
|
1163
1216
|
params.se04Binary,
|
|
1217
|
+
params.resourceArchiveBinary,
|
|
1164
1218
|
].some(Boolean);
|
|
1165
1219
|
if (!hasPayload) {
|
|
1166
|
-
const err = new Error('firmware-update-v4 requires at least one binary path');
|
|
1220
|
+
const err = new Error('firmware-update-v4 requires at least one firmware binary or resource archive path');
|
|
1167
1221
|
err.code = 'MISSING_FIRMWARE_BINARY';
|
|
1168
1222
|
throw err;
|
|
1169
1223
|
}
|