@onekeyfe/hd-core 1.2.0-alpha.101 → 1.2.0-alpha.102

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 (50) hide show
  1. package/__tests__/bridgeBinaryPayload.test.ts +173 -0
  2. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +2 -0
  3. package/__tests__/device-pool-state.test.ts +29 -0
  4. package/__tests__/get-device-state.test.ts +102 -13
  5. package/__tests__/protocol-v2-legacy-recovery.test.ts +29 -0
  6. package/__tests__/protocol-v2.test.ts +234 -1
  7. package/__tests__/refresh-device-state.test.ts +4 -1
  8. package/__tests__/search-devices.test.ts +2 -0
  9. package/__tests__/ton-sign-message.test.ts +84 -0
  10. package/dist/api/FirmwareUpdateV4.d.ts +3 -0
  11. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  12. package/dist/api/GetDeviceState.d.ts.map +1 -1
  13. package/dist/api/SearchDevices.d.ts.map +1 -1
  14. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
  15. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  16. package/dist/api/utils.d.ts.map +1 -1
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/protocolV2LegacyRecovery.d.ts +5 -0
  19. package/dist/core/protocolV2LegacyRecovery.d.ts.map +1 -0
  20. package/dist/device/Device.d.ts +9 -2
  21. package/dist/device/Device.d.ts.map +1 -1
  22. package/dist/device/DevicePool.d.ts +1 -1
  23. package/dist/device/DevicePool.d.ts.map +1 -1
  24. package/dist/index.d.ts +12 -2
  25. package/dist/index.js +243 -60
  26. package/dist/protocols/protocol-v2/features.d.ts +8 -0
  27. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  28. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  29. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  30. package/dist/topLevelInject.d.ts.map +1 -1
  31. package/dist/types/api/getDeviceState.d.ts +1 -0
  32. package/dist/types/api/getDeviceState.d.ts.map +1 -1
  33. package/dist/utils/bridgeBinaryPayload.d.ts +3 -0
  34. package/dist/utils/bridgeBinaryPayload.d.ts.map +1 -0
  35. package/package.json +4 -4
  36. package/src/api/FirmwareUpdateV4.ts +70 -23
  37. package/src/api/GetDeviceState.ts +1 -0
  38. package/src/api/SearchDevices.ts +2 -0
  39. package/src/api/firmware/protocolV2Release.ts +1 -0
  40. package/src/api/ton/TonSignMessage.ts +5 -3
  41. package/src/api/utils.ts +5 -2
  42. package/src/core/index.ts +4 -0
  43. package/src/core/protocolV2LegacyRecovery.ts +12 -0
  44. package/src/device/Device.ts +78 -20
  45. package/src/device/DevicePool.ts +17 -5
  46. package/src/protocols/protocol-v2/features.ts +10 -0
  47. package/src/protocols/protocol-v2/index.ts +2 -0
  48. package/src/topLevelInject.ts +4 -2
  49. package/src/types/api/getDeviceState.ts +2 -0
  50. package/src/utils/bridgeBinaryPayload.ts +137 -0
@@ -28,6 +28,7 @@ import { DevicePool } from '../device/DevicePool';
28
28
  import {
29
29
  PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
30
30
  ProtocolV2FirmwareTargetType,
31
+ isLegacyProtocolV2ProtocolInfo,
31
32
  } from '../protocols/protocol-v2';
32
33
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
33
34
  import {
@@ -393,6 +394,13 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
393
394
  );
394
395
  };
395
396
 
397
+ const isProtocolV2FirmwareUpdateEndpointUnavailable = (error: unknown) => {
398
+ const message = getProtocolV2UnknownErrorText(error).toLowerCase();
399
+ return (
400
+ message.includes('handler not registered') || message.includes('message handler not found')
401
+ );
402
+ };
403
+
396
404
  const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
397
405
  error instanceof HardwareError &&
