@onekeyfe/hardware-cli 1.2.0-alpha.0 → 1.2.0-alpha.10
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 +3 -1
- package/dist/cli.js +325 -20
- package/dist/sdk.d.ts +2 -0
- package/dist/sdk.js +5 -3
- package/dist/transports/nobleBlePlugin.d.ts +2 -0
- package/dist/transports/nobleBlePlugin.js +387 -0
- package/package.json +7 -6
- package/src/__tests__/firmware-update-v4-command.test.ts +19 -0
- package/src/__tests__/noble-ble-plugin.test.ts +113 -0
- package/src/cli.ts +454 -29
- package/src/sdk.ts +11 -3
- package/src/transports/nobleBlePlugin.ts +498 -0
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.program = void 0;
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
4
6
|
const commander_1 = require("commander");
|
|
7
|
+
const hd_core_1 = require("@onekeyfe/hd-core");
|
|
8
|
+
const hd_shared_1 = require("@onekeyfe/hd-shared");
|
|
9
|
+
const chains_1 = require("./chains");
|
|
5
10
|
const sdk_1 = require("./sdk");
|
|
6
11
|
const session_1 = require("./session");
|
|
7
|
-
const chains_1 = require("./chains");
|
|
8
|
-
const hd_shared_1 = require("@onekeyfe/hd-shared");
|
|
9
|
-
const hd_core_1 = require("@onekeyfe/hd-core");
|
|
10
12
|
function extractPassphraseSession(payload) {
|
|
11
13
|
if (typeof payload === 'string') {
|
|
12
14
|
return { passphraseState: payload };
|
|
@@ -32,6 +34,7 @@ function extractPassphraseSession(payload) {
|
|
|
32
34
|
return { passphraseState, sessionId };
|
|
33
35
|
}
|
|
34
36
|
const program = new commander_1.Command();
|
|
37
|
+
exports.program = program;
|
|
35
38
|
program
|
|
36
39
|
.name('onekey-hw')
|
|
37
40
|
.description('OneKey hardware wallet CLI for AI agent integration')
|
|
@@ -41,8 +44,10 @@ program
|
|
|
41
44
|
// ============================================================
|
|
42
45
|
program.option('--connect-id <id>', 'Device connection ID (USB: serial, iOS: uuid, Android: MAC)');
|
|
43
46
|
program.option('--device-id <id>', 'Persistent device ID from getFeatures (changes when seed changes)');
|
|
47
|
+
program.option('--transport <transport>', 'Transport to use: usb or ble', 'usb');
|
|
44
48
|
program.option('--passphrase-state <state>', 'Passphrase state for hidden wallet access');
|
|
45
49
|
program.option('--use-empty-passphrase', 'Use standard wallet (skip passphrase prompt)');
|
|
50
|
+
program.option('--debug', 'Enable SDK debug logs');
|
|
46
51
|
// ============================================================
|
|
47
52
|
// Device Commands
|
|
48
53
|
// ============================================================
|
|
@@ -51,16 +56,16 @@ program
|
|
|
51
56
|
.description('Search for connected OneKey hardware wallet devices')
|
|
52
57
|
.action(() => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
53
58
|
const result = await sdk.searchDevices();
|
|
54
|
-
//
|
|
55
|
-
if (result?.success && Array.isArray(result.payload)) {
|
|
59
|
+
// USB 下自动读取 features 成本低;BLE 搜索阶段只做枚举,避免批量连接导致超时。
|
|
60
|
+
if (globalOpts.transport !== 'ble' && result?.success && Array.isArray(result.payload)) {
|
|
56
61
|
for (const device of result.payload) {
|
|
57
62
|
if (device.connectId) {
|
|
58
63
|
try {
|
|
59
64
|
const features = await sdk.getFeatures(device.connectId);
|
|
60
65
|
if (features?.success && features.payload) {
|
|
61
66
|
device.features = features.payload;
|
|
62
|
-
device.name = features.payload.label || features.payload.
|
|
63
|
-
const devType = features.payload.
|
|
67
|
+
device.name = features.payload.label || features.payload.bleName || device.name;
|
|
68
|
+
const devType = features.payload.deviceType?.toLowerCase();
|
|
64
69
|
if (devType) {
|
|
65
70
|
device.deviceType = devType;
|
|
66
71
|
}
|
|
@@ -411,14 +416,40 @@ program
|
|
|
411
416
|
}));
|
|
412
417
|
program
|
|
413
418
|
.command('firmware-update-ble')
|
|
414
|
-
.description('
|
|
419
|
+
.description('Run Protocol V2 firmware update over BLE')
|
|
415
420
|
.action(() => respondAndExit({
|
|
416
421
|
success: false,
|
|
417
422
|
payload: {
|
|
418
|
-
error: '
|
|
419
|
-
code: '
|
|
423
|
+
error: 'Use `onekey-hw --transport ble firmware-update-v4` for BLE Protocol V2 firmware updates.',
|
|
424
|
+
code: 'USE_FIRMWARE_UPDATE_V4',
|
|
420
425
|
},
|
|
421
426
|
}));
|
|
427
|
+
program
|
|
428
|
+
.command('firmware-update-v4')
|
|
429
|
+
.description('Run Protocol V2 firmware update through sdk.firmwareUpdateV4')
|
|
430
|
+
.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
|
+
.option('--romloader <path>', 'FW_MGMT_TARGET_ROMLOADER binary path')
|
|
433
|
+
.option('--bootloader <path>', 'FW_MGMT_TARGET_BOOTLOADER binary path')
|
|
434
|
+
.option('--application-p1 <path>', 'FW_MGMT_TARGET_APPLICATION_P1 binary path')
|
|
435
|
+
.option('--application-p2 <path>', 'FW_MGMT_TARGET_APPLICATION_P2 binary path')
|
|
436
|
+
.option('--coprocessor <path>', 'FW_MGMT_TARGET_COPROCESSOR binary path')
|
|
437
|
+
.option('--se01 <path>', 'FW_MGMT_TARGET_SE01 binary path')
|
|
438
|
+
.option('--se02 <path>', 'FW_MGMT_TARGET_SE02 binary path')
|
|
439
|
+
.option('--se03 <path>', 'FW_MGMT_TARGET_SE03 binary path')
|
|
440
|
+
.option('--se04 <path>', 'FW_MGMT_TARGET_SE04 binary path')
|
|
441
|
+
.option('--forced-update-res', 'Force resource update')
|
|
442
|
+
.option('--retries <count>', 'Retry count for transient Protocol V2 USB probe failures')
|
|
443
|
+
.action(opts => runCommand({}, async ({ sdk, globalOpts }) => {
|
|
444
|
+
const params = buildFirmwareUpdateV4Params(opts);
|
|
445
|
+
const result = await runFirmwareUpdateV4WithRetry({
|
|
446
|
+
sdk,
|
|
447
|
+
globalOpts,
|
|
448
|
+
params,
|
|
449
|
+
retries: opts.retries ? safeParseInt(opts.retries, '--retries') : undefined,
|
|
450
|
+
});
|
|
451
|
+
outputResult(globalOpts, result);
|
|
452
|
+
}));
|
|
422
453
|
program
|
|
423
454
|
.command('bootloader-check')
|
|
424
455
|
.description('Check bootloader version and status')
|
|
@@ -592,8 +623,8 @@ sessionCmd
|
|
|
592
623
|
skipPassphraseCheck: true,
|
|
593
624
|
});
|
|
594
625
|
const featPayload = featResult?.success ? featResult.payload : undefined;
|
|
595
|
-
const deviceId = featPayload?.
|
|
596
|
-
const sessionId = passphraseSessionId || featPayload?.
|
|
626
|
+
const deviceId = featPayload?.deviceId || device.deviceId || '';
|
|
627
|
+
const sessionId = passphraseSessionId || featPayload?.sessionId || '';
|
|
597
628
|
// 6. Save to keychain
|
|
598
629
|
if (passphraseState && deviceId && sessionId) {
|
|
599
630
|
await (0, session_1.saveSessionToKeychain)(deviceId, passphraseState, sessionId);
|
|
@@ -723,18 +754,18 @@ globalOpts) {
|
|
|
723
754
|
// ── Step 2: Get features if searchDevices didn't populate them ──
|
|
724
755
|
// getFeatures failures here are non-fatal — we fall through to Step 3
|
|
725
756
|
// which will fail with a clearer error if the device is truly unreachable.
|
|
726
|
-
let deviceId = device.features?.
|
|
757
|
+
let deviceId = device.features?.deviceId || device.deviceId || '';
|
|
727
758
|
let deviceType = (0, hd_core_1.getDeviceType)(device.features);
|
|
728
759
|
let unlocked = device.features?.unlocked;
|
|
729
|
-
let passphraseProtection = device.features?.
|
|
760
|
+
let passphraseProtection = device.features?.passphraseProtection;
|
|
730
761
|
if (!deviceId || unlocked == null || passphraseProtection == null) {
|
|
731
762
|
try {
|
|
732
763
|
const featResult = await sdk.getFeatures(connectId);
|
|
733
764
|
if (featResult?.success && featResult.payload) {
|
|
734
|
-
deviceId = featResult.payload.
|
|
765
|
+
deviceId = featResult.payload.deviceId || deviceId;
|
|
735
766
|
deviceType = (0, hd_core_1.getDeviceType)(featResult.payload) || deviceType;
|
|
736
767
|
unlocked = featResult.payload.unlocked;
|
|
737
|
-
passphraseProtection = featResult.payload.
|
|
768
|
+
passphraseProtection = featResult.payload.passphraseProtection;
|
|
738
769
|
}
|
|
739
770
|
}
|
|
740
771
|
catch {
|
|
@@ -750,9 +781,9 @@ globalOpts) {
|
|
|
750
781
|
if (wasLocked) {
|
|
751
782
|
process.stderr.write('[onekey-hw] Device is locked. Unlocking (PIN required)...\n');
|
|
752
783
|
const { payload: feat } = await unlockWithRetry(sdk, connectId);
|
|
753
|
-
deviceId = feat.
|
|
784
|
+
deviceId = feat.deviceId || deviceId;
|
|
754
785
|
unlocked = feat.unlocked;
|
|
755
|
-
passphraseProtection = feat.
|
|
786
|
+
passphraseProtection = feat.passphraseProtection;
|
|
756
787
|
}
|
|
757
788
|
if (!globalOpts.deviceId && deviceId) {
|
|
758
789
|
globalOpts.deviceId = deviceId;
|
|
@@ -793,7 +824,7 @@ globalOpts) {
|
|
|
793
824
|
passphraseState,
|
|
794
825
|
skipPassphraseCheck: true,
|
|
795
826
|
});
|
|
796
|
-
const sessionId = passphraseSessionId || (featAfter?.success ? featAfter.payload?.
|
|
827
|
+
const sessionId = passphraseSessionId || (featAfter?.success ? featAfter.payload?.sessionId : undefined);
|
|
797
828
|
if (sessionId) {
|
|
798
829
|
await (0, session_1.saveSessionToKeychain)(deviceId, passphraseState, sessionId);
|
|
799
830
|
await (0, session_1.preloadSessionFromKeychain)(deviceId);
|
|
@@ -818,6 +849,9 @@ function outputResult(_globalOpts, result) {
|
|
|
818
849
|
async function runCommand(options, handler) {
|
|
819
850
|
const globalOpts = program.opts();
|
|
820
851
|
try {
|
|
852
|
+
if (globalOpts.transport !== 'usb' && globalOpts.transport !== 'ble') {
|
|
853
|
+
throw new Error(`Unsupported transport: ${globalOpts.transport}. Use "usb" or "ble".`);
|
|
854
|
+
}
|
|
821
855
|
const sdk = await (0, sdk_1.createSDK)(globalOpts);
|
|
822
856
|
if (options.needsSession) {
|
|
823
857
|
await prepareSession(sdk, globalOpts);
|
|
@@ -866,6 +900,275 @@ function safeJsonParse(input, label) {
|
|
|
866
900
|
throw err;
|
|
867
901
|
}
|
|
868
902
|
}
|
|
903
|
+
function readBinaryParam(path) {
|
|
904
|
+
const buffer = (0, node_fs_1.readFileSync)(path);
|
|
905
|
+
return new Uint8Array(buffer).buffer;
|
|
906
|
+
}
|
|
907
|
+
function parseResourceBundleParam(spec) {
|
|
908
|
+
const sep = spec.indexOf(':');
|
|
909
|
+
if (sep <= 0 || sep === spec.length - 1) {
|
|
910
|
+
throw new Error(`Invalid --resource-bundle value: "${spec}". Expected <localPath>:<devicePath>`);
|
|
911
|
+
}
|
|
912
|
+
const localPath = spec.slice(0, sep);
|
|
913
|
+
const devicePath = spec.slice(sep + 1);
|
|
914
|
+
if (!devicePath.startsWith('vol')) {
|
|
915
|
+
throw new Error(`Invalid --resource-bundle device path: "${devicePath}". Expected a vol*:/... path`);
|
|
916
|
+
}
|
|
917
|
+
return {
|
|
918
|
+
binary: readBinaryParam(localPath),
|
|
919
|
+
devicePath,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
function getFirmwareUpdateV4TotalBytes(params) {
|
|
923
|
+
return [
|
|
924
|
+
...(params.resourceBundleFiles?.map(item => item.binary) ?? []),
|
|
925
|
+
params.bootloaderBinary,
|
|
926
|
+
params.applicationP1Binary,
|
|
927
|
+
params.applicationP2Binary,
|
|
928
|
+
params.coprocessorBinary,
|
|
929
|
+
params.se01Binary,
|
|
930
|
+
params.se02Binary,
|
|
931
|
+
params.se03Binary,
|
|
932
|
+
params.se04Binary,
|
|
933
|
+
].reduce((total, binary) => total + (binary?.byteLength ?? 0), 0);
|
|
934
|
+
}
|
|
935
|
+
function getFirmwareUpdateV4ErrorText(result) {
|
|
936
|
+
if (!result || typeof result !== 'object')
|
|
937
|
+
return '';
|
|
938
|
+
const { payload } = result;
|
|
939
|
+
if (!payload || typeof payload !== 'object')
|
|
940
|
+
return '';
|
|
941
|
+
const { error } = payload;
|
|
942
|
+
return typeof error === 'string' ? error : '';
|
|
943
|
+
}
|
|
944
|
+
function isProtocolV2UsbProbeTransientResult(result) {
|
|
945
|
+
const error = getFirmwareUpdateV4ErrorText(result);
|
|
946
|
+
return (error.includes('Device protocol mismatch') &&
|
|
947
|
+
error.includes('expected V2') &&
|
|
948
|
+
error.includes('did not respond to expected protocol'));
|
|
949
|
+
}
|
|
950
|
+
function isSuccessResult(result) {
|
|
951
|
+
return (!!result && typeof result === 'object' && result.success === true);
|
|
952
|
+
}
|
|
953
|
+
function getFirmwareUpdatePayload(message) {
|
|
954
|
+
if (!message || typeof message !== 'object')
|
|
955
|
+
return undefined;
|
|
956
|
+
return message.payload;
|
|
957
|
+
}
|
|
958
|
+
function formatFirmwareProgress(progress) {
|
|
959
|
+
if (!Number.isFinite(progress))
|
|
960
|
+
return '0%';
|
|
961
|
+
return `${Math.min(Math.max(Math.round(progress), 0), 100)}%`;
|
|
962
|
+
}
|
|
963
|
+
function formatFirmwareBytes(bytes) {
|
|
964
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
965
|
+
return '';
|
|
966
|
+
return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
967
|
+
}
|
|
968
|
+
function maybePrintFirmwareProgress({ progressType, progress, payload, lastPrintedProgress, }) {
|
|
969
|
+
const printableProgress = Math.floor(progress / 10) * 10;
|
|
970
|
+
if (printableProgress <= lastPrintedProgress && progress < 100) {
|
|
971
|
+
return lastPrintedProgress;
|
|
972
|
+
}
|
|
973
|
+
const transferredBytes = Number(payload.transferredBytes);
|
|
974
|
+
const totalBytes = Number(payload.totalBytes);
|
|
975
|
+
const rateBytesPerSecond = Number(payload.rateBytesPerSecond);
|
|
976
|
+
const sizeText = Number.isFinite(transferredBytes) && Number.isFinite(totalBytes) && totalBytes > 0
|
|
977
|
+
? ` ${formatFirmwareBytes(transferredBytes)}/${formatFirmwareBytes(totalBytes)}`
|
|
978
|
+
: '';
|
|
979
|
+
const speedText = Number.isFinite(rateBytesPerSecond) && rateBytesPerSecond > 0
|
|
980
|
+
? ` ${(rateBytesPerSecond / 1024).toFixed(2)} KiB/s`
|
|
981
|
+
: '';
|
|
982
|
+
process.stderr.write(`[onekey-hw] Firmware ${progressType}: ${formatFirmwareProgress(progress)}${sizeText}${speedText}\n`);
|
|
983
|
+
return progress >= 100 ? 100 : printableProgress;
|
|
984
|
+
}
|
|
985
|
+
function buildFirmwareUpdateV4Metrics({ attempt, maxAttempts, totalBytes, totalStartedAt, transferStartedAt, transferEndedAt, installStartedAt, installEndedAt, progressEvents, lastProgress, installProgressEvents, lastInstallProgress, retried, }) {
|
|
986
|
+
const totalElapsedMs = Date.now() - totalStartedAt;
|
|
987
|
+
const transferElapsedMs = transferStartedAt !== undefined && transferEndedAt !== undefined
|
|
988
|
+
? transferEndedAt - transferStartedAt
|
|
989
|
+
: undefined;
|
|
990
|
+
const installElapsedMs = installStartedAt !== undefined && installEndedAt !== undefined
|
|
991
|
+
? installEndedAt - installStartedAt
|
|
992
|
+
: undefined;
|
|
993
|
+
return {
|
|
994
|
+
attempt,
|
|
995
|
+
maxAttempts,
|
|
996
|
+
retried,
|
|
997
|
+
totalBytes,
|
|
998
|
+
progressEvents,
|
|
999
|
+
lastProgress,
|
|
1000
|
+
installProgressEvents,
|
|
1001
|
+
lastInstallProgress,
|
|
1002
|
+
transferSeconds: transferElapsedMs !== undefined ? Number((transferElapsedMs / 1000).toFixed(2)) : null,
|
|
1003
|
+
transferKiBPerSecond: transferElapsedMs !== undefined && transferElapsedMs > 0
|
|
1004
|
+
? Number((totalBytes / 1024 / (transferElapsedMs / 1000)).toFixed(2))
|
|
1005
|
+
: null,
|
|
1006
|
+
installSeconds: installElapsedMs !== undefined ? Number((installElapsedMs / 1000).toFixed(2)) : null,
|
|
1007
|
+
totalSeconds: Number((totalElapsedMs / 1000).toFixed(2)),
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
async function runFirmwareUpdateV4WithRetry({ sdk, globalOpts, params, retries, }) {
|
|
1011
|
+
const totalBytes = getFirmwareUpdateV4TotalBytes(params);
|
|
1012
|
+
const maxAttempts = Math.max((retries ?? 2) + 1, 1);
|
|
1013
|
+
let currentSdk = sdk;
|
|
1014
|
+
let lastResult;
|
|
1015
|
+
let retried = false;
|
|
1016
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
1017
|
+
let progressEvents = 0;
|
|
1018
|
+
let lastProgress = -1;
|
|
1019
|
+
let transferStartedAt;
|
|
1020
|
+
let transferEndedAt;
|
|
1021
|
+
let installProgressEvents = 0;
|
|
1022
|
+
let lastInstallProgress = -1;
|
|
1023
|
+
let installStartedAt;
|
|
1024
|
+
let installEndedAt;
|
|
1025
|
+
let lastPrintedTransferProgress = -10;
|
|
1026
|
+
let lastPrintedInstallProgress = -10;
|
|
1027
|
+
const totalStartedAt = Date.now();
|
|
1028
|
+
const connectId = retried && globalOpts.transport === 'usb' && globalOpts.connectId
|
|
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
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
currentSdk.on(hd_core_1.UI_EVENT, onUiEvent);
|
|
1084
|
+
try {
|
|
1085
|
+
lastResult = await currentSdk.firmwareUpdateV4(connectId, params);
|
|
1086
|
+
}
|
|
1087
|
+
finally {
|
|
1088
|
+
currentSdk.off?.(hd_core_1.UI_EVENT, onUiEvent);
|
|
1089
|
+
}
|
|
1090
|
+
if (installStartedAt !== undefined && installEndedAt === undefined) {
|
|
1091
|
+
installEndedAt = Date.now();
|
|
1092
|
+
}
|
|
1093
|
+
const metrics = buildFirmwareUpdateV4Metrics({
|
|
1094
|
+
attempt,
|
|
1095
|
+
maxAttempts,
|
|
1096
|
+
totalBytes,
|
|
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
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
if (isSuccessResult(lastResult)) {
|
|
1119
|
+
return lastResult;
|
|
1120
|
+
}
|
|
1121
|
+
if (attempt >= maxAttempts ||
|
|
1122
|
+
globalOpts.transport !== 'usb' ||
|
|
1123
|
+
!isProtocolV2UsbProbeTransientResult(lastResult)) {
|
|
1124
|
+
return lastResult;
|
|
1125
|
+
}
|
|
1126
|
+
retried = true;
|
|
1127
|
+
process.stderr.write(`[onekey-hw] Protocol V2 USB probe was transient; retrying firmwareUpdateV4 (${attempt}/${maxAttempts})...\n`);
|
|
1128
|
+
await (0, sdk_1.disposeSDK)();
|
|
1129
|
+
await new Promise(resolve => {
|
|
1130
|
+
setTimeout(resolve, 3000);
|
|
1131
|
+
});
|
|
1132
|
+
currentSdk = await (0, sdk_1.createSDK)(globalOpts);
|
|
1133
|
+
}
|
|
1134
|
+
return lastResult;
|
|
1135
|
+
}
|
|
1136
|
+
function buildFirmwareUpdateV4Params(opts) {
|
|
1137
|
+
const params = {
|
|
1138
|
+
platform: 'desktop',
|
|
1139
|
+
connectProtocol: 'V2',
|
|
1140
|
+
chunkSize: opts.chunkSize ? safeParseInt(opts.chunkSize, '--chunk-size') : undefined,
|
|
1141
|
+
forcedUpdateRes: opts.forcedUpdateRes,
|
|
1142
|
+
resourceBundleFiles: opts.resourceBundle?.map(parseResourceBundleParam),
|
|
1143
|
+
romloaderBinary: opts.romloader ? readBinaryParam(opts.romloader) : undefined,
|
|
1144
|
+
bootloaderBinary: opts.bootloader ? readBinaryParam(opts.bootloader) : undefined,
|
|
1145
|
+
applicationP1Binary: opts.applicationP1 ? readBinaryParam(opts.applicationP1) : undefined,
|
|
1146
|
+
applicationP2Binary: opts.applicationP2 ? readBinaryParam(opts.applicationP2) : undefined,
|
|
1147
|
+
coprocessorBinary: opts.coprocessor ? readBinaryParam(opts.coprocessor) : undefined,
|
|
1148
|
+
se01Binary: opts.se01 ? readBinaryParam(opts.se01) : undefined,
|
|
1149
|
+
se02Binary: opts.se02 ? readBinaryParam(opts.se02) : undefined,
|
|
1150
|
+
se03Binary: opts.se03 ? readBinaryParam(opts.se03) : undefined,
|
|
1151
|
+
se04Binary: opts.se04 ? readBinaryParam(opts.se04) : undefined,
|
|
1152
|
+
};
|
|
1153
|
+
const hasPayload = [
|
|
1154
|
+
params.resourceBundleFiles,
|
|
1155
|
+
params.romloaderBinary,
|
|
1156
|
+
params.bootloaderBinary,
|
|
1157
|
+
params.applicationP1Binary,
|
|
1158
|
+
params.applicationP2Binary,
|
|
1159
|
+
params.coprocessorBinary,
|
|
1160
|
+
params.se01Binary,
|
|
1161
|
+
params.se02Binary,
|
|
1162
|
+
params.se03Binary,
|
|
1163
|
+
params.se04Binary,
|
|
1164
|
+
].some(Boolean);
|
|
1165
|
+
if (!hasPayload) {
|
|
1166
|
+
const err = new Error('firmware-update-v4 requires at least one binary path');
|
|
1167
|
+
err.code = 'MISSING_FIRMWARE_BINARY';
|
|
1168
|
+
throw err;
|
|
1169
|
+
}
|
|
1170
|
+
return params;
|
|
1171
|
+
}
|
|
869
1172
|
/**
|
|
870
1173
|
* #9 FIX: Safe parseInt with NaN check
|
|
871
1174
|
*/
|
|
@@ -876,4 +1179,6 @@ function safeParseInt(input, label) {
|
|
|
876
1179
|
}
|
|
877
1180
|
return num;
|
|
878
1181
|
}
|
|
879
|
-
|
|
1182
|
+
if (require.main === module) {
|
|
1183
|
+
program.parse();
|
|
1184
|
+
}
|
package/dist/sdk.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface SDKOptions {
|
|
|
13
13
|
connectId?: string;
|
|
14
14
|
passphraseState?: string;
|
|
15
15
|
useEmptyPassphrase?: boolean;
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
transport?: 'usb' | 'ble';
|
|
16
18
|
}
|
|
17
19
|
export declare function createSDK(opts: SDKOptions): Promise<typeof HardwareSDK>;
|
|
18
20
|
/**
|
package/dist/sdk.js
CHANGED
|
@@ -41,6 +41,7 @@ const readline = __importStar(require("node:readline"));
|
|
|
41
41
|
const hd_common_connect_sdk_1 = __importDefault(require("@onekeyfe/hd-common-connect-sdk"));
|
|
42
42
|
const hd_core_1 = require("@onekeyfe/hd-core");
|
|
43
43
|
const pinentry_1 = require("./pinentry");
|
|
44
|
+
const nobleBlePlugin_1 = require("./transports/nobleBlePlugin");
|
|
44
45
|
/**
|
|
45
46
|
* Current per-invocation CLI options. Event handlers read from this object
|
|
46
47
|
* so that invoking createSDK() with different opts never results in stale
|
|
@@ -182,12 +183,13 @@ function registerEventHandlers(sdk) {
|
|
|
182
183
|
// SDK Factory
|
|
183
184
|
// ---------------------------------------------------------------------------
|
|
184
185
|
async function initSDK() {
|
|
186
|
+
const transport = currentOpts.transport ?? 'usb';
|
|
185
187
|
const settings = {
|
|
186
|
-
debug: false,
|
|
188
|
+
debug: currentOpts.debug ?? false,
|
|
187
189
|
fetchConfig: true,
|
|
188
|
-
env: 'node-usb',
|
|
190
|
+
env: transport === 'ble' ? 'lowlevel' : 'node-usb',
|
|
189
191
|
};
|
|
190
|
-
await hd_common_connect_sdk_1.default.init(settings);
|
|
192
|
+
await hd_common_connect_sdk_1.default.init(settings, undefined, transport === 'ble' ? (0, nobleBlePlugin_1.createNobleBlePlugin)() : undefined);
|
|
191
193
|
// Defensive: strip any stale listeners (e.g. left over from a previous
|
|
192
194
|
// dispose/init cycle in a long-running process) before wiring ours.
|
|
193
195
|
// Mirrors app-monorepo's cleanupHardwareSDKInstance() which removes
|