@onekeyfe/hd-core 1.2.0-alpha.16 → 1.2.0-alpha.17

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 (114) hide show
  1. package/__tests__/deviceFeaturesUtils.test.ts +27 -0
  2. package/__tests__/evmLedgerLegacySafety.test.ts +3 -0
  3. package/__tests__/homescreen.test.ts +24 -0
  4. package/__tests__/protocol-v2.test.ts +1006 -204
  5. package/__tests__/protocolV2FileWrite.test.ts +28 -3
  6. package/__tests__/protocolV2UiInteraction.test.ts +266 -0
  7. package/dist/api/BaseMethod.d.ts +4 -0
  8. package/dist/api/BaseMethod.d.ts.map +1 -1
  9. package/dist/api/FirmwareUpdateV4.d.ts +3 -3
  10. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  11. package/dist/api/GetDeviceInfo.d.ts.map +1 -1
  12. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  13. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  14. package/dist/api/device/DeviceCancel.d.ts.map +1 -1
  15. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  16. package/dist/api/device/DeviceLock.d.ts.map +1 -1
  17. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  18. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  19. package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
  20. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  21. package/dist/api/index.d.ts +1 -1
  22. package/dist/api/index.d.ts.map +1 -1
  23. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  24. package/dist/api/protocol-v2/DeviceSessionOpen.d.ts +28 -0
  25. package/dist/api/protocol-v2/DeviceSessionOpen.d.ts.map +1 -0
  26. package/dist/api/protocol-v2/DeviceSettingsPageShow.d.ts.map +1 -1
  27. package/dist/api/protocol-v2/DeviceSettingsSet.d.ts.map +1 -1
  28. package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -1
  29. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  30. package/dist/core/deviceEventRegistration.d.ts +1 -0
  31. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  32. package/dist/core/index.d.ts.map +1 -1
  33. package/dist/device/Device.d.ts +3 -3
  34. package/dist/device/Device.d.ts.map +1 -1
  35. package/dist/device/DeviceCommands.d.ts +3 -1
  36. package/dist/device/DeviceCommands.d.ts.map +1 -1
  37. package/dist/device/DevicePool.d.ts +2 -2
  38. package/dist/device/DevicePool.d.ts.map +1 -1
  39. package/dist/deviceProfile/buildDeviceFeatures.d.ts +6 -2
  40. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  41. package/dist/deviceProfile/buildDeviceProfile.d.ts +3 -2
  42. package/dist/deviceProfile/buildDeviceProfile.d.ts.map +1 -1
  43. package/dist/events/device.d.ts +4 -0
  44. package/dist/events/device.d.ts.map +1 -1
  45. package/dist/events/ui-request.d.ts +18 -2
  46. package/dist/events/ui-request.d.ts.map +1 -1
  47. package/dist/index.d.ts +56 -11
  48. package/dist/index.js +1000 -537
  49. package/dist/protocols/protocol-v2/features.d.ts +6 -18
  50. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  51. package/dist/protocols/protocol-v2/index.d.ts +1 -1
  52. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  53. package/dist/protocols/protocol-v2/uiInteraction.d.ts +39 -0
  54. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -0
  55. package/dist/protocols/protocol-v2/unlockRetry.d.ts +5 -3
  56. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  57. package/dist/protocols/protocol-v2/walletSession.d.ts +2 -1
  58. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  59. package/dist/types/api/getDeviceInfo.d.ts +2 -1
  60. package/dist/types/api/getDeviceInfo.d.ts.map +1 -1
  61. package/dist/types/api/index.d.ts +2 -2
  62. package/dist/types/api/index.d.ts.map +1 -1
  63. package/dist/types/api/protocolV2.d.ts +4 -2
  64. package/dist/types/api/protocolV2.d.ts.map +1 -1
  65. package/dist/types/device.d.ts +2 -1
  66. package/dist/types/device.d.ts.map +1 -1
  67. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  68. package/dist/utils/homescreen.d.ts.map +1 -1
  69. package/dist/utils/patch.d.ts +1 -1
  70. package/dist/utils/patch.d.ts.map +1 -1
  71. package/package.json +4 -4
  72. package/src/api/BaseMethod.ts +10 -2
  73. package/src/api/FirmwareUpdateV4.ts +27 -73
  74. package/src/api/GetDeviceInfo.ts +9 -2
  75. package/src/api/GetPassphraseState.ts +5 -2
  76. package/src/api/UploadPortfolio.ts +2 -0
  77. package/src/api/device/DeviceCancel.ts +1 -0
  78. package/src/api/device/DeviceChangePin.ts +23 -0
  79. package/src/api/device/DeviceLock.ts +1 -0
  80. package/src/api/device/DeviceUnlock.ts +9 -0
  81. package/src/api/device/DeviceWipe.ts +18 -0
  82. package/src/api/helpers/protocolV2FileWrite.ts +35 -11
  83. package/src/api/index.ts +1 -1
  84. package/src/api/protocol-v2/DeviceInfoGet.ts +0 -1
  85. package/src/api/protocol-v2/DeviceSessionOpen.ts +100 -0
  86. package/src/api/protocol-v2/DeviceSettingsPageShow.ts +9 -1
  87. package/src/api/protocol-v2/DeviceSettingsSet.ts +11 -1
  88. package/src/api/protocol-v2/DeviceStatusGet.ts +1 -0
  89. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +2 -1
  90. package/src/core/deviceEventRegistration.ts +2 -0
  91. package/src/core/index.ts +36 -3
  92. package/src/data/messages/messages-protocol-v2.json +122 -23
  93. package/src/device/Device.ts +33 -41
  94. package/src/device/DeviceCommands.ts +22 -4
  95. package/src/device/DevicePool.ts +14 -6
  96. package/src/deviceProfile/buildDeviceFeatures.ts +21 -9
  97. package/src/deviceProfile/buildDeviceProfile.ts +27 -11
  98. package/src/events/device.ts +4 -0
  99. package/src/events/ui-request.ts +24 -2
  100. package/src/inject.ts +2 -2
  101. package/src/protocols/protocol-v2/features.ts +27 -20
  102. package/src/protocols/protocol-v2/index.ts +0 -1
  103. package/src/protocols/protocol-v2/uiInteraction.ts +209 -0
  104. package/src/protocols/protocol-v2/unlockRetry.ts +43 -3
  105. package/src/protocols/protocol-v2/walletSession.ts +122 -31
  106. package/src/types/api/getDeviceInfo.ts +6 -1
  107. package/src/types/api/index.ts +2 -2
  108. package/src/types/api/protocolV2.ts +5 -3
  109. package/src/types/device.ts +6 -1
  110. package/src/utils/deviceFeaturesUtils.ts +13 -0
  111. package/src/utils/homescreen.ts +10 -0
  112. package/dist/api/protocol-v2/DeviceSessionGet.d.ts +0 -6
  113. package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +0 -1
  114. package/src/api/protocol-v2/DeviceSessionGet.ts +0 -15
