@onekeyfe/hd-core 1.2.0-alpha.25 → 1.2.0-alpha.27

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.
Files changed (48) hide show
  1. package/__tests__/DeviceCommands.test.ts +28 -0
  2. package/__tests__/device-lifecycle-events.test.ts +21 -0
  3. package/__tests__/deviceSettings.test.ts +4 -4
  4. package/__tests__/firmware-update/check-all-firmware-release.test.ts +31 -0
  5. package/__tests__/firmware-update/firmware-update-plan.test.ts +70 -0
  6. package/__tests__/kaspa-use-tweak-pro2.test.ts +20 -0
  7. package/__tests__/protocol-v2.test.ts +129 -6
  8. package/__tests__/tron-sign-message-init.test.ts +28 -0
  9. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  10. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +3 -2
  11. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -1
  12. package/dist/api/kaspa/KaspaGetAddress.d.ts +3 -0
  13. package/dist/api/kaspa/KaspaGetAddress.d.ts.map +1 -1
  14. package/dist/api/kaspa/KaspaSignTransaction.d.ts +3 -0
  15. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  16. package/dist/api/tron/TronSignMessage.d.ts +3 -0
  17. package/dist/api/tron/TronSignMessage.d.ts.map +1 -1
  18. package/dist/device/Device.d.ts +1 -0
  19. package/dist/device/Device.d.ts.map +1 -1
  20. package/dist/device/DeviceCommands.d.ts.map +1 -1
  21. package/dist/index.d.ts +6 -1
  22. package/dist/index.js +83 -42
  23. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  24. package/dist/types/api/checkAllFirmwareRelease.d.ts +2 -1
  25. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  26. package/dist/types/api/export.d.ts +1 -1
  27. package/dist/types/api/export.d.ts.map +1 -1
  28. package/dist/types/api/firmwareUpdatePlan.d.ts +1 -0
  29. package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -1
  30. package/dist/types/device.d.ts +1 -0
  31. package/dist/types/device.d.ts.map +1 -1
  32. package/dist/utils/deviceSettings.d.ts.map +1 -1
  33. package/package.json +4 -4
  34. package/src/api/CheckAllFirmwareRelease.ts +2 -0
  35. package/src/api/UploadPortfolio.ts +1 -1
  36. package/src/api/firmware/FirmwareUpdatePlan.ts +47 -37
  37. package/src/api/kaspa/KaspaGetAddress.ts +3 -0
  38. package/src/api/kaspa/KaspaSignTransaction.ts +3 -0
  39. package/src/api/tron/TronSignMessage.ts +3 -0
  40. package/src/data-manager/TransportManager.ts +1 -1
  41. package/src/device/Device.ts +7 -0
  42. package/src/device/DeviceCommands.ts +10 -4
  43. package/src/protocols/protocol-v2/unlockRetry.ts +18 -11
  44. package/src/types/api/checkAllFirmwareRelease.ts +2 -1
  45. package/src/types/api/export.ts +1 -0
  46. package/src/types/api/firmwareUpdatePlan.ts +2 -0
  47. package/src/types/device.ts +2 -0
  48. package/src/utils/deviceSettings.ts +11 -1