398
406
  (error.errorCode === HardwareErrorCode.FirmwareError ||
@@ -587,6 +595,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
587
595
 
588
596
  private protocolV2ExecutionInLoader = false;
589
597
 
598
+ private protocolV2LegacyDirectUpdate = false;
599
+
590
600
  private protocolV2BootResourceStagingSafe = false;
591
601
 
592
602
  private protocolV2CompletedTargetVersions = new Map<number, number>();
@@ -2067,21 +2077,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2067
2077
  return this.device.features?.mode === 'romloader';
2068
2078
  }
2069
2079
 
2070
- async enterProtocolV2BootloaderMode() {
2071
- // romloader is the first update environment and forwards targets to bootloader.
2072
- // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
2073
- if (this.isProtocolV2RomloaderMode()) {
2074
- Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
2075
- this.protocolV2ExecutionInLoader = true;
2076
- return false;
2077
- }
2078
- if (this.isProtocolV2BootloaderMode()) {
2079
- Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
2080
- this.protocolV2ExecutionInLoader = true;
2081
- this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
2082
- return false;
2083
- }
2080
+ private isLegacyProtocolV2Runtime() {
2081
+ const protocolInfo = this.device.state?.raw?.protocolV2ProtocolInfo;
2082
+ return protocolInfo ? isLegacyProtocolV2ProtocolInfo(protocolInfo) : false;
2083
+ }
2084
2084
 
2085
+ private async rebootProtocolV2ToBootloader() {
2085
2086
  try {
2086
2087
  this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
2087
2088
  await this.protocolV2Reboot(DeviceRebootType.Bootloader);
@@ -2099,6 +2100,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2099
2100
  }
2100
2101
  }
2101
2102
 
2103
+ async enterProtocolV2BootloaderMode() {
2104
+ this.protocolV2LegacyDirectUpdate = false;
2105
+ // romloader is the first update environment and forwards targets to bootloader.
2106
+ // It rejects DeviceRebootType.Bootloader, so reuse the current connection.
2107
+ if (this.isProtocolV2RomloaderMode()) {
2108
+ Log.debug('Protocol V2 device is in romloader mode; start firmware update directly');
2109
+ this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
2110
+ this.protocolV2ExecutionInLoader = true;
2111
+ return false;
2112
+ }
2113
+ if (this.isProtocolV2BootloaderMode()) {
2114
+ Log.debug('Protocol V2 device is already in bootloader mode, skip reboot');
2115
+ this.protocolV2LegacyDirectUpdate = this.isLegacyProtocolV2Runtime();
2116
+ this.protocolV2ExecutionInLoader = true;
2117
+ this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
2118
+ return false;
2119
+ }
2120
+
2121
+ return this.rebootProtocolV2ToBootloader();
2122
+ }
2123
+
2102
2124
  private async waitForProtocolV2BootloaderMode(
2103
2125
  timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
2104
2126
  retryInterval = 1000
@@ -2120,7 +2142,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2120
2142
  this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
2121
2143
  const features = await this.device.probeProtocolV2RuntimeState(
2122
2144
  deviceInfo,
2123
- PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2145
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
2146
+ { allowLegacyProtocolV2ProtocolInfo: true }
2124
2147
  );
2125
2148
  if (features?.mode === 'bootloader') {
2126
2149
  return features;
@@ -2809,7 +2832,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2809
2832
  private async probeProtocolV2NormalMode(deviceInfo: ProtocolV2DeviceInfo) {
2810
2833
  const features = await this.device.probeProtocolV2RuntimeState(
2811
2834
  deviceInfo,
2812
- PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2835
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
2836
+ { allowLegacyProtocolV2ProtocolInfo: true }
2813
2837
  );
2814
2838
  return features.mode === 'normal' && !features.bootloaderMode;
2815
2839
  }
@@ -2871,7 +2895,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2871
2895
  this.assertProtocolV2DeviceInfoIdentity(deviceInfo);
2872
2896
  const features = await this.device.probeProtocolV2RuntimeState(
2873
2897
  deviceInfo,
2874
- PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT
2898
+ PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
2899
+ { allowLegacyProtocolV2ProtocolInfo: true }
2875
2900
  );
2876
2901
  if (features.mode === 'normal' && !features.bootloaderMode) {
2877
2902
  return features;
@@ -3044,12 +3069,34 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3044
3069
  targets: Array<{ target_id: number; path: string }>;
3045
3070
  }) {
3046
3071
  const commands = this.device.getCommands();
3047
- const response: ProtocolV2FirmwareUpdateStartResponse = await commands.typedCall(
3048
- 'DeviceFirmwareUpdateRequest',
3049
- 'Success',
3050
- { targets },
3051
- { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
3052
- );
3072
+ const startUpdate = () =>
3073
+ commands.typedCall(
3074
+ 'DeviceFirmwareUpdateRequest',
3075
+ 'Success',
3076
+ { targets },
3077
+ { timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT }
3078
+ );
3079
+ let response: ProtocolV2FirmwareUpdateStartResponse;
3080
+ try {
3081
+ response = await startUpdate();
3082
+ } catch (error) {
3083
+ if (
3084
+ !this.protocolV2LegacyDirectUpdate ||
3085
+ !isProtocolV2FirmwareUpdateEndpointUnavailable(error)
3086
+ ) {
3087
+ throw error;
3088
+ }
3089
+
3090
+ // Both legacy loaders register this request. A missing handler therefore identifies
3091
+ // a legacy App before dispatch, so it is safe to reboot and retry the unhandled request.
3092
+ this.protocolV2LegacyDirectUpdate = false;
3093
+ Log.debug(
3094
+ '[FirmwareUpdateV4] legacy App does not expose DeviceFirmwareUpdateRequest; rebooting to bootloader'
3095
+ );
3096
+ await this.rebootProtocolV2ToBootloader();
3097
+ response = await startUpdate();
3098
+ }
3099
+ this.protocolV2LegacyDirectUpdate = false;
3053
3100
  // Success acknowledges that the device accepted installation. End confirmation
3054
3101
  // and begin status polling only after receiving this ACK.
3055
3102
  this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdating);
@@ -43,6 +43,7 @@ export default class GetDeviceState extends BaseMethod<
43
43
  async run() {
44
44
  const state = await this.device.getDeviceState({
45
45
  refreshSections: SCOPE_SECTIONS[this.params.scope],
46
+ ...(this.params.scope === 'firmware' ? { allowLegacyProtocolV2ProtocolInfo: true } : {}),
46
47
  });
47
48
  if (
48
49
  this.params.scope === 'settings' &&
@@ -63,6 +63,8 @@ export default class SearchDevices extends BaseMethod {
63
63
  connectProtocol: undefined,
64
64
  forceProtocolDetection: true,
65
65
  refreshRuntimeState: true,
66
+ // Old loader firmware predates build_fingerprint. Keep this recovery-only.
67
+ allowLegacyProtocolV2ProtocolInfo: true,
66
68
  });
67
69
  deviceList.push(...result.deviceList);
68
70
  } catch (error) {
@@ -60,6 +60,7 @@ export async function loadProtocolV2FirmwareReleaseContext({
60
60
  refreshSections: checkFirmwareHash
61
61
  ? ['identity', 'versions', 'verification']
62
62
  : ['identity', 'versions'],
63
+ allowLegacyProtocolV2ProtocolInfo: true,
63
64
  });
64
65
  if (state.identity.deviceType !== 'pro2' && state.identity.deviceType !== 'neo') {
65
66
  throw ERRORS.TypedError(
@@ -174,12 +174,14 @@ export default class TonSignMessage extends BaseMethod<HardwareTonSignMessage> {
174
174
  if (!request.init_data_length) {
175
175
  const deviceType = this.device.getCurrentDeviceType();
176
176
  const hasClassic = DeviceModelToTypes.model_classic1s.includes(deviceType);
177
- // use signing_message_repr sign, not exists signning_message, skip validate
178
- const hasSigningMessageRepr = request.signning_message == null;
177
+ const signingMessage = request.signing_message ?? request.signning_message;
178
+ // Blind signing omits the signing message in both protocol schemas.
179
+ const shouldSkipValidation = signingMessage == null;
179
180
 
180
181
  return Promise.resolve({
181
182
  ...request,
182
- skip_validate: hasClassic || hasSigningMessageRepr,
183
+ signing_message: signingMessage,
184
+ skip_validate: hasClassic || shouldSkipValidation,
183
185
  });
184
186
  }
185
187
 
package/src/api/utils.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import * as ApiMethods from './index';
4
+ import { decodeBridgeBinaryPayload } from '../utils/bridgeBinaryPayload';
4
5
 
5
6
  import type { BaseMethod } from './BaseMethod';
6
7
  import type { IFrameCallMessage } from '../events';
@@ -11,14 +12,16 @@ type MethodConstructor = new (message: IFrameCallMessage & { id?: number }) => B
11
12
  const publicMethodRegistry = ApiMethods as unknown as Record<string, MethodConstructor>;
12
13
 
13
14
  export function findMethod(message: IFrameCallMessage & { id?: number }): BaseMethod<any> {
14
- const { method } = message.payload;
15
+ const payload = decodeBridgeBinaryPayload(message.payload) as IFrameCallMessage['payload'];
16
+ const normalizedMessage = payload === message.payload ? message : { ...message, payload };
17
+ const { method } = payload;
15
18
  if (typeof method !== 'string') {
16
19
  throw ERRORS.TypedError(HardwareErrorCode.CallMethodInvalidParameter, 'Method is not set');
17
20
  }
18
21
 
19
22
  const MethodConstructor = publicMethodRegistry[method];
20
23
  if (MethodConstructor) {
21
- return new MethodConstructor(message);
24
+ return new MethodConstructor(normalizedMessage);
22
25
  }
23
26
 
24
27
  throw ERRORS.TypedError(
package/src/core/index.ts CHANGED
@@ -64,6 +64,7 @@ import {
64
64
  isProtocolV2UiEnabled,
65
65
  } from '../protocols/protocol-v2/uiInteraction';
66
66
  import { createUiProgressMessageFilter } from '../utils/uiProgressThrottle';
67
+ import { isLegacyProtocolV2FirmwareRecoveryMethod } from './protocolV2LegacyRecovery';
67
68
 
68
69
  import type { ConnectSettings, Features, KnownDevice } from '../types';
69
70
  import type { CoreMessage, IFrameCallMessage, UiPromise, UiPromiseResponse } from '../events';
@@ -109,6 +110,9 @@ const parseInitOptions = (method?: BaseMethod): InitOptions => ({
109
110
  connectProtocol: method?.payload.connectProtocol,
110
111
  forceProtocolDetection: method?.payload.forceProtocolDetection,
111
112
  protocolV2DeviceInfoTimeoutMs: method?.payload.protocolV2DeviceInfoTimeoutMs,
113
+ ...(isLegacyProtocolV2FirmwareRecoveryMethod(method)
114
+ ? { allowLegacyProtocolV2ProtocolInfo: true }
115
+ : {}),
112
116
  });
113
117
 
114
118
  let _core: Core | undefined;
@@ -0,0 +1,12 @@
1
+ import type { BaseMethod } from '../api/BaseMethod';
2
+
3
+ type CoreMethodDescriptor = Pick<BaseMethod, 'name' | 'payload'>;
4
+
5
+ /**
6
+ * TEMPORARY COMPATIBILITY: Legacy ProtocolInfo is accepted only by the
7
+ * firmware recovery entry points used before and during Protocol V2 updates.
8
+ */
9
+ export const isLegacyProtocolV2FirmwareRecoveryMethod = (method?: CoreMethodDescriptor): boolean =>
10
+ method?.name === 'firmwareUpdateV4' ||
11
+ method?.name === 'checkAllFirmwareRelease' ||
12
+ (method?.name === 'getDeviceState' && method.payload.scope === 'firmware');
@@ -61,6 +61,7 @@ import {
61
61
  PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
62
62
  type ProtocolV2RuntimeMode,
63
63
  getProtocolV2RuntimeMode,
64
+ isLegacyProtocolV2ProtocolInfo,
64
65
  requestProtocolV2DeviceInfo,
65
66
  requestProtocolV2DeviceStatus,
66
67
  requestProtocolV2ProtocolInfo,
@@ -99,6 +100,8 @@ export type InitOptions = {
99
100
  protocolV2DeviceInfoTimeoutMs?: number;
100
101
  /** Refresh Protocol V2 runtime state before returning discovery results. */
101
102
  refreshRuntimeState?: boolean;
103
+ /** Recovery-only compatibility used by discovery and Protocol V2 firmware updates. */
104
+ allowLegacyProtocolV2ProtocolInfo?: boolean;
102
105
  /**
103
106
  * Protocol V1 Initialize response timeout override. Reboot-wait polling passes a
104
107
  * short value so an unanswered probe settles before the next poll tick.
@@ -977,10 +980,16 @@ export class Device extends EventEmitter {
977
980
  });
978
981
  // The default request excludes SE/hash data and therefore uses basic scope.
979
982
  // Full version and verification data require getDeviceState({ scope: 'firmware' }).
980
- const features = await this.probeProtocolV2RuntimeState(
981
- deviceInfo,
982
- options?.protocolV2DeviceInfoTimeoutMs
983
- );
983
+ const features = options?.allowLegacyProtocolV2ProtocolInfo
984
+ ? await this.probeProtocolV2RuntimeState(
985
+ deviceInfo,
986
+ options.protocolV2DeviceInfoTimeoutMs,
987
+ { allowLegacyProtocolV2ProtocolInfo: true }
988
+ )
989
+ : await this.probeProtocolV2RuntimeState(
990
+ deviceInfo,
991
+ options?.protocolV2DeviceInfoTimeoutMs
992
+ );
984
993
  Log.debug('Protocol V2 features:', features);
985
994
  } catch (error) {
986
995
  Log.error('Protocol V2 initialization failed:', error);
@@ -1017,7 +1026,13 @@ export class Device extends EventEmitter {
1017
1026
  commands: this.commands,
1018
1027
  request: getProtocolV2DeviceInfoRequest(),
1019
1028
  });
1020
- await this.probeProtocolV2RuntimeState(deviceInfo);
1029
+ if (params.allowLegacyProtocolV2ProtocolInfo) {
1030
+ await this.probeProtocolV2RuntimeState(deviceInfo, undefined, {
1031
+ allowLegacyProtocolV2ProtocolInfo: true,
1032
+ });
1033
+ } else {
1034
+ await this.probeProtocolV2RuntimeState(deviceInfo);
1035
+ }
1021
1036
  refreshedDeviceInfo = deviceInfo;
1022
1037
  initializedWithDeviceInfo = true;
1023
1038
  } else {
@@ -1053,7 +1068,16 @@ export class Device extends EventEmitter {
1053
1068
  }
1054
1069
 
1055
1070
  if (refresh.has('status') && !initializedWithDeviceInfo) {
1056
- await this.probeProtocolV2RuntimeState(refreshedDeviceInfo);
1071
+ const cachedMode = this.state?.status.mode;
1072
+ await this.probeProtocolV2RuntimeState(refreshedDeviceInfo, undefined, {
1073
+ // Loader firmware does not support DeviceStatusGet. Renegotiate ProtocolInfo
1074
+ // during an explicit refresh so a device rebooted into application firmware
1075
+ // can leave the cached loader state.
1076
+ forceRuntimeContextRefresh: cachedMode === 'bootloader' || cachedMode === 'romloader',
1077
+ ...(params.allowLegacyProtocolV2ProtocolInfo
1078
+ ? { allowLegacyProtocolV2ProtocolInfo: true }
1079
+ : {}),
1080
+ });
1057
1081
  }
1058
1082
 
1059
1083
  if (refresh.has('settings') && this.state?.status.mode === 'normal') {
@@ -1124,19 +1148,39 @@ export class Device extends EventEmitter {
1124
1148
  return this.features;
1125
1149
  }
1126
1150
 
1127
- async ensureProtocolV2RuntimeContext(timeoutMs?: number): Promise<ProtocolInfo> {
1151
+ async ensureProtocolV2RuntimeContext(
1152
+ timeoutMs?: number,
1153
+ options?: {
1154
+ forceRefresh?: boolean;
1155
+ allowLegacyProtocolV2ProtocolInfo?: boolean;
1156
+ }
1157
+ ): Promise<ProtocolInfo> {
1158
+ const assertProtocolInfoAllowed = (protocolInfo: ProtocolInfo) => {
1159
+ if (
1160
+ isLegacyProtocolV2ProtocolInfo(protocolInfo) &&
1161
+ options?.allowLegacyProtocolV2ProtocolInfo !== true
1162
+ ) {
1163
+ throw ERRORS.TypedError(
1164
+ HardwareErrorCode.DeviceInitializeFailed,
1165
+ 'Legacy Protocol V2 ProtocolInfo is supported only during device discovery and firmware update.'
1166
+ );
1167
+ }
1168
+ return protocolInfo;
1169
+ };
1128
1170
  const cachedProtocolInfo =
1129
- this.protocolV2RuntimeContext ??
1130
- (!this.protocolV2StateNeedsReload
1131
- ? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
1132
- : undefined);
1171
+ options?.forceRefresh === true
1172
+ ? undefined
1173
+ : this.protocolV2RuntimeContext ??
1174
+ (!this.protocolV2StateNeedsReload
1175
+ ? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
1176
+ : undefined);
1133
1177
  if (cachedProtocolInfo) {
1134
1178
  this.protocolV2RuntimeContext = cachedProtocolInfo;
1135
- return cachedProtocolInfo;
1179
+ return assertProtocolInfoAllowed(cachedProtocolInfo);
1136
1180
  }
1137
1181
 
1138
1182
  if (this.protocolV2RuntimeContextPromise) {
1139
- return this.protocolV2RuntimeContextPromise;
1183
+ return assertProtocolInfoAllowed(await this.protocolV2RuntimeContextPromise);
1140
1184
  }
1141
1185
 
1142
1186
  const requestToken = {};
@@ -1158,7 +1202,7 @@ export class Device extends EventEmitter {
1158
1202
  this.protocolV2RuntimeContextPromise = pendingRequest;
1159
1203
 
1160
1204
  try {
1161
- return await pendingRequest;
1205
+ return assertProtocolInfoAllowed(await pendingRequest);
1162
1206
  } finally {
1163
1207
  if (this.protocolV2RuntimeContextPromise === pendingRequest) {
1164
1208
  this.protocolV2RuntimeContextPromise = undefined;
@@ -1169,9 +1213,20 @@ export class Device extends EventEmitter {
1169
1213
  }
1170
1214
  }
1171
1215
 
1172
- async probeProtocolV2RuntimeState(deviceInfo?: ProtocolV2DeviceInfo, timeoutMs?: number) {
1173
- const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs);
1216
+ async probeProtocolV2RuntimeState(
1217
+ deviceInfo?: ProtocolV2DeviceInfo,
1218
+ timeoutMs?: number,
1219
+ options?: {
1220
+ forceRuntimeContextRefresh?: boolean;
1221
+ allowLegacyProtocolV2ProtocolInfo?: boolean;
1222
+ }
1223
+ ) {
1224
+ const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs, {
1225
+ forceRefresh: options?.forceRuntimeContextRefresh,
1226
+ allowLegacyProtocolV2ProtocolInfo: options?.allowLegacyProtocolV2ProtocolInfo,
1227
+ });
1174
1228
  const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
1229
+ const legacyProtocolInfo = isLegacyProtocolV2ProtocolInfo(protocolInfo);
1175
1230
  const runtimeDeviceInfo = deviceInfo ?? this.state?.raw?.protocolV2DeviceInfo;
1176
1231
  const protocolV2DeviceType = runtimeDeviceInfo
1177
1232
  ? resolveProtocolV2DeviceIdentity(runtimeDeviceInfo.hw?.Device_type).deviceType
@@ -1186,10 +1241,9 @@ export class Device extends EventEmitter {
1186
1241
  'Protocol V2 romloader mode is only supported for Pro2 and Neo.'
1187
1242
  );
1188
1243
  }
1189
- const deviceStatusSupported = supportsProtocolV2Message(
1190
- protocolInfo,
1191
- PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE
1192
- );
1244
+ const deviceStatusSupported =
1245
+ legacyProtocolInfo ||
1246
+ supportsProtocolV2Message(protocolInfo, PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE);
1193
1247
 
1194
1248
  if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
1195
1249
  return this.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
@@ -1491,6 +1545,10 @@ export class Device extends EventEmitter {
1491
1545
 
1492
1546
  async interruptionFromUser() {
1493
1547
  const error = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
1548
+ // Cancellation often ends the transport session, especially during firmware
1549
+ // update reboot or exit. Invalidate V2 state before awaiting asynchronous
1550
+ // cancellation callbacks so the next request cannot reuse a stale runtime mode.
1551
+ this.markTransportDisconnected();
1494
1552
  await this.cancelableAction?.(error);
1495
1553
  await this.commands?.cancel();
1496
1554
 
@@ -182,7 +182,7 @@ export class DevicePool extends EventEmitter {
182
182
  try {
183
183
  await device.initialize(initOptions);
184
184
  if (initOptions?.refreshRuntimeState && device.isProtocolV2()) {
185
- await this._refreshProtocolV2DiscoveryState(device);
185
+ await this._refreshProtocolV2DiscoveryState(device, initOptions);
186
186
  }
187
187
  } finally {
188
188
  await device.release();
@@ -197,7 +197,7 @@ export class DevicePool extends EventEmitter {
197
197
  await device.run(
198
198
  async () => {
199
199
  try {
200
- await this._refreshProtocolV2DiscoveryState(device);
200
+ await this._refreshProtocolV2DiscoveryState(device, initOptions);
201
201
  } catch (error) {
202
202
  // Device.run releases after the callback; then propagate the actual read error.
203
203
  refreshError = error;
@@ -206,6 +206,9 @@ export class DevicePool extends EventEmitter {
206
206
  {
207
207
  connectProtocol: initOptions.connectProtocol,
208
208
  forceProtocolDetection: initOptions.forceProtocolDetection,
209
+ ...(initOptions.allowLegacyProtocolV2ProtocolInfo
210
+ ? { allowLegacyProtocolV2ProtocolInfo: true }
211
+ : {}),
209
212
  }
210
213
  );
211
214
  if (refreshError instanceof Error) throw refreshError;
@@ -217,10 +220,19 @@ export class DevicePool extends EventEmitter {
217
220
  * error; settings only supplies a label, so its failure retains the existing name.
218
221
  * Device.getDeviceState skips unsupported status/settings calls in loader mode.
219
222
  */
220
- static async _refreshProtocolV2DiscoveryState(device: Device) {
221
- await device.getDeviceState({ refreshSections: ['status'] });
223
+ static async _refreshProtocolV2DiscoveryState(device: Device, initOptions?: InitOptions) {
224
+ const stateReadOptions = initOptions?.allowLegacyProtocolV2ProtocolInfo
225
+ ? { allowLegacyProtocolV2ProtocolInfo: true as const }
226
+ : {};
227
+ await device.getDeviceState({
228
+ refreshSections: ['status'],
229
+ ...stateReadOptions,
230
+ });
222
231
  try {
223
- await device.getDeviceState({ refreshSections: ['settings'] });
232
+ await device.getDeviceState({
233
+ refreshSections: ['settings'],
234
+ ...stateReadOptions,
235
+ });
224
236
  } catch (error) {
225
237
  Log.debug('Unable to refresh Protocol V2 device label during discovery', error);
226
238
  }
@@ -56,6 +56,16 @@ export const getProtocolV2SeType = (se?: DeviceSEInfo): string | null =>
56
56
 
57
57
  export type ProtocolV2RuntimeMode = 'normal' | 'bootloader' | 'romloader';
58
58
 
59
+ export type ProtocolV2ProtocolInfo = ProtocolInfo & {
60
+ /** Present only when hd-transport decoded the pre-build-fingerprint wire layout. */
61
+ protobuf_definition?: string | null;
62
+ };
63
+
64
+ export const isLegacyProtocolV2ProtocolInfo = (
65
+ protocolInfo: ProtocolInfo
66
+ ): protocolInfo is ProtocolV2ProtocolInfo & { protobuf_definition: string | null } =>
67
+ Object.prototype.hasOwnProperty.call(protocolInfo, 'protobuf_definition');
68
+
59
69
  /**
60
70
  * Protocol V2 fingerprints use:
61
71
  * <binary>__<version>__<commit>__<PROD|DEV>__<DEBUG|RELEASE>
@@ -7,6 +7,7 @@ export {
7
7
  getProtocolV2RuntimeMode,
8
8
  getProtocolV2SeState,
9
9
  getProtocolV2SeType,
10
+ isLegacyProtocolV2ProtocolInfo,
10
11
  parseProtocolV2BuildFingerprint,
11
12
  requestProtocolV2ProtocolInfo,
12
13
  supportsProtocolV2Message,
@@ -18,6 +19,7 @@ export type {
18
19
  ProtocolV2SEInfo,
19
20
  ProtocolV2SeStateLabel,
20
21
  ProtocolV2RuntimeMode,
22
+ ProtocolV2ProtocolInfo,
21
23
  } from './features';
22
24
  export * from './firmware';
23
25
  export * from './walletSession';
@@ -2,6 +2,7 @@ import EventEmitter from 'events';
2
2
 
3
3
  import { createCoreApi, createProtocolAwareCall } from './inject';
4
4
  import { unregisterFirmwareUpdateHostBinding } from './api/firmware/FirmwareHostBinding';
5
+ import { encodeBridgeBinaryPayload } from './utils/bridgeBinaryPayload';
5
6
 
6
7
  import type { ConnectSettings } from './types/settings';
7
8
  import type { CoreApi } from './types/api';
@@ -16,9 +17,10 @@ const eventEmitter = new EventEmitter();
16
17
 
17
18
  export const topLevelInject = () => {
18
19
  let lowLevelApi: LowLevelCoreApi | undefined;
19
- const call = (params: any) => {
20
+ const call = async (params: any) => {
20
21
  if (!lowLevelApi) return Promise.resolve(undefined);
21
- return lowLevelApi.call(params);
22
+ const encodedParams = await encodeBridgeBinaryPayload(params);
23
+ return lowLevelApi.call(encodedParams as any);
22
24
  };
23
25
  const protocolAwareCall = createProtocolAwareCall(call);
24
26
  const api: CoreApi = {
@@ -11,6 +11,8 @@ export type GetDeviceStateParams = CommonParams & {
11
11
  export type DeviceStateReadOptions = {
12
12
  refreshSections?: DeviceStateSection[];
13
13
  includeRaw?: boolean;
14
+ /** Internal recovery-only compatibility used by discovery and firmware update flows. */
15
+ allowLegacyProtocolV2ProtocolInfo?: boolean;
14
16
  };
15
17
 
16
18
  export declare function getDeviceState(