@onekeyfe/hd-core 1.2.0-alpha.134 → 1.2.0-alpha.135

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 (31) hide show
  1. package/__tests__/DeviceCommands.test.ts +8 -1
  2. package/__tests__/device-lifecycle-events.test.ts +95 -2
  3. package/__tests__/device-settings.test.ts +167 -9
  4. package/__tests__/device-wallet-session-store.test.ts +21 -79
  5. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +37 -1
  6. package/__tests__/protocol-v2-unlock-policy.test.ts +59 -0
  7. package/__tests__/protocol-v2.test.ts +86 -21
  8. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  9. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  10. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  11. package/dist/api/device/DeviceVerify.d.ts +1 -0
  12. package/dist/api/device/DeviceVerify.d.ts.map +1 -1
  13. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  14. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  15. package/dist/core/index.d.ts +3 -1
  16. package/dist/core/index.d.ts.map +1 -1
  17. package/dist/device/Device.d.ts +2 -0
  18. package/dist/device/Device.d.ts.map +1 -1
  19. package/dist/device/DeviceCommands.d.ts.map +1 -1
  20. package/dist/index.d.ts +4 -1
  21. package/dist/index.js +296 -247
  22. package/package.json +4 -4
  23. package/src/api/FirmwareUpdateV4.ts +25 -68
  24. package/src/api/device/DeviceChangePin.ts +4 -1
  25. package/src/api/device/DeviceSettings.ts +2 -16
  26. package/src/api/device/DeviceVerify.ts +9 -0
  27. package/src/api/device/DeviceWipe.ts +4 -1
  28. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +11 -0
  29. package/src/core/index.ts +67 -25
  30. package/src/device/Device.ts +45 -42
  31. package/src/device/DeviceCommands.ts +15 -2
