@onekeyfe/hd-core 1.2.0-alpha.8 → 1.2.0-alpha.9

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 (63) hide show
  1. package/__tests__/device-wallet-session-store.test.ts +131 -0
  2. package/__tests__/protocol-v2-firmware-targets.test.ts +19 -0
  3. package/__tests__/protocol-v2.test.ts +738 -460
  4. package/dist/api/ClearSessionCache.d.ts +9 -0
  5. package/dist/api/ClearSessionCache.d.ts.map +1 -0
  6. package/dist/api/FirmwareUpdateV4.d.ts +7 -6
  7. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  8. package/dist/api/firmware/getBinary.d.ts +1 -1
  9. package/dist/api/index.d.ts +3 -0
  10. package/dist/api/index.d.ts.map +1 -1
  11. package/dist/api/protocol-v2/DeviceSessionGet.d.ts +9 -0
  12. package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +1 -0
  13. package/dist/api/protocol-v2/DeviceStatusGet.d.ts +6 -0
  14. package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -0
  15. package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
  16. package/dist/api/protocol-v2/helpers.d.ts +1 -1
  17. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  18. package/dist/device/Device.d.ts +2 -2
  19. package/dist/device/Device.d.ts.map +1 -1
  20. package/dist/device/DeviceWalletSessionStore.d.ts +15 -0
  21. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -0
  22. package/dist/index.d.ts +32 -19
  23. package/dist/index.js +644 -263
  24. package/dist/inject.d.ts.map +1 -1
  25. package/dist/protocols/protocol-v2/index.d.ts +1 -0
  26. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  27. package/dist/protocols/protocol-v2/walletSession.d.ts +13 -0
  28. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -0
  29. package/dist/types/api/firmwareUpdate.d.ts +4 -1
  30. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  31. package/dist/types/api/index.d.ts +6 -1
  32. package/dist/types/api/index.d.ts.map +1 -1
  33. package/dist/types/api/protocolV2.d.ts +6 -2
  34. package/dist/types/api/protocolV2.d.ts.map +1 -1
  35. package/dist/types/api/sessionCache.d.ts +10 -0
  36. package/dist/types/api/sessionCache.d.ts.map +1 -0
  37. package/dist/types/settings.d.ts +6 -14
  38. package/dist/types/settings.d.ts.map +1 -1
  39. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  40. package/dist/utils/patch.d.ts +1 -1
  41. package/dist/utils/patch.d.ts.map +1 -1
  42. package/package.json +4 -4
  43. package/src/api/ClearSessionCache.ts +28 -0
  44. package/src/api/FileRead.ts +2 -2
  45. package/src/api/FirmwareUpdateV4.ts +269 -210
  46. package/src/api/index.ts +3 -0
  47. package/src/api/protocol-v2/DeviceSessionGet.ts +26 -0
  48. package/src/api/protocol-v2/DeviceStatusGet.ts +14 -0
  49. package/src/api/protocol-v2/FilesystemFormat.ts +4 -1
  50. package/src/api/protocol-v2/helpers.ts +17 -14
  51. package/src/data/messages/messages-protocol-v2.json +134 -3
  52. package/src/data/messages/messages.json +8 -0
  53. package/src/device/Device.ts +64 -56
  54. package/src/device/DeviceWalletSessionStore.ts +78 -0
  55. package/src/inject.ts +5 -2
  56. package/src/protocols/protocol-v2/index.ts +1 -0
  57. package/src/protocols/protocol-v2/walletSession.ts +83 -0
  58. package/src/types/api/firmwareUpdate.ts +8 -2
  59. package/src/types/api/index.ts +10 -3
  60. package/src/types/api/protocolV2.ts +16 -2
  61. package/src/types/api/sessionCache.ts +14 -0
  62. package/src/types/settings.ts +14 -16
  63. package/src/utils/deviceFeaturesUtils.ts +22 -32
package/src/api/index.ts CHANGED
@@ -7,6 +7,7 @@ export { default as getDeviceInfo } from './GetDeviceInfo';
7
7
  export { default as getOnekeyFeatures } from './GetOnekeyFeatures';
8
8
  export { default as getPassphraseState } from './GetPassphraseState';
9
9
  export { default as getLogs } from './GetLogs';