@@ -19,10 +19,13 @@ export default class GetPassphraseState extends BaseMethod {
19
19
 
20
20
  const passphraseProtection = this.device.getCurrentPassphraseProtection();
21
21
  const deviceType = this.device.getCurrentDeviceType();
22
- const isProSeries = deviceType === EDeviceType.Pro || deviceType === EDeviceType.Pro2;
22
+
23
+ if (deviceType === EDeviceType.Pro2) {
24
+ return Promise.resolve(passphraseProtection === true ? passphraseState : undefined);
25
+ }
23
26
 
24
27
  return Promise.resolve(
25
- isProSeries || passphraseProtection === true ? passphraseState : undefined
28
+ deviceType === EDeviceType.Pro || passphraseProtection === true ? passphraseState : undefined
26
29
  );
27
30
  }
28
31
  }
@@ -25,6 +25,8 @@ export default class UploadPortfolio extends FileWrite {
25
25
  };
26
26
  super.init();
27
27
  this.unlockPolicy = 'retry-on-locked';
28
+ // Portfolio 是后台数据写入与应用流程,设备不需要用户确认;包括自动解锁阶段在内均不合成 UI Event。
29
+ this.protocolV2UiMode = 'none';
28
30
  }
29
31
 
30
32
  async run() {
@@ -5,6 +5,7 @@ import type { Cancel } from '@onekeyfe/hd-transport';
5
5
  export default class DeviceCancel extends BaseMethod<Cancel> {
6
6
  init() {
7
7
  this.useDevicePassphraseState = false;
8
+ this.unlockPolicy = 'none';
8
9
  }
9
10
 
10
11
  async run() {
@@ -1,5 +1,8 @@
1
+ import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
2
+
1
3
  import { BaseMethod } from '../BaseMethod';
2
4
  import { validateParams } from '../helpers/paramsValidator';
5
+ import { invalidParameter } from '../helpers/filesystemValidation';
3
6
 
4
7
  import type { ChangePin } from '@onekeyfe/hd-transport';
5
8
 
@@ -13,9 +16,29 @@ export default class DeviceChangePin extends BaseMethod<ChangePin> {
13
16
  this.params = {
14
17
  remove: this.payload.remove,
15
18
  };
19
+ this.protocolV2UiInteraction = {
20
+ request: 'button',
21
+ source: 'method-lifecycle',
22
+ reason: 'change-pin',
23
+ completion: 'operation-completed',
24
+ deviceOnly: true,
25
+ page: DeviceSettingsPage.DevicePinChange,
26
+ };
16
27
  }
17
28
 
18
29
  async run() {
30
+ if (this.device.isProtocolV2()) {
31
+ if (this.params.remove) {
32
+ throw invalidParameter(
33
+ 'Parameter [remove=true] is not supported by the Pro2 device PIN page.'
34
+ );
35
+ }
36
+ const res = await this.device.commands.typedCall('DeviceSettingsPageShow', 'Success', {
37
+ page: DeviceSettingsPage.DevicePinChange,
38
+ });
39
+ return Promise.resolve(res.message);
40
+ }
41
+
19
42
  const res = await this.device.commands.typedCall('ChangePin', 'Success', {
20
43
  ...this.params,
21
44
  });
@@ -5,6 +5,7 @@ import type { LockDevice } from '@onekeyfe/hd-transport';
5
5
  export default class DeviceLock extends BaseMethod<LockDevice> {
6
6
  init() {
7
7
  this.useDevicePassphraseState = false;
8
+ this.unlockPolicy = 'none';
8
9
  }
9
10
 
10
11
  async run() {
@@ -5,6 +5,15 @@ import type { LockDevice } from '@onekeyfe/hd-transport';
5
5
  export default class DeviceUnlock extends BaseMethod<LockDevice> {
6
6
  init() {
7
7
  this.useDevicePassphraseState = false;
8
+ this.unlockPolicy = 'none';
9
+ this.protocolV2UiInteraction = {
10
+ request: 'pin',
11
+ source: 'method-lifecycle',
12
+ reason: 'device-unlock',
13
+ completion: 'operation-completed',
14
+ deviceOnly: true,
15
+ operation: 'unlock-device',
16
+ };
8
17
  }
9
18
 
10
19
  async run() {
@@ -1,3 +1,5 @@
1
+ import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
2
+
1
3
  import { BaseMethod } from '../BaseMethod';
2
4
 
3
5
  import type { WipeDevice } from '@onekeyfe/hd-transport';
@@ -5,9 +7,25 @@ import type { WipeDevice } from '@onekeyfe/hd-transport';
5
7
  export default class DeviceWipe extends BaseMethod<WipeDevice> {
6
8
  init() {
7
9
  this.useDevicePassphraseState = false;
10
+ this.protocolV2UiInteraction = {
11
+ request: 'button',
12
+ source: 'method-lifecycle',
13
+ reason: 'device-management',
14
+ completion: 'operation-completed',
15
+ deviceOnly: true,
16
+ page: DeviceSettingsPage.DeviceReset,
17
+ operation: 'wipe-device',
18
+ };
8
19
  }
9
20
 
10
21
  async run() {
22
+ if (this.device.isProtocolV2()) {
23
+ const res = await this.device.commands.typedCall('DeviceSettingsPageShow', 'Success', {
24
+ page: DeviceSettingsPage.DeviceReset,
25
+ });
26
+ return Promise.resolve(res.message);
27
+ }
28
+
11
29
  const res = await this.device.commands.typedCall('WipeDevice', 'Success');
12
30
 
13
31
  return Promise.resolve(res.message);
@@ -30,12 +30,23 @@ export type ProtocolV2FileWriteOptions = {
30
30
  append?: boolean;
31
31
  uiPercentage?: number;
32
32
  timeoutMs?: number;
33
+ maxChunkRetries?: number;
33
34
  throwIfAborted?: () => void;
34
35
  onProgress?: (progress: ProtocolV2FileWriteProgress) => void;
35
36
  };
36
37
 
37
38
  const MIN_FILE_CHUNK_SIZE = 64;
38
39
 
40
+ function isRetryableFileWriteTimeout(error: unknown) {
41
+ if (!error || typeof error !== 'object') return false;
42
+ const candidate = error as { errorCode?: number; code?: number; message?: string };
43
+ const code = candidate.errorCode ?? candidate.code;
44
+ return (
45
+ code === HardwareErrorCode.BleTimeoutError ||
46
+ /Lowlevel response timeout/i.test(candidate.message ?? '')
47
+ );
48
+ }
49
+
39
50
  function getProtocolV2FileChunkLimit() {
40
51
  const env = DataManager.getSettings('env');
41
52
  return env && DataManager.isBleConnect(env)
@@ -116,17 +127,30 @@ export async function writeProtocolV2File(options: ProtocolV2FileWriteOptions) {
116
127
  const progress =
117
128
  options.uiPercentage ??
118
129
  getDeviceTransferProgress(offset, offset + chunk.byteLength, totalSize);
119
- const response = await options.commands.typedCall(
120
- 'FilesystemFileWrite',
121
- 'FilesystemFile',
122
- {
123
- file: { path: options.path, offset, total_size: totalSize, data: chunk },
124
- overwrite: chunks === 0 ? options.overwrite ?? false : false,
125
- append: options.append ?? false,
126
- ui_percentage: progress,
127
- },
128
- { timeoutMs: options.timeoutMs }
129
- );
130
+ const request = {
131
+ file: { path: options.path, offset, total_size: totalSize, data: chunk },
132
+ overwrite: chunks === 0 ? options.overwrite ?? false : false,
133
+ append: options.append ?? false,
134
+ ui_percentage: progress,
135
+ };
136
+ const maxChunkRetries = Math.max(Math.floor(options.maxChunkRetries ?? 0), 0);
137
+ let retryCount = 0;
138
+ let response;
139
+ while (true) {
140
+ try {
141
+ response = await options.commands.typedCall(
142
+ 'FilesystemFileWrite',
143
+ 'FilesystemFile',
144
+ request,
145
+ { timeoutMs: options.timeoutMs }
146
+ );
147
+ break;
148
+ } catch (error) {
149
+ if (retryCount >= maxChunkRetries || !isRetryableFileWriteTimeout(error)) throw error;
150
+ retryCount += 1;
151
+ options.throwIfAborted?.();
152
+ }
153
+ }
130
154
  options.throwIfAborted?.();
131
155
  lastMessage = response.message;
132
156
  const rawProcessedByte = response.message?.processed_byte;
package/src/api/index.ts CHANGED
@@ -50,7 +50,7 @@ export { default as deviceReboot } from './protocol-v2/DeviceReboot';
50
50
  export { default as deviceInfoGet } from './protocol-v2/DeviceInfoGet';
51
51
  export { default as deviceStatusGet } from './protocol-v2/DeviceStatusGet';
52
52
  export { default as deviceGetOnboardingStatus } from './protocol-v2/DeviceGetOnboardingStatus';
53
- export { default as deviceSessionGet } from './protocol-v2/DeviceSessionGet';
53
+ export { default as deviceSessionOpen } from './protocol-v2/DeviceSessionOpen';
54
54
  export { default as deviceFirmwareUpdate } from './protocol-v2/DeviceFirmwareUpdate';
55
55
  export { default as deviceGetFirmwareUpdateStatus } from './protocol-v2/DeviceGetFirmwareUpdateStatus';
56
56
  export { default as deviceFactoryInfoSet } from './protocol-v2/DeviceFactoryInfoSet';
@@ -45,7 +45,6 @@ const DEFAULT_TARGETS: DeviceInfoGetTargets = {
45
45
  hw: true,
46
46
  fw: true,
47
47
  coprocessor: true,
48
- status: true,
49
48
  };
50
49
 
51
50
  const DEFAULT_TYPES: DeviceInfoGetTypes = {
@@ -0,0 +1,100 @@
1
+ import { BaseMethod } from '../BaseMethod';
2
+ import { invalidParameter } from '../helpers/filesystemValidation';
3
+
4
+ import type {
5
+ DeviceAttachPinOnDevice,
6
+ DeviceHostPassphrase,
7
+ DevicePassphraseOnDevice,
8
+ DeviceSessionResume,
9
+ } from '@onekeyfe/hd-transport';
10
+
11
+ type HiddenWalletSelection =
12
+ | {
13
+ host_passphrase: DeviceHostPassphrase;
14
+ passphrase_on_device?: never;
15
+ attach_pin_on_device?: never;
16
+ }
17
+ | {
18
+ host_passphrase?: never;
19
+ passphrase_on_device: DevicePassphraseOnDevice;
20
+ attach_pin_on_device?: never;
21
+ }
22
+ | {
23
+ host_passphrase?: never;
24
+ passphrase_on_device?: never;
25
+ attach_pin_on_device: DeviceAttachPinOnDevice;
26
+ };
27
+
28
+ export type DeviceSessionOpenParams =
29
+ | { resume: DeviceSessionResume; select?: never }
30
+ | { resume?: never; select: HiddenWalletSelection };
31
+
32
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
33
+ !!value && typeof value === 'object' && !Array.isArray(value);
34
+
35
+ const normalizeParams = (payload: Record<string, unknown>): DeviceSessionOpenParams => {
36
+ const hasResume = payload.resume !== undefined;
37
+ const hasSelect = payload.select !== undefined;
38
+ if (hasResume === hasSelect) {
39
+ throw invalidParameter('DeviceSessionOpen requires exactly one of [resume] or [select].');
40
+ }
41
+
42
+ if (hasResume) {
43
+ if (!isRecord(payload.resume) || typeof payload.resume.session_id !== 'string') {
44
+ throw invalidParameter('Parameter [resume.session_id] must be a non-empty string.');
45
+ }
46
+ const sessionId = payload.resume.session_id.trim();
47
+ if (!sessionId) {
48
+ throw invalidParameter('Parameter [resume.session_id] must be a non-empty string.');
49
+ }
50
+ return { resume: { session_id: sessionId } };
51
+ }
52
+
53
+ if (!isRecord(payload.select)) {
54
+ throw invalidParameter('Parameter [select] must be a hidden-wallet selection object.');
55
+ }
56
+ const { select } = payload;
57
+ const accessKeys = ['host_passphrase', 'passphrase_on_device', 'attach_pin_on_device'] as const;
58
+ const selected = accessKeys.filter(key => select[key] !== undefined);
59
+ const unknown = Object.keys(select).filter(
60
+ key => !accessKeys.includes(key as (typeof accessKeys)[number])
61
+ );
62
+ if (unknown.length > 0 || selected.length !== 1) {
63
+ throw invalidParameter('Parameter [select] requires exactly one hidden-wallet access mode.');
64
+ }
65
+
66
+ if (selected[0] === 'host_passphrase') {
67
+ const host = select.host_passphrase;
68
+ if (!isRecord(host) || typeof host.passphrase !== 'string') {
69
+ throw invalidParameter('Parameter [select.host_passphrase.passphrase] is required.');
70
+ }
71
+ const passphrase = host.passphrase.normalize('NFKD');
72
+ if (!passphrase || passphrase.includes('\0') || passphrase.length > 50) {
73
+ throw invalidParameter('Parameter [select.host_passphrase.passphrase] is invalid.');
74
+ }
75
+ return { select: { host_passphrase: { passphrase } } };
76
+ }
77
+
78
+ if (selected[0] === 'passphrase_on_device') {
79
+ return { select: { passphrase_on_device: {} } };
80
+ }
81
+ return { select: { attach_pin_on_device: {} } };
82
+ };
83
+
84
+ export default class DeviceSessionOpen extends BaseMethod<DeviceSessionOpenParams> {
85
+ init() {
86
+ this.requireProtocolV2 = true;
87
+ this.useDevicePassphraseState = false;
88
+ this.skipForceUpdateCheck = true;
89
+ this.params = normalizeParams(this.payload as unknown as Record<string, unknown>);
90
+ }
91
+
92
+ async run() {
93
+ const { message } = await this.device.commands.typedCall(
94
+ 'DeviceSessionOpen',
95
+ 'DeviceSession',
96
+ this.params
97
+ );
98
+ return message;
99
+ }
100
+ }
@@ -26,7 +26,7 @@ const SUPPORTED_PAGES = new Set<number>([
26
26
  function normalizePage(value: DeviceSettingsPageShowParams['page']): SupportedDeviceSettingsPage {
27
27
  const page = typeof value === 'string' ? DeviceSettingsPage[value] : value;
28
28
  if (page !== undefined && SUPPORTED_PAGES.has(page)) {
29
- return page as SupportedDeviceSettingsPage;
29
+ return page;
30
30
  }
31
31
  throw invalidParameter(
32
32
  'Parameter [page] must be DeviceReset, DevicePinChange, DevicePassphrase, or DeviceAirgap.'
@@ -46,6 +46,14 @@ export default class DeviceSettingsPageShow extends BaseMethod<{
46
46
  page: normalizePage(this.payload.page),
47
47
  field_name: this.payload.field_name ?? this.payload.fieldName,
48
48
  };
49
+ this.protocolV2UiInteraction = {
50
+ request: 'button',
51
+ source: 'method-lifecycle',
52
+ reason: 'settings-page',
53
+ completion: 'operation-completed',
54
+ deviceOnly: true,
55
+ page: this.params.page,
56
+ };
49
57
  }
50
58
 
51
59
  async run() {
@@ -11,7 +11,7 @@ export default class DeviceSettingsSet extends BaseMethod<{
11
11
  settings: Omit<DeviceSettings, 'passphrase_enable' | 'airgap_mode'>;
12
12
  }> {
13
13
  init() {
14
- const settings = this.payload.settings;
14
+ const { settings } = this.payload;
15
15
  if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
16
16
  throw invalidParameter('Parameter [settings] must be an object.');
17
17
  }
@@ -30,6 +30,16 @@ export default class DeviceSettingsSet extends BaseMethod<{
30
30
  this.skipForceUpdateCheck = true;
31
31
  this.useDevicePassphraseState = false;
32
32
  this.params = { settings: supported };
33
+ if (typeof supported.label === 'string') {
34
+ this.protocolV2UiInteraction = {
35
+ request: 'button',
36
+ source: 'method-lifecycle',
37
+ reason: 'device-management',
38
+ completion: 'operation-completed',
39
+ deviceOnly: true,
40
+ operation: 'change-label',
41
+ };
42
+ }
33
43
  }
34
44
 
35
45
  async run() {
@@ -9,6 +9,7 @@ export default class DeviceStatusGet extends BaseMethod {
9
9
 
10
10
  async run() {
11
11
  const { message } = await this.device.commands.typedCall('DeviceStatusGet', 'DeviceStatus', {});
12
+ this.device.updateProtocolV2Status(message);
12
13
  return message;
13
14
  }
14
15
  }
@@ -27,7 +27,7 @@ export type DeviceUploadWallpaperResponse = {
27
27
  message?: string;
28
28
  };
29
29
 
30
- const WALLPAPER_DIRECTORY = 'vol0:/wallpapers/user';
30
+ const WALLPAPER_DIRECTORY = 'vol1:/wallpapers';
31
31
  const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
32
32
 
33
33
  function normalizeFileName(fileName: string | undefined, data: Uint8Array): string {
@@ -100,6 +100,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
100
100
  data: encoded.data,
101
101
  totalSize: encoded.data.byteLength,
102
102
  chunkSize: this.params.chunkSize,
103
+ maxChunkRetries: 3,
103
104
  overwrite: true,
104
105
  append: false,
105
106
  throwIfAborted: () => this.throwIfAborted(),
@@ -7,6 +7,7 @@ export type HardwareUiEventHandlers = {
7
7
  button: (...event: DeviceEvents[typeof DEVICE.BUTTON]) => void;
8
8
  passphrase: (...event: DeviceEvents[typeof DEVICE.PASSPHRASE]) => void;
9
9
  passphraseOnDevice: (...event: DeviceEvents[typeof DEVICE.PASSPHRASE_ON_DEVICE]) => void;
10
+ attachPinOnDevice: (...event: DeviceEvents[typeof DEVICE.ATTACH_PIN_ON_DEVICE]) => void;
10
11
  };
11
12
 
12
13
  export function registerHardwareUiEventListeners(
@@ -17,5 +18,6 @@ export function registerHardwareUiEventListeners(
17
18
  device.on(DEVICE.BUTTON, handlers.button);
18
19
  device.on(DEVICE.PASSPHRASE, handlers.passphrase);
19
20
  device.on(DEVICE.PASSPHRASE_ON_DEVICE, handlers.passphraseOnDevice);
21
+ device.on(DEVICE.ATTACH_PIN_ON_DEVICE, handlers.attachPinOnDevice);
20
22
  return true;
21
23
  }
package/src/core/index.ts CHANGED
@@ -58,6 +58,10 @@ import RequestQueue from './RequestQueue';
58
58
  import { registerHardwareUiEventListeners } from './deviceEventRegistration';
59
59
  import { getSynchronize } from '../utils/getSynchronize';
60
60
  import { runMethodWithUnlockRetry } from '../protocols/protocol-v2/unlockRetry';
61
+ import {
62
+ ProtocolV2UiInteractionCoordinator,
63
+ isProtocolV2UiEnabled,
64
+ } from '../protocols/protocol-v2/uiInteraction';
61
65
 
62
66
  import type { ConnectSettings, Features, KnownDevice } from '../types';
63
67
  import type { CoreMessage, IFrameCallMessage, UiPromise, UiPromiseResponse } from '../events';
@@ -403,12 +407,15 @@ const onCallDevice = async (
403
407
  ? onEmptyPassphraseHandler
404
408
  : onDevicePassphraseHandler,
405
409
  passphraseOnDevice: onEnterPassphraseOnDeviceHandler,
410
+ attachPinOnDevice: onEnterAttachPinOnDeviceHandler,
406
411
  });
407
412
  device.on(DEVICE.FEATURES, onDeviceFeaturesHandler);
408
413
  device.on(
409
414
  DEVICE.SELECT_DEVICE_IN_BOOTLOADER_FOR_WEB_DEVICE,
410
415
  onSelectDeviceInBootloaderForWebDeviceHandler
411
416
  );
417
+
418
+ const protocolV2UiCoordinator = new ProtocolV2UiInteractionCoordinator(device, postMessage);
412
419
  device.on(
413
420
  DEVICE.SELECT_DEVICE_FOR_SWITCH_FIRMWARE_WEB_DEVICE,
414
421
  onSelectDeviceForSwitchFirmwareWebDeviceHandler
@@ -619,7 +626,11 @@ const onCallDevice = async (
619
626
  method.device?.commands?.checkDisposed();
620
627
 
621
628
  try {
622
- const response: object = await runMethodWithUnlockRetry(method, device);
629
+ const response: object = await runMethodWithUnlockRetry(
630
+ method,
631
+ device,
632
+ protocolV2UiCoordinator
633
+ );
623
634
  messageResponse = createResponseMessage(method.responseID, true, response);
624
635
  requestQueue.resolveRequest(method.responseID, messageResponse);
625
636
  completeMethodRequestContext(method);
@@ -636,6 +647,8 @@ const onCallDevice = async (
636
647
  ) {
637
648
  throw error;
638
649
  }
650
+ } finally {
651
+ protocolV2UiCoordinator.close();
639
652
  }
640
653
  };
641
654
  Log.debug('Call API - Device Run: ', device.mainId);
@@ -689,7 +702,9 @@ const onCallDevice = async (
689
702
 
690
703
  requestQueue.releaseTask(method.responseID);
691
704
 
692
- closePopup();
705
+ if (isProtocolV2UiEnabled(method)) {
706
+ closePopup();
707
+ }
693
708
 
694
709
  cleanup();
695
710
 
@@ -1235,6 +1250,9 @@ const onDevicePassphraseHandler = async (
1235
1250
  device: device.toMessageObject() as KnownDevice,
1236
1251
  passphraseState: device.passphraseState,
1237
1252
  existsAttachPinUser: requestPayload.existsAttachPinUser,
1253
+ source: requestPayload.source,
1254
+ reason: requestPayload.reason,
1255
+ expectedPassphraseState: requestPayload.expectedPassphraseState,
1238
1256
  })
1239
1257
  );
1240
1258
  // wait for passphrase
@@ -1256,12 +1274,27 @@ const onEmptyPassphraseHandler = (...[_, , callback]: DeviceEvents['passphrase']
1256
1274
  };
1257
1275
 
1258
1276
  const onEnterPassphraseOnDeviceHandler = (
1259
- ...[device]: [...DeviceEvents['passphrase_on_device']]
1277
+ ...[device, requestPayload]: [...DeviceEvents['passphrase_on_device']]
1260
1278
  ) => {
1261
1279
  postMessage(
1262
1280
  createUiMessage(UI_REQUEST.REQUEST_PASSPHRASE_ON_DEVICE, {
1263
1281
  device: device.toMessageObject() as KnownDevice,
1264
1282
  passphraseState: device.passphraseState,
1283
+ source: requestPayload?.source,
1284
+ reason: requestPayload?.reason,
1285
+ })
1286
+ );
1287
+ };
1288
+
1289
+ const onEnterAttachPinOnDeviceHandler = (
1290
+ ...[device, requestPayload]: [...DeviceEvents['attach_pin_on_device']]
1291
+ ) => {
1292
+ postMessage(
1293
+ createUiMessage(UI_REQUEST.REQUEST_PIN, {
1294
+ device: device.toMessageObject() as KnownDevice,
1295
+ type: 'ButtonRequest_AttachPin',
1296
+ source: requestPayload?.source,
1297
+ reason: requestPayload?.reason,
1265
1298
  })
1266
1299
  );
1267
1300
  };