@@ -284,6 +284,9 @@ export class Device extends EventEmitter {
284
284
 
285
285
  runPromise?: Deferred<void> | null;
286
286
 
287
+ /** Resolves only after the active run has completed its release path. */
288
+ private runCleanupPromise?: Promise<void>;
289
+
287
290
  externalState: string[] = [];
288
291
 
289
292
  unavailableCapabilities: UnavailableCapabilities = {};
@@ -927,6 +930,17 @@ export class Device extends EventEmitter {
927
930
  };
928
931
 
929
932
  const expectedDeviceId = options?.deviceId;
933
+ if (expectedDeviceId) {
934
+ // 先只读校验物理设备身份;钱包上下文仍由下方携带完整参数的
935
+ // Initialize 选择,避免标准钱包请求复用此前的隐藏钱包上下文。
936
+ this.passphraseState = undefined;
937
+ const { message } = await this.commands.typedCall('GetFeatures', 'Features', {});
938
+ this._updateFeatures(message);
939
+ await TransportManager.reconfigure(this.features);
940
+ if (!this.checkDeviceId(expectedDeviceId)) {
941
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
942
+ }
943
+ }
930
944
 
931
945
  this.passphraseState = options?.passphraseState;
932
946
 
@@ -946,46 +960,7 @@ export class Device extends EventEmitter {
946
960
  payload.derive_cardano = true;
947
961
  }
948
962
 
949
- if (this.features) {
950
- // Re-sync the V1 message schema for THIS device before encoding
951
- // Initialize: the process-global schema may still reflect another
952
- // device (e.g. legacy-firmware Touch/Mini) on multi-device setups, and
953
- // a stale legacy schema would silently strip passphrase_state /
954
- // is_contains_attach from the wire message. Local operation, no wire
955
- // I/O; a no-op when the schema is unchanged.
956
- await TransportManager.reconfigure(this.features);
957
- }
958
-
959
- // Initialize's own Features response carries device_id, so the physical
960
- // device identity is validated on the same round trip instead of via a
961
- // separate read-only GetFeatures preflight (which doubled the wire cost
962
- // of every deviceId-carrying call). The method fn has not run yet, so a
963
- // mismatch still fails before any wallet data can be derived, with the
964
- // same DeviceCheckDeviceIdError. Wallet-context selection is unchanged:
965
- // it is decided by the Initialize payload above either way.
966
- const assertExpectedDeviceIdentity = () => {
967
- if (expectedDeviceId && !this.checkDeviceId(expectedDeviceId)) {
968
- // The mismatched Initialize may have cached a session under the wrong
969
- // device's identity; drop it so no wallet context survives from it.
970
- // (This also evicts any session the wrong device legitimately cached
971
- // under the same passphraseState — a deliberate conservative purge
972
- // after a physical-swap event, consistent with
973
- // reconcileDeviceIdentity purging the previous device's sessions.)
974
- this.clearInternalState();
975
- throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
976
- }
977
- };
978
-
979
- try {
980
- await callInitialize(payload, options?.initSession);
981
- } catch (error) {
982
- // callInitialize can fail AFTER the wire call cached the session (e.g.
983
- // TransportManager.reconfigure rejecting); the identity check must
984
- // still run so a wrong-device session never survives the error path.
985
- assertExpectedDeviceIdentity();
986
- throw error;
987
- }
988
- assertExpectedDeviceIdentity();
963
+ await callInitialize(payload, options?.initSession);
989
964
  } catch (error) {
990
965
  Log.error('Initialization failed:', error);
991
966
  throw error;
@@ -1107,6 +1082,10 @@ export class Device extends EventEmitter {
1107
1082
  return params.includeRaw ? cloneDeviceState(this.state) : createPublicDeviceState(this.state);
1108
1083
  }
1109
1084
 
1085
+ async refreshProtocolV2SettingsAfterMutation() {
1086
+ return this.getDeviceState({ refreshSections: ['status', 'settings'] });
1087
+ }
1088
+
1110
1089
  _updateFeatures(protoFeatures: PROTO.Features | Features, initSession?: boolean) {
1111
1090
  const previousDeviceId = this.getCurrentDeviceId();
1112
1091
  let feat =
@@ -1424,12 +1403,20 @@ export class Device extends EventEmitter {
1424
1403
 
1425
1404
  const runPromise = createDeferred<void>();
1426
1405
  this.runPromise = runPromise;
1427
- this._runInner(fn, options, runPromise).catch(error => {
1406
+ const cleanupPromise = this._runInner(fn, options, runPromise).catch(error => {
1428
1407
  if (this.runPromise === runPromise) {
1429
1408
  this.runPromise = null;
1430
1409
  }
1431
1410
  runPromise.reject(error);
1432
1411
  });
1412
+ this.runCleanupPromise = cleanupPromise;
1413
+ cleanupPromise
1414
+ .finally(() => {
1415
+ if (this.runCleanupPromise === cleanupPromise) {
1416
+ this.runCleanupPromise = undefined;
1417
+ }
1418
+ })
1419
+ .catch(() => undefined);
1433
1420
  return runPromise.promise;
1434
1421
  }
1435
1422
 
@@ -1541,13 +1528,29 @@ export class Device extends EventEmitter {
1541
1528
 
1542
1529
  async interruptionFromUser() {
1543
1530
  const error = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
1544
- await this.cancelableAction?.(error);
1531
+ const cleanupPromise = this.runCleanupPromise;
1532
+ const { cancelableAction } = this;
1533
+ const env = DataManager.getSettings('env');
1534
+ if (cancelableAction) {
1535
+ await cancelableAction(error);
1536
+ } else if (
1537
+ this.isProtocolV2() &&
1538
+ (DataManager.isBleConnect(env) ||
1539
+ DataManager.isBrowserWebUsb(env) ||
1540
+ DataManager.isDesktopWebUsb(env)) &&
1541
+ this.hasDeviceAcquire()
1542
+ ) {
1543
+ await this.commands?.cancelDevice?.().catch(cancelError => {
1544
+ Log.debug('Protocol V2 fallback cancel error', cancelError);
1545
+ });
1546
+ }
1545
1547
  await this.commands?.cancel();
1546
1548
 
1547
1549
  if (this.runPromise) {
1548
1550
  this.runPromise.reject(error);
1549
1551
  this.runPromise = null;
1550
1552
  }
1553
+ await cleanupPromise?.catch(() => undefined);
1551
1554
  }
1552
1555
 
1553
1556
  setCancelableAction(callback: (err?: Error) => Promise<unknown>) {
@@ -69,7 +69,7 @@ const DEVICE_CONTROL_CALLS = new Set([
69
69
  // Older Protocol V2 firmware may omit the cancellation subcode, so retain a
70
70
  // narrow message fallback for compatibility.
71
71
  const isProtocolV2ActionCancelledMessage = (message: string) =>
72
- /^(?:cancel(?:led|ed)(?: on device)?|confirm dismissed|user cancel(?:led|ed)(?:\s+.*)?)$/i.test(
72
+ /^(?:cancel(?:led|ed)(?: on device)?|confirm dismissed|update cancel(?:led|ed)|user cancel(?:led|ed)(?:\s+.*)?)$/i.test(
73
73
  message
74
74
  );
75
75
 
@@ -264,6 +264,7 @@ export class DeviceCommands {
264
264
  }
265
265
  const activeCall = this.callPromise;
266
266
  let timer: ReturnType<typeof setTimeout> | undefined;
267
+ let timedOut = false;
267
268
  const cancellation = (async () => {
268
269
  await this.dispose(true);
269
270
  await activeCall?.catch(() => undefined);
@@ -273,11 +274,23 @@ export class DeviceCommands {
273
274
  await Promise.race([
274
275
  cancellation,
275
276
  new Promise<void>(resolve => {
276
- timer = setTimeout(resolve, 10 * 1000);
277
+ timer = setTimeout(() => {
278
+ timedOut = true;
279
+ resolve();
280
+ }, 10 * 1000);
277
281
  }),
278
282
  ]);
279
283
  } finally {
280
284
  if (timer) clearTimeout(timer);
285
+ if (
286
+ timedOut &&
287
+ this.transport.name === 'ReactNativeBleTransport' &&
288
+ this.transport.disconnect
289
+ ) {
290
+ await this.transport.disconnect(this.mainId).catch(error => {
291
+ Log.debug('BLE cancellation timeout disconnect error (ignored)', error);
292
+ });
293
+ }
281
294
  if (this.callPromise === activeCall) {
282
295
  this.callPromise = undefined;
283
296
  }