10
+ export { default as clearSessionCache } from './ClearSessionCache';
10
11
  export { default as checkFirmwareRelease } from './CheckFirmwareRelease';
11
12
  export { default as checkBLEFirmwareRelease } from './CheckBLEFirmwareRelease';
12
13
  export { default as checkBridgeStatus } from './CheckBridgeStatus';
@@ -47,6 +48,8 @@ export { default as protocolInfoRequest } from './protocol-v2/ProtocolInfoReques
47
48
  export { default as ping } from './protocol-v2/Ping';
48
49
  export { default as deviceReboot } from './protocol-v2/DeviceReboot';
49
50
  export { default as deviceInfoGet } from './protocol-v2/DeviceInfoGet';
51
+ export { default as deviceStatusGet } from './protocol-v2/DeviceStatusGet';
52
+ export { default as deviceSessionGet } from './protocol-v2/DeviceSessionGet';
50
53
  export { default as deviceFirmwareUpdate } from './protocol-v2/DeviceFirmwareUpdate';
51
54
  export { default as deviceGetFirmwareUpdateStatus } from './protocol-v2/DeviceGetFirmwareUpdateStatus';
52
55
  export { default as deviceFactoryInfoSet } from './protocol-v2/DeviceFactoryInfoSet';
@@ -0,0 +1,26 @@
1
+ import { BaseMethod } from '../BaseMethod';
2
+
3
+ export type DeviceSessionGetParams = {
4
+ sessionId?: string;
5
+ };
6
+
7
+ export default class DeviceSessionGet extends BaseMethod<DeviceSessionGetParams> {
8
+ init() {
9
+ this.requireProtocolV2 = true;
10
+ this.useDevicePassphraseState = false;
11
+ this.skipForceUpdateCheck = true;
12
+ this.params = {
13
+ sessionId: this.payload.sessionId,
14
+ };
15
+ }
16
+
17
+ async run() {
18
+ const payload = this.params.sessionId ? { session_id: this.params.sessionId } : {};
19
+ const { message } = await this.device.commands.typedCall(
20
+ 'DeviceSessionGet',
21
+ 'DeviceSession',
22
+ payload
23
+ );
24
+ return message;
25
+ }
26
+ }
@@ -0,0 +1,14 @@
1
+ import { BaseMethod } from '../BaseMethod';
2
+
3
+ export default class DeviceStatusGet extends BaseMethod {
4
+ init() {
5
+ this.requireProtocolV2 = true;
6
+ this.useDevicePassphraseState = false;
7
+ this.skipForceUpdateCheck = true;
8
+ }
9
+
10
+ async run() {
11
+ const { message } = await this.device.commands.typedCall('DeviceStatusGet', 'DeviceStatus', {});
12
+ return message;
13
+ }
14
+ }
@@ -10,7 +10,10 @@ export default class FilesystemFormat extends BaseMethod {
10
10
  }
11
11
 
12
12
  async run() {
13
- const res = await this.device.commands.typedCall('FilesystemFormat', 'Success', {});
13
+ const res = await this.device.commands.typedCall('FilesystemFormat', 'Success', {
14
+ data: true,
15
+ user: true,
16
+ });
14
17
  return Promise.resolve(res.message);
15
18
  }
16
19
  }
@@ -5,8 +5,8 @@ import { ProtocolV2FirmwareTargetType } from '../../protocols/protocol-v2/firmwa
5
5
 
6
6
  import type {
7
7
  DeviceFirmwareTarget,
8
- DeviceFirmwareUpdateRecordFields,
9
8
  DeviceFirmwareTargetType,
9
+ DeviceFirmwareUpdateRecordFields,
10
10
  TransportCallOptions,
11
11
  } from '@onekeyfe/hd-transport';
12
12
 
@@ -138,16 +138,21 @@ export function normalizeRebootType(value: RebootTypeInput | undefined): DeviceR
138
138
  return DeviceRebootType.Normal;
139
139
  }
140
140
 