@@ -12,6 +12,7 @@ import {
12
12
  import type {
13
13
  FirmwareUpdatePlan,
14
14
  FirmwareUpdatePlanArtifact,
15
+ FirmwareUpdatePlanForceTarget,
15
16
  FirmwareUpdatePlanTarget,
16
17
  } from '../../types/api/firmwareUpdatePlan';
17
18
  import type { Features } from '../../types';
@@ -498,6 +499,7 @@ export const buildFirmwareUpdatePlan = ({
498
499
  firmware,
499
500
  ble,
500
501
  bootloader,
502
+ forceUpdateTargets,
501
503
  }: {
502
504
  features: Features;
503
505
  firmwareType: EFirmwareType;
@@ -505,18 +507,22 @@ export const buildFirmwareUpdatePlan = ({
505
507
  firmware: ReleaseSelection;
506
508
  ble: ReleaseSelection;
507
509
  bootloader?: ReleaseSelection;
510
+ forceUpdateTargets?: FirmwareUpdatePlanForceTarget[];
508
511
  }): FirmwareUpdatePlan => {
509
512
  const executor = getExecutor(features);
510
513
  let artifacts: FirmwareUpdatePlanArtifact[] = [];
511
514
  let targetsToUpdate: FirmwareUpdatePlanTarget[] = [];
512
515
  const firmwareRelease = asRelease(firmware);
516
+ const forcedTargets = new Set(forceUpdateTargets);
517
+ const shouldUpdateFirmware = isUpgrade(firmware) || forcedTargets.has('firmware');
518
+ const shouldUpdateResource = isUpgrade(firmware) || forcedTargets.has('resource');
513
519
 
514
- if (executor === 'v4' && isUpgrade(firmware)) {
520
+ if (executor === 'v4' && (shouldUpdateFirmware || shouldUpdateResource)) {
515
521
  const protocolV2 = buildProtocolV2Artifacts(firmwareRelease ?? {});
516
522
  artifacts = protocolV2.artifacts;
517
523
  targetsToUpdate = protocolV2.targets;
518
524
  } else {
519
- if (isUpgrade(bootloader)) {
525
+ if (isUpgrade(bootloader) || forcedTargets.has('bootloader')) {
520
526
  artifacts.push({
521
527
  artifactId: 'bootloader',
522
528
  role: 'bootloader',
@@ -534,52 +540,56 @@ export const buildFirmwareUpdatePlan = ({
534
540
  : {}),
535
541
  });
536
542
  }
537
- if (isUpgrade(firmware)) {
543
+ if (shouldUpdateFirmware || shouldUpdateResource) {
538
544
  if (!firmwareRelease) {
539
545
  throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Firmware release is invalid', {
540
546
  firmwareUpdateCode: 'FirmwarePlanInvalid',
541
547
  });
542
548
  }
543
- artifacts.push({
544
- artifactId: 'firmware',
545
- role: 'firmware',
546
- target: 'firmware',
547
- url: assertArtifactUrl(firmwareRelease.url, 'Firmware release'),
548
- container: 'raw',
549
- ...asIntegrity({
550
- size: firmwareRelease.expectedSize,
551
- sha256: firmwareRelease.fingerprint,
552
- }),
553
- ...(asVersion(firmwareRelease.version)
554
- ? { targetVersion: asVersion(firmwareRelease.version) }
555
- : {}),
556
- });
557
- const resourceUrl = selectResourceUrl({
558
- release: firmwareRelease,
559
- features,
560
- platform,
561
- });
562
- if (resourceUrl) {
549
+ if (shouldUpdateFirmware) {
563
550
  artifacts.push({
564
- artifactId: 'resource',
565
- role: 'resource',
566
- target: 'resource',
567
- url: assertArtifactUrl(resourceUrl, 'Firmware resource release'),
568
- container: 'zip',
551
+ artifactId: 'firmware',
552
+ role: 'firmware',
553
+ target: 'firmware',
554
+ url: assertArtifactUrl(firmwareRelease.url, 'Firmware release'),
555
+ container: 'raw',
569
556
  ...asIntegrity({
570
- size:
571
- resourceUrl === asString(firmwareRelease.fullResource)
572
- ? firmwareRelease.fullResourceExpectedSize
573
- : firmwareRelease.resourceExpectedSize,
574
- sha256:
575
- resourceUrl === asString(firmwareRelease.fullResource)
576
- ? firmwareRelease.fullResourceFingerprint
577
- : firmwareRelease.resourceFingerprint,
557
+ size: firmwareRelease.expectedSize,
558
+ sha256: firmwareRelease.fingerprint,
578
559
  }),
560
+ ...(asVersion(firmwareRelease.version)
561
+ ? { targetVersion: asVersion(firmwareRelease.version) }
562
+ : {}),
579
563
  });
580
564
  }
565
+ if (shouldUpdateResource) {
566
+ const resourceUrl = selectResourceUrl({
567
+ release: firmwareRelease,
568
+ features,
569
+ platform,
570
+ });
571
+ if (resourceUrl) {
572
+ artifacts.push({
573
+ artifactId: 'resource',
574
+ role: 'resource',
575
+ target: 'resource',
576
+ url: assertArtifactUrl(resourceUrl, 'Firmware resource release'),
577
+ container: 'zip',
578
+ ...asIntegrity({
579
+ size:
580
+ resourceUrl === asString(firmwareRelease.fullResource)
581
+ ? firmwareRelease.fullResourceExpectedSize
582
+ : firmwareRelease.resourceExpectedSize,
583
+ sha256:
584
+ resourceUrl === asString(firmwareRelease.fullResource)
585
+ ? firmwareRelease.fullResourceFingerprint
586
+ : firmwareRelease.resourceFingerprint,
587
+ }),
588
+ });
589
+ }
590
+ }
581
591
  }
582
- if (isUpgrade(ble)) {
592
+ if (isUpgrade(ble) || forcedTargets.has('ble')) {
583
593
  const bleRelease = asRelease(ble);
584
594
  artifacts.push({
585
595
  artifactId: 'ble',
@@ -67,6 +67,9 @@ export default class KaspaGetAddress extends BaseMethod<HardwareKaspaGetAddress[
67
67
  model_classic1s: {
68
68
  min: '3.12.0',
69
69
  },
70
+ pro2: {
71
+ min: '0.0.0',
72
+ },
70
73
  };
71
74
  }
72
75
 
@@ -183,6 +183,9 @@ export default class KaspaSignTransaction extends BaseMethod<KaspaSignTransactio
183
183
  model_classic1s: {
184
184
  min: '3.12.0',
185
185
  },
186
+ pro2: {
187
+ min: '0.0.0',
188
+ },
186
189
  };
187
190
  }
188
191
 
@@ -57,6 +57,9 @@ export default class TronSignMessage extends BaseMethod<HardwareTronSignMessage>
57
57
 
58
58
  getMessageV2VersionRange() {
59
59
  return {
60
+ pro2: {
61
+ min: '0.0.0',
62
+ },
60
63
  pro: {
61
64
  min: '4.16.0',
62
65
  },
@@ -17,7 +17,7 @@ const LowLevelLogger = getLogger(LoggerNames.HdTransportLowLevel);
17
17
  const NodeUsbLogger = getLogger(LoggerNames.HdTransportNodeUsb);
18
18
  const WebBleLogger = getLogger(LoggerNames.HdWebBleTransport);
19
19
  const WebUsbLogger = getLogger(LoggerNames.HdTransportWebUsb);
20
- const REACT_NATIVE_BLE_SCAN_TIMEOUT_MS = 8000;
20
+ const REACT_NATIVE_BLE_SCAN_TIMEOUT_MS = 3000;
21
21
 
22
22
  /**
23
23
  * transport 在同一个环境中只会存在一个
@@ -314,6 +314,7 @@ export class Device extends EventEmitter {
314
314
  // Keep the legacy top-level field string-compatible while preserving
315
315
  // the canonical nullable value at state.identity.label.
316
316
  label: displayName ?? '',
317
+ status: this.getStatus(),
317
318
  mode: this.getMode(),
318
319
  features,
319
320
  state,
@@ -1335,6 +1336,12 @@ export class Device extends EventEmitter {
1335
1336
  return EOneKeyDeviceMode.normal;
1336
1337
  }
1337
1338
 
1339
+ getStatus() {
1340
+ if (this.isUsedElsewhere()) return 'occupied' as const;
1341
+ if (this.isUsedHere()) return 'used' as const;
1342
+ return 'available' as const;
1343
+ }
1344
+
1338
1345
  getFirmwareVersion() {
1339
1346
  if (!this.state) return null;
1340
1347
  return parseDeviceVersion(this.state.versions.firmware);
@@ -54,6 +54,13 @@ const DEVICE_SESSION_CALLS = new Set([
54
54
  'DeviceSessionAskPassphrase',
55
55
  ]);
56
56
 
57
+ // Protocol V2 subcodes are domain-scoped, so cross-domain cancellation fallback
58
+ // must use explicit firmware cancellation messages instead of a global number map.
59
+ const isProtocolV2ActionCancelledMessage = (message: string) =>
60
+ /^(?:cancel(?:led|ed)(?: on device)?|confirm dismissed|user cancel(?:led|ed)(?:\s+.*)?)$/i.test(
61
+ message
62
+ );
63
+
57
64
  function shouldReduceDebugForCall(type: string) {
58
65
  return HIGH_VOLUME_DEBUG_CALLS.has(type);
59
66
  }
@@ -474,9 +481,8 @@ export class DeviceCommands {
474
481
 
475
482
  if (code === 'Failure_ProcessError') {
476
483
  const normalizedMessage = message?.trim() ?? '';
477
- const isLegacyProtocolV2ActionCancelledFailure =
478
- this.device.isProtocolV2() &&
479
- /^(?:cancelled on device|confirm dismissed)$/i.test(normalizedMessage);
484
+ const isProtocolV2ActionCancelledFailure =
485
+ this.device.isProtocolV2() && isProtocolV2ActionCancelledMessage(normalizedMessage);
480
486
  const isLegacyProtocolV2LockedFailure =
481
487
  this.device.isProtocolV2() && /^device (?:is )?locked$/i.test(normalizedMessage);
482
488
  if (
@@ -536,7 +542,7 @@ export class DeviceCommands {
536
542
  });
537
543
  } else if (
538
544
  subcode === DeviceErrorCode.DeviceError_ActionCancelled ||
539
- isLegacyProtocolV2ActionCancelledFailure
545
+ isProtocolV2ActionCancelledFailure
540
546
  ) {
541
547
  error = ERRORS.TypedError(HardwareErrorCode.ActionCancelled, message, {
542
548
  failureCode: code,
@@ -24,6 +24,22 @@ type UiInteractionCoordinator = Pick<
24
24
  'enterMethodInteraction' | 'enterUnlockInteraction' | 'resumeMethodInteraction'
25
25
  >;
26
26
 
27
+ const restoreExpectedWalletSessionAfterUnlock = async (method: RunnableMethod, device: Device) => {
28
+ const expectedPassphraseState = method.payload?.useEmptyPassphrase
29
+ ? undefined
30
+ : method.payload?.passphraseState ?? device.passphraseState;
31
+ if (
32
+ !method.useDevicePassphraseState ||
33
+ typeof expectedPassphraseState !== 'string' ||
34
+ expectedPassphraseState.length === 0
35
+ ) {
36
+ return;
37
+ }
38
+
39
+ await restoreProtocolV2WalletSession(device, expectedPassphraseState);
40
+ Log.debug('Protocol V2 wallet session restored after unlock', { method: method.name });
41
+ };
42
+
27
43
  export async function runMethodWithUnlockRetry(
28
44
  method: RunnableMethod,
29
45
  device: Device,
@@ -39,6 +55,7 @@ export async function runMethodWithUnlockRetry(
39
55
  }
40
56
  await device.unlockDevice();
41
57
  Log.debug('Protocol V2 pre-unlock completed', { method: method.name });
58
+ await restoreExpectedWalletSessionAfterUnlock(method, device);
42
59
  if (shouldEmitUi) {
43
60
  uiCoordinator?.enterMethodInteraction(resolveProtocolV2UiInteraction(method));
44
61
  }
@@ -65,17 +82,7 @@ export async function runMethodWithUnlockRetry(
65
82
  }
66
83
  await device.unlockDevice();
67
84
  Log.debug('Protocol V2 unlock completed', { method: method.name });
68
- const expectedPassphraseState = method.payload?.useEmptyPassphrase
69
- ? undefined
70
- : method.payload?.passphraseState ?? device.passphraseState;
71
- if (
72
- method.useDevicePassphraseState &&
73
- typeof expectedPassphraseState === 'string' &&
74
- expectedPassphraseState.length > 0
75
- ) {
76
- await restoreProtocolV2WalletSession(device, expectedPassphraseState);
77
- Log.debug('Protocol V2 wallet session restored after unlock', { method: method.name });
78
- }
85
+ await restoreExpectedWalletSessionAfterUnlock(method, device);
79
86
  if (shouldEmitUi) {
80
87
  uiCoordinator?.resumeMethodInteraction();
81
88
  }
@@ -2,7 +2,7 @@ import type { IBLEFirmwareReleaseInfo } from '../settings';
2
2
  import type { EFirmwareType } from '@onekeyfe/hd-shared';
3
3
  import type { CommonParams, Response } from '../params';
4
4
  import type { Features, IDeviceBLEFirmwareStatus, IDeviceFirmwareStatus } from '../device';
5
- import type { FirmwareUpdatePlan } from './firmwareUpdatePlan';
5
+ import type { FirmwareUpdatePlan, FirmwareUpdatePlanForceTarget } from './firmwareUpdatePlan';
6
6
 
7
7
  export type FirmwareRelease = {
8
8
  shouldUpdate?: boolean;
@@ -28,6 +28,7 @@ export type CheckAllFirmwareReleaseParams = {
28
28
  checkBridgeRelease?: boolean;
29
29
  firmwareType?: EFirmwareType;
30
30
  platform?: 'native' | 'desktop' | 'ext' | 'web' | 'web-embed';
31
+ forceUpdateTargets?: FirmwareUpdatePlanForceTarget[];
31
32
  };
32
33
 
33
34
  export declare function checkAllFirmwareRelease(
@@ -39,6 +39,7 @@ export type {
39
39
  FirmwareUpdatePlan,
40
40
  FirmwareUpdatePlanArtifact,
41
41
  FirmwareUpdatePlanArtifactRole,
42
+ FirmwareUpdatePlanForceTarget,
42
43
  FirmwareUpdatePlanTarget,
43
44
  } from './firmwareUpdatePlan';
44
45
  export type {
@@ -11,6 +11,8 @@ export type FirmwareUpdatePlanArtifactRole =
11
11
 
12
12
  export type FirmwareUpdatePlanTarget = 'firmware' | 'ble' | 'bootloader' | FirmwareUpdateV4Target;
13
13
 
14
+ export type FirmwareUpdatePlanForceTarget = 'firmware' | 'ble' | 'bootloader' | 'resource';
15
+
14
16
  export interface FirmwareUpdatePlanArtifact {
15
17
  artifactId: string;
16
18
  role: FirmwareUpdatePlanArtifactRole;
@@ -43,6 +43,8 @@ export type KnownDevice = {
43
43
  bleName: string | null;
44
44
  name: string;
45
45
  error?: typeof undefined;
46
+ /** Current SDK/transport usage state for connection indicators. */
47
+ status?: DeviceStatus;
46
48
  mode: EOneKeyDeviceMode;
47
49
  features?: Features;
48
50
  /** Unified SDK device-state snapshot; features remains a legacy projection. */
@@ -90,8 +90,18 @@ export const mapLanguageFromProtocolV2 = (language?: string) =>
90
90
  language ? PROTOCOL_V2_LANGUAGE_KEY_BY_TAG[language] ?? language : undefined;
91
91
 
92
92
  const PRO2_LANGUAGE_OPTIONS = [
93
- ...Object.entries(LANGUAGE_LABELS).map(([code, label]) => ({ code, label })),
93
+ { code: 'en', label: 'English' },
94
+ { code: 'zh_cn', label: '简体中文' },
95
+ { code: 'zh_hk', label: '繁體中文(香港)' },
94
96
  { code: 'zh-Hant-TW', label: '繁體中文(台灣)' },
97
+ { code: 'ja', label: '日本語' },
98
+ { code: 'ko', label: '한국어' },
99
+ { code: 'fr', label: 'Français' },
100
+ { code: 'de', label: 'Deutsch' },
101
+ { code: 'ru', label: 'Русский' },
102
+ { code: 'es', label: 'Español' },
103
+ { code: 'it', label: 'Italiano' },
104
+ { code: 'pt_br', label: 'Portuguese (Brazil)' },
95
105
  { code: 'vi-Latn-VN', label: 'Tiếng Việt' },
96
106
  { code: 'tr-Latn-TR', label: 'Türkçe' },
97
107
  { code: 'id-Latn-ID', label: 'Bahasa Indonesia' },