141
- // 当前 firmware-pro2 子模块的 DeviceFirmwareTargetType 合法值从生成 enum 派生,
142
- // 避免协议枚举增减时这里继续保留过期手写编号。
143
- const VALID_FIRMWARE_TARGET_IDS = new Set<number>(
144
- Object.values(ProtocolV2FirmwareTargetType).filter(value => typeof value === 'number') as number[]
145
- );
141
+ // firmware-pro2 proto_target_to_firmware_target 安装白名单保持一致。
142
+ const INSTALLABLE_FIRMWARE_TARGET_IDS = new Set<number>([
143
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_CRATE,
144
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
145
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1,
146
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2,
147
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR,
148
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01,
149
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02,
150
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03,
151
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04,
152
+ ]);
146
153
  const FIRMWARE_TARGET_ID_BY_NAME = new Map<string, DeviceFirmwareTargetType>(
147
154
  Object.entries(ProtocolV2FirmwareTargetType).flatMap(([key, value]) =>
148
- VALID_FIRMWARE_TARGET_IDS.has(value)
149
- ? [[key, value as DeviceFirmwareTargetType]]
150
- : []
155
+ INSTALLABLE_FIRMWARE_TARGET_IDS.has(value) ? [[key, value as DeviceFirmwareTargetType]] : []
151
156
  )
152
157
  );
153
158
 
@@ -160,14 +165,12 @@ function normalizeTargetId(
160
165
  }
161
166
  const named = typeof value === 'string' ? FIRMWARE_TARGET_ID_BY_NAME.get(value) : undefined;
162
167
  const numeric = named ?? (typeof value === 'number' ? value : Number(value));
163
- // 校验值域:仅接受 DeviceFirmwareTargetType 中定义的 target id,
164
- // 不再放行任意非负整数。
165
- if (Number.isSafeInteger(numeric) && VALID_FIRMWARE_TARGET_IDS.has(numeric)) {
168
+ if (Number.isSafeInteger(numeric) && INSTALLABLE_FIRMWARE_TARGET_IDS.has(numeric)) {
166
169
  return numeric as DeviceFirmwareTargetType;
167
170
  }
168
171
  throw invalidParameter(
169
- `Parameter [${name}] must be a valid firmware target id (one of ${[
170
- ...VALID_FIRMWARE_TARGET_IDS,
172
+ `Parameter [${name}] must be an installable firmware target id (one of ${[
173
+ ...INSTALLABLE_FIRMWARE_TARGET_IDS,
171
174
  ].join(', ')}).`
172
175
  );
173
176
  }
@@ -107,8 +107,6 @@
107
107
  "MessageType_FirmwareHash": 89,
108
108
  "MessageType_UnlockPath": 93,
109
109
  "MessageType_UnlockedPathRequest": 94,
110
- "MessageType_UnLockDevice": 10030,
111
- "MessageType_UnLockDeviceResponse": 10031,
112
110
  "MessageType_SetU2FCounter": 63,
113
111
  "MessageType_GetNextU2FCounter": 80,
114
112
  "MessageType_NextU2FCounter": 81,
@@ -197,6 +195,7 @@
197
195
  "MessageType_EthereumGnosisSafeTxAck": 20118,
198
196
  "MessageType_EthereumGnosisSafeTxRequest": 20119,
199
197
  "MessageType_EthereumSignTxEIP7702OneKey": 20120,
198
+ "MessageType_EthereumSignTypedDataQR": 20121,
200
199
  "MessageType_NEMGetAddress": 67,
201
200
  "MessageType_NEMAddress": 68,
202
201
  "MessageType_NEMSignTx": 69,
@@ -454,6 +453,8 @@
454
453
  "MessageType_UiviewConfirmTxRequest": 30202,
455
454
  "MessageType_UiviewConfirmSignMessageRequest": 30203,
456
455
  "MessageType_UiviewResponse": 30204,
456
+ "MessageType_UnLockDevice": 10030,
457
+ "MessageType_UnLockDeviceResponse": 10031,
457
458
  "MessageType_DeviceFactoryInfoSet": 60000,
458
459
  "MessageType_DeviceFactoryInfoGet": 60001,
459
460
  "MessageType_DeviceFactoryInfo": 60002,
@@ -478,6 +479,12 @@
478
479
  "MessageType_Wallpaper": 60432,
479
480
  "MessageType_DeviceInfoGet": 60600,
480
481
  "MessageType_DeviceInfo": 60601,
482
+ "MessageType_DeviceStatusGet": 60602,
483
+ "MessageType_DeviceStatus": 60603,
484
+ "MessageType_DevGetOnboardingStatus": 60604,
485
+ "MessageType_DevOnboardingStatus": 60605,
486
+ "MessageType_DeviceSessionGet": 60606,
487
+ "MessageType_DeviceSession": 60607,
481
488
  "MessageType_FilesystemPermissionFix": 60800,
482
489
  "MessageType_FilesystemPathInfo": 60801,
483
490
  "MessageType_FilesystemPathInfoQuery": 60802,
@@ -492,7 +499,8 @@
492
499
  "MessageType_FilesystemFormat": 60811,
493
500
  "MessageType_DeviceFirmwareUpdateRequest": 61000,
494
501
  "MessageType_DeviceFirmwareUpdateStatusGet": 61001,
495
- "MessageType_DeviceFirmwareUpdateStatus": 61002
502
+ "MessageType_DeviceFirmwareUpdateStatus": 61002,
503
+ "MessageType_PortfolioUpdate": 61200
496
504
  },
497
505
  "reserved": [
498
506
  [90, 92],
@@ -5395,6 +5403,37 @@
5395
5403
  }
5396
5404
  }
5397
5405
  },
5406
+ "EthereumSignTypedDataQR": {
5407
+ "fields": {
5408
+ "address_n": {
5409
+ "rule": "repeated",
5410
+ "type": "uint32",
5411
+ "id": 1,
5412
+ "options": {
5413
+ "packed": false
5414
+ }
5415
+ },
5416
+ "json_data": {
5417
+ "type": "bytes",
5418
+ "id": 2
5419
+ },
5420
+ "chain_id": {
5421
+ "type": "uint64",
5422
+ "id": 3
5423
+ },
5424
+ "metamask_v4_compat": {
5425
+ "type": "bool",
5426
+ "id": 4,
5427
+ "options": {
5428
+ "default": true
5429
+ }
5430
+ },
5431
+ "request_id": {
5432
+ "type": "bytes",
5433
+ "id": 5
5434
+ }
5435
+ }
5436
+ },
5398
5437
  "EthereumGetPublicKeyOneKey": {
5399
5438
  "fields": {
5400
5439
  "address_n": {
@@ -11120,6 +11159,26 @@
11120
11159
  }
11121
11160
  }
11122
11161
  },
11162
+ "ViewRawData": {
11163
+ "fields": {
11164
+ "initial_data": {
11165
+ "rule": "required",
11166
+ "type": "string",
11167
+ "id": 1
11168
+ },
11169
+ "placeholder": {
11170
+ "rule": "required",
11171
+ "type": "uint32",
11172
+ "id": 2
11173
+ }
11174
+ }
11175
+ },
11176
+ "ViewSignLayout": {
11177
+ "values": {
11178
+ "LayoutDefault": 0,
11179
+ "LayoutSafeTxCreate": 1
11180
+ }
11181
+ },
11123
11182
  "ViewSignPage": {
11124
11183
  "fields": {
11125
11184
  "title": {
@@ -11139,6 +11198,24 @@
11139
11198
  "tip": {
11140
11199
  "type": "ViewTip",
11141
11200
  "id": 4
11201
+ },
11202
+ "raw_data": {
11203
+ "type": "ViewRawData",
11204
+ "id": 5
11205
+ },
11206
+ "slide_to_confirm": {
11207
+ "type": "bool",
11208
+ "id": 6,
11209
+ "options": {
11210
+ "default": true
11211
+ }
11212
+ },
11213
+ "layout": {
11214
+ "type": "ViewSignLayout",
11215
+ "id": 7,
11216
+ "options": {
11217
+ "default": "LayoutDefault"
11218
+ }
11142
11219
  }
11143
11220
  }
11144
11221
  },
@@ -11892,6 +11969,46 @@
11892
11969
  }
11893
11970
  }
11894
11971
  },
11972
+ "DeviceStatusGet": {
11973
+ "fields": {}
11974
+ },
11975
+ "DevOnboardingStage": {
11976
+ "values": {
11977
+ "DEV_ONBOARDING_STAGE_UNKNOWN": 0,
11978
+ "DEV_ONBOARDING_STAGE_SAFETY_CHECK": 1,
11979
+ "DEV_ONBOARDING_STAGE_PERSONALIZATION": 2,
11980
+ "DEV_ONBOARDING_STAGE_SELECT_SETUP_METHOD": 3,
11981
+ "DEV_ONBOARDING_STAGE_NEW_DEVICE": 4,
11982
+ "DEV_ONBOARDING_STAGE_SELECT_RESTORE_METHOD": 5,
11983
+ "DEV_ONBOARDING_STAGE_RESTORE_MNEMONIC": 6,
11984
+ "DEV_ONBOARDING_STAGE_RESTORE_SEEDCARD": 7,
11985
+ "DEV_ONBOARDING_STAGE_WALLET_READY": 8,
11986
+ "DEV_ONBOARDING_STAGE_SEEDCARD_BACKUP_PROMPT": 9,
11987
+ "DEV_ONBOARDING_STAGE_SELECT_SEEDCARD_BACKUP_METHOD": 10,
11988
+ "DEV_ONBOARDING_STAGE_SEEDCARD_BACKUP": 11,
11989
+ "DEV_ONBOARDING_STAGE_DONE": 12
11990
+ }
11991
+ },
11992
+ "DevGetOnboardingStatus": {
11993
+ "fields": {}
11994
+ },
11995
+ "DevOnboardingStatus": {
11996
+ "fields": {
11997
+ "stage": {
11998
+ "rule": "required",
11999
+ "type": "DevOnboardingStage",
12000
+ "id": 1
12001
+ },
12002
+ "status_code": {
12003
+ "type": "uint32",
12004
+ "id": 2
12005
+ },
12006
+ "detail_code": {
12007
+ "type": "uint32",
12008
+ "id": 3
12009
+ }
12010
+ }
12011
+ },
11895
12012
  "FilesystemPermissionFix": {
11896
12013
  "fields": {}
11897
12014
  },
@@ -12105,6 +12222,20 @@
12105
12222
  }
12106
12223
  },
12107
12224
  "FilesystemFormat": {
12225
+ "fields": {
12226
+ "data": {
12227
+ "rule": "required",
12228
+ "type": "bool",
12229
+ "id": 1
12230
+ },
12231
+ "user": {
12232
+ "rule": "required",
12233
+ "type": "bool",
12234
+ "id": 2
12235
+ }
12236
+ }
12237
+ },
12238
+ "PortfolioUpdate": {
12108
12239
  "fields": {}
12109
12240
  },
12110
12241
  "google": {
@@ -7784,6 +7784,14 @@
7784
7784
  "passphrase_state": {
7785
7785
  "type": "string",
7786
7786
  "id": 1
7787
+ },
7788
+ "_only_main_pin": {
7789
+ "type": "bool",
7790
+ "id": 2
7791
+ },
7792
+ "allow_create_attach_pin": {
7793
+ "type": "bool",
7794
+ "id": 3
7787
7795
  }
7788
7796
  }
7789
7797
  },
@@ -9,6 +9,7 @@ import {
9
9
  HardwareError,
10
10
  HardwareErrorCode,
11
11
  createDeferred,
12
+ createDeviceNotSupportMethodError,
12
13
  } from '@onekeyfe/hd-shared';
13
14
 
14
15
  import {
@@ -31,6 +32,7 @@ import {
31
32
  import { generateInstanceId } from '../utils/tracing';
32
33
  // eslint-disable-next-line import/no-cycle
33
34
  import { DeviceCommands } from './DeviceCommands';
35
+ import { deviceWalletSessionStore } from './DeviceWalletSessionStore';
34
36
  import {
35
37
  type DeviceFirmwareRange,
36
38
  DeviceModelToTypes,
@@ -53,6 +55,7 @@ import {
53
55
  PROTOCOL_V2_STATUS_DEVICE_INFO_REQUEST,
54
56
  requestProtocolV2DeviceInfo,
55
57
  } from '../protocols/protocol-v2/features';
58
+ import { refreshProtocolV2DeviceStatus } from '../protocols/protocol-v2/walletSession';
56
59
  import { buildProtocolV1FeaturesPayload, buildProtocolV2FeaturesPayload } from '../deviceProfile';
57
60
 
58
61
  import type { PROTO } from '../constants';
@@ -65,6 +68,7 @@ import type { PassphrasePromptResponse } from './DeviceCommands';
65
68
  import type { Deferred, HardwareConnectProtocol } from '@onekeyfe/hd-shared';
66
69
  import type {
67
70
  OneKeyDeviceInfo as DeviceDescriptor,
71
+ DeviceStatus,
68
72
  ProtocolV2DeviceInfo,
69
73
  Success,
70
74
  } from '@onekeyfe/hd-transport';
@@ -119,8 +123,6 @@ export interface Device {
119
123
  emit<K extends keyof DeviceEvents>(type: K, ...args: DeviceEvents[K]): boolean;
120
124
  }
121
125
 
122
- const deviceSessionCache: Record<string, string> = {};
123
-
124
126
  /**
125
127
  * Pre-populate the device session cache with a known session ID.
126
128
  *
@@ -138,8 +140,7 @@ export function preloadSessionCache(
138
140
  passphraseState: string,
139
141
  sessionId: string
140
142
  ): void {
141
- const key = `${deviceId}@${passphraseState}`;
142
- deviceSessionCache[key] = sessionId;
143
+ deviceWalletSessionStore.set(deviceId, passphraseState, sessionId);
143
144
  }
144
145
 
145
146
  export class Device extends EventEmitter {
@@ -265,7 +266,7 @@ export class Device extends EventEmitter {
265
266
  const connectId = this.getConnectId();
266
267
  const deviceId = this.getCurrentDeviceId() || null;
267
268
 
268
- const features = this.features;
269
+ const { features } = this;
269
270
 
270
271
  return {
271
272
  /** Android uses Mac address, iOS uses uuid, USB uses uuid */
@@ -628,13 +629,6 @@ export class Device extends EventEmitter {
628
629
  };
629
630
  }
630
631
 
631
- private generateStateKey(deviceId: string, passphraseState?: string) {
632
- if (passphraseState) {
633
- return `${deviceId}@${passphraseState}`;
634
- }
635
- return deviceId;
636
- }
637
-
638
632
  private getSessionCacheDeviceKey(_deviceId?: string) {
639
633
  const deviceId = _deviceId || this.getCurrentDeviceId();
640
634
  if (deviceId) return deviceId;
@@ -645,7 +639,6 @@ export class Device extends EventEmitter {
645
639
  }
646
640
 
647
641
  getInternalState(_deviceId?: string) {
648
- Log.debug('getInternalState session cache: ', deviceSessionCache);
649
642
  Log.debug(
650
643
  'getInternalState session param: ',
651
644
  `device_id: ${_deviceId}`,
@@ -663,8 +656,7 @@ export class Device extends EventEmitter {
663
656
  // downstream call carries it and hits the primary lookup below.
664
657
  if (!this.passphraseState) return undefined;
665
658
 
666
- const usePassKey = this.generateStateKey(deviceId, this.passphraseState);
667
- return deviceSessionCache[usePassKey];
659
+ return deviceWalletSessionStore.get(deviceId, this.passphraseState);
668
660
  }
669
661
 
670
662
  // attach to pin to fix internal state
@@ -680,36 +672,26 @@ export class Device extends EventEmitter {
680
672
  `device_id: ${deviceId}`,
681
673
  `enablePassphrase: ${enablePassphrase}`,
682
674
  `passphraseState: ${passphraseState}`,
683
- `sessionId: ${sessionId}`,
684
- `featuresSessionId: ${featuresSessionId}`
675
+ `hasSessionId: ${Boolean(sessionId)}`,
676
+ `hasFeaturesSessionId: ${Boolean(featuresSessionId)}`
685
677
  );
686
678
 
687
679
  const cacheDeviceKey = this.getSessionCacheDeviceKey(deviceId);
688
680
  if (!cacheDeviceKey) return;
689
681
 
690
682
  if (enablePassphrase) {
691
- // update the sessionId
692
- if (sessionId) {
693
- deviceSessionCache[this.generateStateKey(cacheDeviceKey, passphraseState)] = sessionId;
694
- } else if (featuresSessionId) {
695
- deviceSessionCache[this.generateStateKey(cacheDeviceKey, passphraseState)] =
696
- featuresSessionId;
697
- }
698
- }
699
-
700
- // delete the old sessionId
701
- const oldKey = `${cacheDeviceKey}`;
702
- if (deviceSessionCache[oldKey]) {
703
- delete deviceSessionCache[oldKey];
683
+ const walletSessionId =
684
+ sessionId || featuresSessionId || deviceWalletSessionStore.getPending(cacheDeviceKey);
685
+ deviceWalletSessionStore.set(cacheDeviceKey, passphraseState, walletSessionId ?? undefined);
704
686
  }
705
687
 
706
- Log.debug('updateInternalState session cache: ', deviceSessionCache);
688
+ deviceWalletSessionStore.deletePending(cacheDeviceKey);
707
689
  }
708
690
 
709
691
  private setInternalState(state: string, initSession?: boolean) {
710
692
  Log.debug(
711
693
  'setInternalState session param: ',
712
- `state: ${state}`,
694
+ `hasState: ${Boolean(state)}`,
713
695
  `initSession: ${initSession}`,
714
696
  `deviceId: ${this.features?.deviceId}`,
715
697
  `passphraseState: ${this.passphraseState}`
@@ -720,12 +702,11 @@ export class Device extends EventEmitter {
720
702
  const deviceId = this.getSessionCacheDeviceKey();
721
703
  if (!deviceId) return;
722
704
 
723
- const key = this.generateStateKey(deviceId, this.passphraseState);
724
-
725
- if (state) {
726
- deviceSessionCache[key] = state;
705
+ if (this.passphraseState) {
706
+ deviceWalletSessionStore.set(deviceId, this.passphraseState, state);
707
+ } else if (initSession) {
708
+ deviceWalletSessionStore.setPending(deviceId, state);
727
709
  }
728
- Log.debug('setInternalState done session cache: ', deviceSessionCache);
729
710
  }
730
711
 
731
712
  clearInternalState(_deviceId?: string) {
@@ -733,12 +714,10 @@ export class Device extends EventEmitter {
733
714
 
734
715
  const deviceId = this.getSessionCacheDeviceKey(_deviceId);
735
716
  if (!deviceId) return;
736
- const key = `${deviceId}`;
737
- delete deviceSessionCache[key];
717
+ deviceWalletSessionStore.deletePending(deviceId);
738
718
 
739
719
  if (this.passphraseState) {
740
- const usePassKey = this.generateStateKey(deviceId, this.passphraseState);
741
- delete deviceSessionCache[usePassKey];
720
+ deviceWalletSessionStore.delete(deviceId, this.passphraseState);
742
721
  }
743
722
  }
744
723
 
@@ -867,6 +846,7 @@ export class Device extends EventEmitter {
867
846
  }
868
847
 
869
848
  _updateFeatures(protoFeatures: PROTO.Features | Features, initSession?: boolean) {
849
+ const previousCacheDeviceKey = this.getSessionCacheDeviceKey();
870
850
  let feat =
871
851
  'protocol' in protoFeatures
872
852
  ? protoFeatures
@@ -876,28 +856,50 @@ export class Device extends EventEmitter {
876
856
  if (this.features?.sessionId && !feat.sessionId) {
877
857
  feat.sessionId = this.features.sessionId;
878
858
  }
879
- if (this.getCurrentDeviceId() && feat.sessionId) {
880
- this.setInternalState(feat.sessionId, initSession);
881
- }
882
859
  feat.unlocked = feat.unlocked ?? true;
883
860
 
884
861
  feat = fixFeaturesFirmwareVersion(feat);
885
862
 
886
863
  this.features = feat;
864
+ const nextCacheDeviceKey = this.getSessionCacheDeviceKey();
865
+ if (previousCacheDeviceKey && nextCacheDeviceKey) {
866
+ deviceWalletSessionStore.migrateDeviceKey(previousCacheDeviceKey, nextCacheDeviceKey);
867
+ }
868
+ if (feat.deviceId && feat.sessionId) {
869
+ this.setInternalState(feat.sessionId, initSession);
870
+ }
887
871
  this.featuresNeedsReload = false;
888
872
  this.emit(DEVICE.FEATURES, this, feat);
889
873
  }
890
874
 
891
875
  updateProtocolV2Features(deviceInfo?: ProtocolV2DeviceInfo) {
876
+ const previousCacheDeviceKey = this.getSessionCacheDeviceKey();
892
877
  const features = fixFeaturesFirmwareVersion(
893
878
  buildProtocolV2FeaturesPayload(deviceInfo, this.features)
894
879
  );
895
880
  this.features = features;
881
+ const nextCacheDeviceKey = this.getSessionCacheDeviceKey();
882
+ if (previousCacheDeviceKey && nextCacheDeviceKey) {
883
+ deviceWalletSessionStore.migrateDeviceKey(previousCacheDeviceKey, nextCacheDeviceKey);
884
+ }
896
885
  this.featuresNeedsReload = false;
897
886
  this.emit(DEVICE.FEATURES, this, features);
898
887
  return features;
899
888
  }
900
889
 
890
+ updateProtocolV2Status(status: DeviceStatus) {
891
+ const previousDeviceInfo = this.features?.raw?.protocolV2DeviceInfo;
892
+ const previousStatus = previousDeviceInfo?.status;
893
+ return this.updateProtocolV2Features({
894
+ ...(previousDeviceInfo ?? {}),
895
+ protocol_version: previousDeviceInfo?.protocol_version ?? this.features?.protocolVersion ?? 2,
896
+ status: {
897
+ ...previousStatus,
898
+ ...status,
899
+ },
900
+ });
901
+ }
902
+
901
903
  /**
902
904
  * 暂时只在 acquire 后更新 Session ID
903
905
  * 后续看是否有需要依据 listen 返回结果更新
@@ -1178,8 +1180,7 @@ export class Device extends EventEmitter {
1178
1180
  }
1179
1181
 
1180
1182
  supportUnlockVersionRange(): DeviceFirmwareRange {
1181
- // 仅适用于 Protocol V1 的 Pro 系列;Pro2 走独立版本线,
1182
- // 且 Protocol V2 固件从首个版本即支持 UnLockDevice(见 unlockDevice 的 isProtocolV2 短路)。
1183
+ // 仅适用于 Protocol V1 的 Pro 系列;Pro2 走独立的 Protocol V2 解锁流程。
1183
1184
  return {
1184
1185
  pro: {
1185
1186
  min: '4.15.0',
@@ -1188,6 +1189,23 @@ export class Device extends EventEmitter {
1188
1189
  }
1189
1190
 
1190
1191
  async unlockDevice() {
1192
+ if (this.isProtocolV2()) {
1193
+ try {
1194
+ await this.commands.typedCall('UnLockDevice', 'UnLockDeviceResponse');
1195
+ } catch (error) {
1196
+ const errorText =
1197
+ error instanceof Error
1198
+ ? `${error.name} ${error.message}`
1199
+ : String((error as { message?: unknown } | null)?.message ?? error ?? '');
1200
+ if (errorText.includes('Failure_UnexpectedMessage')) {
1201
+ throw createDeviceNotSupportMethodError('deviceUnlock', this.getCurrentFirmwareType());
1202
+ }
1203
+ throw error;
1204
+ }
1205
+
1206
+ return refreshProtocolV2DeviceStatus(this);
1207
+ }
1208
+
1191
1209
  const firmwareVersion = this.getCurrentFirmwareVersionString() ?? '0.0.0';
1192
1210
  const versionRange = this.getCurrentMethodVersionRange(
1193
1211
  type => this.supportUnlockVersionRange()[type]
@@ -1197,9 +1215,7 @@ export class Device extends EventEmitter {
1197
1215
  this.features,
1198
1216
  Enum_Capability.Capability_AttachToPin
1199
1217
  );
1200
- // Pro2 (Protocol V2) 版本线独立于 Pro 系列,固件从首个版本即支持 UnLockDevice
1201
1218
  const supportUnlock =
1202
- this.isProtocolV2() ||
1203
1219
  supportAttachPinCapability ||
1204
1220
  (versionRange &&
1205
1221
  semver.valid(firmwareVersion) &&
@@ -1221,14 +1237,6 @@ export class Device extends EventEmitter {
1221
1237
  return Promise.resolve(features);
1222
1238
  }
1223
1239
 
1224
- // legacy 解锁探测仅适用于 Protocol V1 老固件;V2 固件必然支持 UnLockDevice
1225
- if (this.isProtocolV2()) {
1226
- throw ERRORS.TypedError(
1227
- HardwareErrorCode.RuntimeError,
1228
- 'unlock device error: device firmware does not support UnLockDevice'
1229
- );
1230
- }
1231
-
1232
1240
  const { type } = await this.commands.typedCall('GetAddress', 'Address', {
1233
1241
  address_n: [toHardened(44), toHardened(1), toHardened(0), 0, 0],
1234
1242
  coin_name: 'Testnet',