@onekeyfe/hd-transport-web-device 1.2.2-alpha.12 → 1.2.2-alpha.120

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.
package/README.md CHANGED
@@ -26,4 +26,4 @@ yar update:protobuf to generate new ./messages.json and ./src/types/messages.ts
26
26
 
27
27
  ## Docs
28
28
 
29
- Documentation is available [hardware-js-sdk](https://developer.onekey.so/connect-to-hardware/hardware-sdk/start)
29
+ Documentation is available [Hardware SDK Getting Started](https://developer.onekey.so/en/hardware-sdk/getting-started)
@@ -182,6 +182,40 @@ describe('ElectronBleTransport protocol detection', () => {
182
182
  jest.clearAllMocks();
183
183
  });
184
184
 
185
+ test('does not treat an error envelope from a legacy preload as a successful connect', async () => {
186
+ const device = { id: 'stale-bond-id', name: 'OneKey Pro 2' };
187
+ const nobleBle = createNobleBle(device);
188
+ nobleBle.connect.mockResolvedValue({
189
+ type: 'NobleBleIpcError',
190
+ success: false,
191
+ error: {
192
+ name: 'HardwareError',
193
+ message: 'Bluetooth pairing information is no longer valid',
194
+ errorCode: HardwareErrorCode.BleBondInvalid,
195
+ },
196
+ } as never);
197
+ const bleTransport = configureTransport(nobleBle);
198
+
199
+ await expect(
200
+ bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
201
+ ).rejects.toMatchObject({
202
+ errorCode: HardwareErrorCode.BleBondInvalid,
203
+ });
204
+ expect(nobleBle.subscribe).not.toHaveBeenCalled();
205
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
206
+ });
207
+
208
+ test('sends a native disconnect even before acquire marks the device connected', async () => {
209
+ const device = { id: 'pending-connect-id', name: 'OneKey Pro 2' };
210
+ const nobleBle = createNobleBle(device);
211
+ const bleTransport = configureTransport(nobleBle) as any;
212
+
213
+ await bleTransport.releaseNative(device.id);
214
+
215
+ expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
216
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
217
+ });
218
+
185
219
  test('keeps raw BLE lifecycle payloads off the public device event channel', async () => {
186
220
  const device = { id: 'lifecycle-pro2-id', name: 'OneKey Pro 2' };
187
221
  const nobleBle = createNobleBle(device);
@@ -464,7 +498,7 @@ describe('ElectronBleTransport protocol detection', () => {
464
498
  await expect(result).resolves.toBe('rejected');
465
499
  });
466
500
 
467
- test('throws when both protocol probes fail', async () => {
501
+ test('disconnects when both protocol probes fail and reconnects for the next acquire', async () => {
468
502
  const device = { id: 'dead-device-id', name: 'Unknown Device' };
469
503
  const nobleBle = createNobleBle(device);
470
504
 
@@ -476,7 +510,25 @@ describe('ElectronBleTransport protocol detection', () => {
476
510
  await expect(transport.acquire({ uuid: device.id })).rejects.toThrow(
477
511
  /Unable to detect BLE protocol/
478
512
  );
513
+ expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
514
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
479
515
  expect(transport.getProtocolType(device.id)).toBeUndefined();
516
+
517
+ echoProtocolV2(nobleBle, device.id);
518
+ await expect(
519
+ transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
520
+ ).resolves.toMatchObject({ uuid: device.id });
521
+ expect(nobleBle.connect).toHaveBeenCalledTimes(2);
522
+ expect(nobleBle.disconnect.mock.invocationCallOrder.at(-1)).toBeLessThan(
523
+ nobleBle.connect.mock.invocationCallOrder[1]
524
+ );
525
+ await expect(
526
+ transport.call(device.id, 'Ping', { message: 'after-reconnect' })
527
+ ).resolves.toMatchObject({
528
+ type: 'Success',
529
+ message: { message: 'ok' },
530
+ });
531
+ await transport.release(device.id);
480
532
  });
481
533
 
482
534
  test('surfaces Protocol V2 link disabled while the initial V1 probe is active', async () => {
@@ -553,14 +605,19 @@ describe('ElectronBleTransport protocol detection', () => {
553
605
  test('fails Protocol V2 acquire immediately when subscribe reports insufficient encryption', async () => {
554
606
  const device = { id: 'stale-bond-pro2-id', name: 'OneKey Pro 2' };
555
607
  const nobleBle = createNobleBle(device);
556
- nobleBle.subscribe.mockRejectedValue(new Error('Encryption is insufficient'));
608
+ nobleBle.subscribe.mockRejectedValue({
609
+ name: 'HardwareError',
610
+ message: 'Bluetooth pairing information is no longer valid',
611
+ errorCode: HardwareErrorCode.BleBondInvalid,
612
+ params: { nativeErrorMessage: 'Encryption is insufficient' },
613
+ });
557
614
  const transport = configureTransport(nobleBle);
558
615
  const probe = jest.spyOn(transport as any, 'probeProtocolV2');
559
616
 
560
617
  await expect(
561
618
  transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
562
619
  ).rejects.toMatchObject({
563
- errorCode: HardwareErrorCode.BleDeviceBondError,
620
+ errorCode: HardwareErrorCode.BleBondInvalid,
564
621
  });
565
622
 
566
623
  expect(probe).not.toHaveBeenCalled();
@@ -568,21 +625,40 @@ describe('ElectronBleTransport protocol detection', () => {
568
625
  expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
569
626
  });
570
627
 
571
- test('maps macOS CoreBluetooth peer-removed pairing failures to the precise error', async () => {
572
- const device = { id: 'reset-pro2-macos-id', name: 'OneKey Pro 2' };
628
+ test('rehydrates a structured stale-bond error before the protocol is known', async () => {
629
+ const device = { id: 'reset-unknown-protocol-id', name: 'OneKey Pro 2' };
573
630
  const nobleBle = createNobleBle(device);
574
- nobleBle.connect.mockRejectedValue(
575
- new Error('CBErrorDomain:14 Peer removed pairing information on the device side')
576
- );
631
+ nobleBle.connect.mockRejectedValue({
632
+ name: 'HardwareError',
633
+ message: 'Bluetooth pairing information is no longer valid',
634
+ errorCode: HardwareErrorCode.BleBondInvalid,
635
+ params: { nativeErrorMessage: 'CBErrorDomain:14 native message' },
636
+ });
577
637
  const transport = configureTransport(nobleBle);
578
638
 
579
- await expect(
580
- transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
581
- ).rejects.toMatchObject({
582
- errorCode: HardwareErrorCode.BlePeerRemovedPairingInformation,
639
+ await expect(transport.acquire({ uuid: device.id })).rejects.toMatchObject({
640
+ name: 'HardwareError',
641
+ errorCode: HardwareErrorCode.BleBondInvalid,
642
+ params: { nativeErrorMessage: 'CBErrorDomain:14 native message' },
583
643
  });
584
644
  });
585
645
 
646
+ test('does not infer hardware errors from native error text in the renderer', async () => {
647
+ const device = { id: 'reset-pro2-localized-id', name: 'OneKey Pro 2' };
648
+ const nobleBle = createNobleBle(device);
649
+ nobleBle.connect.mockRejectedValue(new Error('CBATTErrorDomain:14 localized native message'));
650
+ const transport = configureTransport(nobleBle);
651
+
652
+ try {
653
+ await transport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
654
+ throw new Error('Expected acquire to fail');
655
+ } catch (error) {
656
+ expect(error).toBeInstanceOf(Error);
657
+ expect((error as Error).message).toBe('CBATTErrorDomain:14 localized native message');
658
+ expect((error as { errorCode?: unknown }).errorCode).toBeUndefined();
659
+ }
660
+ });
661
+
586
662
  test('does not classify a generic macOS connection failure as a stale bond', async () => {
587
663
  const device = { id: 'offline-pro2-macos-id', name: 'OneKey Pro 2' };
588
664
  const nobleBle = createNobleBle(device);
@@ -615,7 +691,7 @@ describe('ElectronBleTransport protocol detection', () => {
615
691
  }
616
692
  });
617
693
 
618
- test('reports a stale bond when a previously confirmed Protocol V2 device stops responding', async () => {
694
+ test('keeps a probe miss retryable after a previously confirmed Protocol V2 connection', async () => {
619
695
  const device = { id: 'reset-pro2-id', name: 'OneKey Pro 2' };
620
696
  const nobleBle = createNobleBle(device);
621
697
  const transport = configureTransport(nobleBle);
@@ -628,7 +704,7 @@ describe('ElectronBleTransport protocol detection', () => {
628
704
  await expect(
629
705
  transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
630
706
  ).rejects.toMatchObject({
631
- errorCode: HardwareErrorCode.BleDeviceBondError,
707
+ errorCode: HardwareErrorCode.RuntimeError,
632
708
  });
633
709
 
634
710
  expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
@@ -636,6 +712,52 @@ describe('ElectronBleTransport protocol detection', () => {
636
712
  expect(transport.getProtocolType(device.id)).toBeUndefined();
637
713
  });
638
714
 
715
+ test.each([false, true])(
716
+ 'preserves an expected Protocol V2 Ping timeout with a prior successful connection: %s',
717
+ async previouslyConnected => {
718
+ const device = { id: 'timeout-pro2-id', name: 'OneKey Pro 2' };
719
+ const nobleBle = createNobleBle(device);
720
+ const transport = configureTransport(nobleBle);
721
+
722
+ if (previouslyConnected) {
723
+ echoProtocolV2(nobleBle, device.id);
724
+ await transport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
725
+ await transport.release(device.id);
726
+ }
727
+ // Keep the real probe and response timer, but drop its notification.
728
+ nobleBle.write.mockImplementation(() => Promise.resolve());
729
+
730
+ await expect(
731
+ transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
732
+ ).rejects.toMatchObject({
733
+ errorCode: HardwareErrorCode.BleTimeoutError,
734
+ message: 'BLE response timeout after 5000ms for Ping',
735
+ });
736
+
737
+ expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
738
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
739
+ expect(transport.getProtocolType(device.id)).toBeUndefined();
740
+ }
741
+ );
742
+
743
+ test('preserves a native stale-bond error during an expected Protocol V2 probe', async () => {
744
+ const device = { id: 'stale-bond-probe-id', name: 'OneKey Pro 2' };
745
+ const nobleBle = createNobleBle(device);
746
+ nobleBle.write.mockRejectedValue({
747
+ name: 'HardwareError',
748
+ message: 'Bluetooth pairing information is no longer valid',
749
+ errorCode: HardwareErrorCode.BleBondInvalid,
750
+ });
751
+ const transport = configureTransport(nobleBle);
752
+
753
+ await expect(
754
+ transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
755
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleBondInvalid });
756
+
757
+ expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
758
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
759
+ });
760
+
639
761
  test('does not take a Protocol V2 hint from the BLE name', async () => {
640
762
  const device = { id: 'named-pro2-id', name: 'OneKey Pro 2' };
641
763
  const nobleBle = createNobleBle(device);
@@ -27,7 +27,6 @@ export default class ElectronBleTransport {
27
27
  private connectedDevices;
28
28
  private deviceProtocol;
29
29
  private deviceProtocolHints;
30
- private confirmedProtocolV2;
31
30
  private deviceMtus;
32
31
  private devicePacketCapacities;
33
32
  private v1Buffers;
@@ -41,7 +40,7 @@ export default class ElectronBleTransport {
41
40
  private hostDisconnectCleanup?;
42
41
  private notificationTokens;
43
42
  private nextNotificationToken;
44
- private toStaleBondError;
43
+ private normalizeBluetoothError;
45
44
  private handleBluetoothError;
46
45
  private cleanupDeviceState;
47
46
  init(logger: any, emitter?: EventEmitter): void;
@@ -1 +1 @@
1
- {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -218,8 +218,6 @@ declare class ElectronBleTransport {
218
218
  private connectedDevices;
219
219
  private deviceProtocol;
220
220
  private deviceProtocolHints;
221
- /** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
222
- private confirmedProtocolV2;
223
221
  private deviceMtus;
224
222
  private devicePacketCapacities;
225
223
  private v1Buffers;
@@ -242,7 +240,7 @@ declare class ElectronBleTransport {
242
240
  private hostDisconnectCleanup?;
243
241
  private notificationTokens;
244
242
  private nextNotificationToken;
245
- private toStaleBondError;
243
+ private normalizeBluetoothError;
246
244
  private handleBluetoothError;
247
245
  private cleanupDeviceState;
248
246
  init(logger: any, emitter?: EventEmitter): void;
package/dist/index.js CHANGED
@@ -823,6 +823,19 @@ function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapa
823
823
 
824
824
  const { parseConfigure, ProtocolV1, check } = transport__default["default"];
825
825
  const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
826
+ const invokeNobleBle = (request) => __awaiter(void 0, void 0, void 0, function* () {
827
+ const response = yield request;
828
+ if (response &&
829
+ typeof response === 'object' &&
830
+ 'type' in response &&
831
+ response.type === 'NobleBleIpcError' &&
832
+ 'success' in response &&
833
+ response.success === false &&
834
+ 'error' in response) {
835
+ return Promise.reject(response.error);
836
+ }
837
+ return response;
838
+ });
826
839
  const BLE_PACKET_SIZE_FALLBACK = 192;
827
840
  const BLE_PACKET_SIZE_MAXIMUM = 244;
828
841
  const BLE_WRITE_DELAY_MS = 5;
@@ -837,7 +850,6 @@ class ElectronBleTransport {
837
850
  this.connectedDevices = new Set();
838
851
  this.deviceProtocol = new Map();
839
852
  this.deviceProtocolHints = new Map();
840
- this.confirmedProtocolV2 = new Set();
841
853
  this.deviceMtus = new Map();
842
854
  this.devicePacketCapacities = new Map();
843
855
  this.v1Buffers = new Map();
@@ -871,40 +883,20 @@ class ElectronBleTransport {
871
883
  this.notificationTokens = new Map();
872
884
  this.nextNotificationToken = 1;
873
885
  }
874
- toStaleBondError(error) {
875
- var _a;
876
- if (hdShared.isBleStaleBondHardwareError(error)) {
877
- return error;
878
- }
879
- const errorMessage = error && typeof error === 'object' && 'message' in error
880
- ? String((_a = error.message) !== null && _a !== void 0 ? _a : '')
881
- : String(error !== null && error !== void 0 ? error : '');
882
- if (!hdShared.isBleStaleBondErrorText(errorMessage)) {
883
- return null;
884
- }
885
- const normalizedErrorMessage = errorMessage.toLowerCase();
886
- return hdShared.ERRORS.TypedError(normalizedErrorMessage.includes('peer removed pairing information') ||
887
- normalizedErrorMessage.includes('cberrordomain:14')
888
- ? hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation
889
- : hdShared.HardwareErrorCode.BleDeviceBondError, errorMessage);
890
- }
891
- handleBluetoothError(error, mapProtocolV2StaleBond = false) {
892
- if (mapProtocolV2StaleBond) {
893
- const staleBondError = this.toStaleBondError(error);
894
- if (staleBondError) {
895
- throw staleBondError;
896
- }
897
- }
886
+ normalizeBluetoothError(error) {
898
887
  if (error && typeof error === 'object') {
888
+ if (typeof error.errorCode === 'number') {
889
+ return hdShared.ERRORS.TypedError(error.errorCode, typeof error.message === 'string' ? error.message : undefined, error.params);
890
+ }
899
891
  if ('code' in error) {
900
892
  if (error.code === hdShared.HardwareErrorCode.BlePoweredOff) {
901
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff);
893
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff);
902
894
  }
903
895
  if (error.code === hdShared.HardwareErrorCode.BleUnsupported) {
904
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported);
896
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported);
905
897
  }
906
898
  if (error.code === hdShared.HardwareErrorCode.BlePermissionError) {
907
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
899
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
908
900
  }
909
901
  }
910
902
  const errorMessage = error.message || String(error);
@@ -912,16 +904,19 @@ class ElectronBleTransport {
912
904
  const unsupportedMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleUnsupported];
913
905
  const permissionMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BlePermissionError];
914
906
  if (errorMessage.includes(poweredOffMessage) || errorMessage.includes('poweredOff')) {
915
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff);
907
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff);
916
908
  }
917
909
  if (errorMessage.includes(unsupportedMessage) || errorMessage.includes('unsupported')) {
918
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported);
910
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported);
919
911
  }
920
912
  if (errorMessage.includes(permissionMessage) || errorMessage.includes('unauthorized')) {
921
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
913
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
922
914
  }
923
915
  }
924
- throw error;
916
+ return error;
917
+ }
918
+ handleBluetoothError(error) {
919
+ throw this.normalizeBluetoothError(error);
925
920
  }
926
921
  cleanupDeviceState(deviceId) {
927
922
  this.protocolV2Links
@@ -1008,7 +1003,7 @@ class ElectronBleTransport {
1008
1003
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
1009
1004
  throw new Error('Noble BLE API not available');
1010
1005
  }
1011
- const devices = yield window.desktopApi.nobleBle.enumerate();
1006
+ const devices = yield invokeNobleBle(window.desktopApi.nobleBle.enumerate());
1012
1007
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] enumerate found ${devices.length} device(s):`);
1013
1008
  for (const dev of devices) {
1014
1009
  (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] id="${dev.id}" name="${dev.name}"`);
@@ -1025,9 +1020,6 @@ class ElectronBleTransport {
1025
1020
  var _a, _b, _c, _d, _e, _f, _g;
1026
1021
  return __awaiter(this, void 0, void 0, function* () {
1027
1022
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
1028
- const shouldMapProtocolV2StaleBond = expectedProtocol
1029
- ? expectedProtocol === 'V2'
1030
- : this.confirmedProtocolV2.has(uuid);
1031
1023
  if (!uuid) {
1032
1024
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
1033
1025
  }
@@ -1044,7 +1036,7 @@ class ElectronBleTransport {
1044
1036
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
1045
1037
  throw new Error('Noble BLE API not available');
1046
1038
  }
1047
- const device = yield window.desktopApi.nobleBle.getDevice(uuid);
1039
+ const device = yield invokeNobleBle(window.desktopApi.nobleBle.getDevice(uuid));
1048
1040
  if (!device) {
1049
1041
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound, `Device ${uuid} not found`);
1050
1042
  }
@@ -1055,11 +1047,11 @@ class ElectronBleTransport {
1055
1047
  this.deviceProtocolHints.set(uuid, protocolHint);
1056
1048
  }
1057
1049
  try {
1058
- yield window.desktopApi.nobleBle.connect(uuid);
1050
+ yield invokeNobleBle(window.desktopApi.nobleBle.connect(uuid));
1059
1051
  this.connectedDevices.add(uuid);
1060
1052
  }
1061
1053
  catch (error) {
1062
- this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
1054
+ this.handleBluetoothError(error);
1063
1055
  }
1064
1056
  const mtuCleanup = this.createMtuSubscription(uuid);
1065
1057
  if (mtuCleanup) {
@@ -1068,10 +1060,10 @@ class ElectronBleTransport {
1068
1060
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1069
1061
  this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
1070
1062
  try {
1071
- yield window.desktopApi.nobleBle.subscribe(uuid);
1063
+ yield invokeNobleBle(window.desktopApi.nobleBle.subscribe(uuid));
1072
1064
  }
1073
1065
  catch (error) {
1074
- this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
1066
+ this.handleBluetoothError(error);
1075
1067
  }
1076
1068
  yield this.refreshBlePacketCapacity(uuid);
1077
1069
  const cleanup = this.createNotificationSubscription(uuid);
@@ -1089,16 +1081,22 @@ class ElectronBleTransport {
1089
1081
  catch (error) {
1090
1082
  (_e = this.Log) === null || _e === void 0 ? void 0 : _e.error('[Electron BLE] acquire failed:', error);
1091
1083
  try {
1092
- if (((_f = window.desktopApi) === null || _f === void 0 ? void 0 : _f.nobleBle) && this.connectedDevices.has(uuid)) {
1093
- yield window.desktopApi.nobleBle.unsubscribe(uuid);
1094
- yield window.desktopApi.nobleBle.disconnect(uuid);
1084
+ const nobleBle = (_f = window.desktopApi) === null || _f === void 0 ? void 0 : _f.nobleBle;
1085
+ if (nobleBle) {
1086
+ if (this.connectedDevices.has(uuid)) {
1087
+ yield invokeNobleBle(nobleBle.unsubscribe(uuid)).catch(cleanupError => {
1088
+ var _a;
1089
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] acquire unsubscribe failed:', cleanupError);
1090
+ });
1091
+ }
1092
+ yield invokeNobleBle(nobleBle.disconnect(uuid));
1095
1093
  }
1096
1094
  }
1097
1095
  catch (cleanupError) {
1098
1096
  (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
1099
1097
  }
1100
1098
  this.cleanupDeviceState(uuid);
1101
- throw error;
1099
+ this.handleBluetoothError(error);
1102
1100
  }
1103
1101
  });
1104
1102
  }
@@ -1124,13 +1122,17 @@ class ElectronBleTransport {
1124
1122
  var _a, _b;
1125
1123
  return __awaiter(this, void 0, void 0, function* () {
1126
1124
  try {
1127
- if (this.connectedDevices.has(id)) {
1128
- if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
1129
- yield window.desktopApi.nobleBle.unsubscribe(id);
1130
- yield window.desktopApi.nobleBle.disconnect(id);
1125
+ const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
1126
+ if (nobleBle) {
1127
+ if (this.connectedDevices.has(id)) {
1128
+ yield invokeNobleBle(nobleBle.unsubscribe(id)).catch(error => {
1129
+ var _a;
1130
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] release unsubscribe failed:', error);
1131
+ });
1131
1132
  }
1132
- this.cleanupDeviceState(id);
1133
+ yield invokeNobleBle(nobleBle.disconnect(id));
1133
1134
  }
1135
+ this.cleanupDeviceState(id);
1134
1136
  }
1135
1137
  catch (error) {
1136
1138
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] release failed:', error);
@@ -1154,7 +1156,7 @@ class ElectronBleTransport {
1154
1156
  }
1155
1157
  return;
1156
1158
  }
1157
- yield release(id, keepSession);
1159
+ yield invokeNobleBle(release(id, keepSession));
1158
1160
  }
1159
1161
  catch (error) {
1160
1162
  (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] logical release failed:', error);
@@ -1162,9 +1164,8 @@ class ElectronBleTransport {
1162
1164
  }
1163
1165
  });
1164
1166
  }
1165
- createProtocolMismatchError(expected, uuid) {
1166
- const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
1167
- return hdShared.ERRORS.TypedError(isStaleV2Bond ? hdShared.HardwareErrorCode.BleDeviceBondError : hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1167
+ createProtocolMismatchError(expected) {
1168
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1168
1169
  }
1169
1170
  createProtocolDetectionError() {
1170
1171
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
@@ -1183,13 +1184,12 @@ class ElectronBleTransport {
1183
1184
  return 'V1';
1184
1185
  }
1185
1186
  if (expectedProtocol === 'V2') {
1186
- if (yield this.probeProtocolV2(uuid)) {
1187
+ if (yield this.probeProtocolV2(uuid, expectedProtocol)) {
1187
1188
  this.deviceProtocol.set(uuid, 'V2');
1188
- this.confirmedProtocolV2.add(uuid);
1189
1189
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1190
1190
  return 'V2';
1191
1191
  }
1192
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
1192
+ throw this.createProtocolMismatchError(expectedProtocol);
1193
1193
  }
1194
1194
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1195
1195
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1200,9 +1200,6 @@ class ElectronBleTransport {
1200
1200
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1201
1201
  if (detected) {
1202
1202
  this.deviceProtocol.set(uuid, protocol);
1203
- if (protocol === 'V2') {
1204
- this.confirmedProtocolV2.add(uuid);
1205
- }
1206
1203
  (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
1207
1204
  return protocol;
1208
1205
  }
@@ -1265,7 +1262,7 @@ class ElectronBleTransport {
1265
1262
  }
1266
1263
  });
1267
1264
  }
1268
- probeProtocolV2(uuid) {
1265
+ probeProtocolV2(uuid, expectedProtocol) {
1269
1266
  var _a;
1270
1267
  return __awaiter(this, void 0, void 0, function* () {
1271
1268
  if (!this._messages || !this._messagesV2) {
@@ -1283,7 +1280,9 @@ class ElectronBleTransport {
1283
1280
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1284
1281
  this.resetProtocolV2Frames(uuid);
1285
1282
  },
1286
- shouldRethrow: error => hdShared.isBleStaleBondHardwareError(error) || transport.isProtocolV2LinkDisabledError(error),
1283
+ shouldRethrow: error => expectedProtocol === 'V2' ||
1284
+ hdShared.isBleStaleBondHardwareError(error) ||
1285
+ transport.isProtocolV2LinkDisabledError(error),
1287
1286
  });
1288
1287
  if (!detected) {
1289
1288
  this.clearProbeProtocol(uuid, 'V2');
@@ -1299,21 +1298,18 @@ class ElectronBleTransport {
1299
1298
  throw new Error('Noble BLE API not available');
1300
1299
  }
1301
1300
  try {
1302
- yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
1301
+ yield invokeNobleBle(nobleBle.write(uuid, hexData, { pacingDelayMs: 0 }));
1303
1302
  }
1304
1303
  catch (error) {
1305
- const staleBondError = this.toStaleBondError(error);
1306
- if (staleBondError) {
1307
- throw staleBondError;
1308
- }
1309
- throw error;
1304
+ this.handleBluetoothError(error);
1310
1305
  }
1311
1306
  });
1312
1307
  }
1313
1308
  refreshBlePacketCapacity(uuid) {
1314
- var _a, _b;
1309
+ var _a;
1315
1310
  return __awaiter(this, void 0, void 0, function* () {
1316
- const device = yield ((_b = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) === null || _b === void 0 ? void 0 : _b.getDevice(uuid));
1311
+ const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
1312
+ const device = nobleBle ? yield invokeNobleBle(nobleBle.getDevice(uuid)) : undefined;
1317
1313
  this.updateBlePacketCapacity(uuid, device === null || device === void 0 ? void 0 : device.mtu);
1318
1314
  });
1319
1315
  }
@@ -1536,7 +1532,7 @@ class ElectronBleTransport {
1536
1532
  if (hexString.length === 0) {
1537
1533
  throw new Error(`Buffer ${i + 1} is empty`);
1538
1534
  }
1539
- yield window.desktopApi.nobleBle.write(uuid, hexString);
1535
+ yield invokeNobleBle(window.desktopApi.nobleBle.write(uuid, hexString));
1540
1536
  }
1541
1537
  const response = yield Promise.race([
1542
1538
  runPromise.promise,
@@ -1569,7 +1565,7 @@ class ElectronBleTransport {
1569
1565
  yield this.releaseNative(uuid);
1570
1566
  }
1571
1567
  }
1572
- throw e;
1568
+ throw this.normalizeBluetoothError(e);
1573
1569
  }
1574
1570
  finally {
1575
1571
  if (timeout)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-web-device",
3
- "version": "1.2.2-alpha.12",
3
+ "version": "1.2.2-alpha.120",
4
4
  "author": "OneKey",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -21,13 +21,13 @@
21
21
  "lint:fix": "eslint . --fix"
22
22
  },
23
23
  "dependencies": {
24
- "@onekeyfe/hd-shared": "1.2.2-alpha.12",
25
- "@onekeyfe/hd-transport": "1.2.2-alpha.12"
24
+ "@onekeyfe/hd-shared": "1.2.2-alpha.120",
25
+ "@onekeyfe/hd-transport": "1.2.2-alpha.120"
26
26
  },
27
27
  "devDependencies": {
28
- "@onekeyfe/hd-transport-electron": "1.2.2-alpha.12",
28
+ "@onekeyfe/hd-transport-electron": "1.2.2-alpha.120",
29
29
  "@types/w3c-web-usb": "^1.0.6",
30
30
  "@types/web-bluetooth": "^0.0.17"
31
31
  },
32
- "gitHead": "9b5a45bc55819d22ed752899b4c40aad70de4527"
32
+ "gitHead": "26958fa2be6aa34a07f42403fb7022d2785e70b9"
33
33
  }
@@ -18,7 +18,6 @@ import {
18
18
  HardwareErrorCode,
19
19
  HardwareErrorCodeMessage,
20
20
  createDeferred,
21
- isBleStaleBondErrorText,
22
21
  isBleStaleBondHardwareError,
23
22
  isHeaderChunk,
24
23
  } from '@onekeyfe/hd-shared';
@@ -26,7 +25,7 @@ import {
26
25
  import { resolveBlePacketCapacity } from './ble-packet-capacity';
27
26
 
28
27
  import type { Deferred } from '@onekeyfe/hd-shared';
29
- import type { DesktopAPI } from '@onekeyfe/hd-transport-electron';
28
+ import type { DesktopAPI, NobleBleIpcErrorResponse } from '@onekeyfe/hd-transport-electron';
30
29
  import type {
31
30
  OneKeyDeviceInfo,
32
31
  ProtocolType,
@@ -69,6 +68,22 @@ const toBleDescriptor = (
69
68
  ...(protocolType ? { protocolType } : {}),
70
69
  } as OneKeyDeviceInfo);
71
70
 
71
+ const invokeNobleBle = async <T>(request: Promise<T>): Promise<T> => {
72
+ const response = await (request as Promise<T | NobleBleIpcErrorResponse>);
73
+ if (
74
+ response &&
75
+ typeof response === 'object' &&
76
+ 'type' in response &&
77
+ response.type === 'NobleBleIpcError' &&
78
+ 'success' in response &&
79
+ response.success === false &&
80
+ 'error' in response
81
+ ) {
82
+ return Promise.reject(response.error);
83
+ }
84
+ return response as T;
85
+ };
86
+
72
87
  const BLE_PACKET_SIZE_FALLBACK = 192;
73
88
  const BLE_PACKET_SIZE_MAXIMUM = 244;
74
89
  const BLE_WRITE_DELAY_MS = 5;
@@ -106,9 +121,6 @@ export default class ElectronBleTransport {
106
121
 
107
122
  private deviceProtocolHints: Map<string, ProtocolType> = new Map();
108
123
 
109
- /** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
110
- private confirmedProtocolV2 = new Set<string>();
111
-
112
124
  private deviceMtus: Map<string, number> = new Map();
113
125
 
114
126
  private devicePacketCapacities: Map<string, number> = new Map();
@@ -163,44 +175,24 @@ export default class ElectronBleTransport {
163
175
 
164
176
  private nextNotificationToken = 1;
165
177
 
166
- private toStaleBondError(error: unknown): Error | null {
167
- if (isBleStaleBondHardwareError(error)) {
168
- return error as Error;
169
- }
170
- const errorMessage =
171
- error && typeof error === 'object' && 'message' in error
172
- ? String((error as { message?: unknown }).message ?? '')
173
- : String(error ?? '');
174
- if (!isBleStaleBondErrorText(errorMessage)) {
175
- return null;
176
- }
177
- const normalizedErrorMessage = errorMessage.toLowerCase();
178
- return ERRORS.TypedError(
179
- normalizedErrorMessage.includes('peer removed pairing information') ||
180
- normalizedErrorMessage.includes('cberrordomain:14')
181
- ? HardwareErrorCode.BlePeerRemovedPairingInformation
182
- : HardwareErrorCode.BleDeviceBondError,
183
- errorMessage
184
- );
185
- }
186
-
187
- private handleBluetoothError(error: any, mapProtocolV2StaleBond = false): never {
188
- if (mapProtocolV2StaleBond) {
189
- const staleBondError = this.toStaleBondError(error);
190
- if (staleBondError) {
191
- throw staleBondError;
192
- }
193
- }
178
+ private normalizeBluetoothError(error: any): any {
194
179
  if (error && typeof error === 'object') {
180
+ if (typeof error.errorCode === 'number') {
181
+ return ERRORS.TypedError(
182
+ error.errorCode,
183
+ typeof error.message === 'string' ? error.message : undefined,
184
+ error.params
185
+ );
186
+ }
195
187
  if ('code' in error) {
196
188
  if (error.code === HardwareErrorCode.BlePoweredOff) {
197
- throw ERRORS.TypedError(HardwareErrorCode.BlePoweredOff);
189
+ return ERRORS.TypedError(HardwareErrorCode.BlePoweredOff);
198
190
  }
199
191
  if (error.code === HardwareErrorCode.BleUnsupported) {
200
- throw ERRORS.TypedError(HardwareErrorCode.BleUnsupported);
192
+ return ERRORS.TypedError(HardwareErrorCode.BleUnsupported);
201
193
  }
202
194
  if (error.code === HardwareErrorCode.BlePermissionError) {
203
- throw ERRORS.TypedError(HardwareErrorCode.BlePermissionError);
195
+ return ERRORS.TypedError(HardwareErrorCode.BlePermissionError);
204
196
  }
205
197
  }
206
198
  const errorMessage = error.message || String(error);
@@ -209,16 +201,20 @@ export default class ElectronBleTransport {
209
201
  const permissionMessage = HardwareErrorCodeMessage[HardwareErrorCode.BlePermissionError];
210
202
 
211
203
  if (errorMessage.includes(poweredOffMessage) || errorMessage.includes('poweredOff')) {
212
- throw ERRORS.TypedError(HardwareErrorCode.BlePoweredOff);
204
+ return ERRORS.TypedError(HardwareErrorCode.BlePoweredOff);
213
205
  }
214
206
  if (errorMessage.includes(unsupportedMessage) || errorMessage.includes('unsupported')) {
215
- throw ERRORS.TypedError(HardwareErrorCode.BleUnsupported);
207
+ return ERRORS.TypedError(HardwareErrorCode.BleUnsupported);
216
208
  }
217
209
  if (errorMessage.includes(permissionMessage) || errorMessage.includes('unauthorized')) {
218
- throw ERRORS.TypedError(HardwareErrorCode.BlePermissionError);
210
+ return ERRORS.TypedError(HardwareErrorCode.BlePermissionError);
219
211
  }
220
212
  }
221
- throw error;
213
+ return error;
214
+ }
215
+
216
+ private handleBluetoothError(error: any): never {
217
+ throw this.normalizeBluetoothError(error);
222
218
  }
223
219
 
224
220
  private cleanupDeviceState(deviceId: string): void {
@@ -337,7 +333,7 @@ export default class ElectronBleTransport {
337
333
  if (!window.desktopApi?.nobleBle) {
338
334
  throw new Error('Noble BLE API not available');
339
335
  }
340
- const devices = await window.desktopApi.nobleBle.enumerate();
336
+ const devices = await invokeNobleBle(window.desktopApi.nobleBle.enumerate());
341
337
  this.Log?.debug(`[Electron BLE] enumerate found ${devices.length} device(s):`);
342
338
  for (const dev of devices) {
343
339
  this.Log?.debug(`[Electron BLE] id="${dev.id}" name="${dev.name}"`);
@@ -351,9 +347,6 @@ export default class ElectronBleTransport {
351
347
 
352
348
  async acquire(input: BleAcquireInput) {
353
349
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
354
- const shouldMapProtocolV2StaleBond = expectedProtocol
355
- ? expectedProtocol === 'V2'
356
- : this.confirmedProtocolV2.has(uuid);
357
350
 
358
351
  if (!uuid) {
359
352
  throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
@@ -375,7 +368,7 @@ export default class ElectronBleTransport {
375
368
  throw new Error('Noble BLE API not available');
376
369
  }
377
370
 
378
- const device = await window.desktopApi.nobleBle.getDevice(uuid);
371
+ const device = await invokeNobleBle(window.desktopApi.nobleBle.getDevice(uuid));
379
372
  if (!device) {
380
373
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound, `Device ${uuid} not found`);
381
374
  }
@@ -387,10 +380,10 @@ export default class ElectronBleTransport {
387
380
  }
388
381
 
389
382
  try {
390
- await window.desktopApi.nobleBle.connect(uuid);
383
+ await invokeNobleBle(window.desktopApi.nobleBle.connect(uuid));
391
384
  this.connectedDevices.add(uuid);
392
385
  } catch (error) {
393
- this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
386
+ this.handleBluetoothError(error);
394
387
  }
395
388
 
396
389
  const mtuCleanup = this.createMtuSubscription(uuid);
@@ -402,9 +395,9 @@ export default class ElectronBleTransport {
402
395
  this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
403
396
 
404
397
  try {
405
- await window.desktopApi.nobleBle.subscribe(uuid);
398
+ await invokeNobleBle(window.desktopApi.nobleBle.subscribe(uuid));
406
399
  } catch (error) {
407
- this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
400
+ this.handleBluetoothError(error);
408
401
  }
409
402
  await this.refreshBlePacketCapacity(uuid);
410
403
 
@@ -427,15 +420,20 @@ export default class ElectronBleTransport {
427
420
  } catch (error) {
428
421
  this.Log?.error('[Electron BLE] acquire failed:', error);
429
422
  try {
430
- if (window.desktopApi?.nobleBle && this.connectedDevices.has(uuid)) {
431
- await window.desktopApi.nobleBle.unsubscribe(uuid);
432
- await window.desktopApi.nobleBle.disconnect(uuid);
423
+ const nobleBle = window.desktopApi?.nobleBle;
424
+ if (nobleBle) {
425
+ if (this.connectedDevices.has(uuid)) {
426
+ await invokeNobleBle(nobleBle.unsubscribe(uuid)).catch(cleanupError => {
427
+ this.Log?.debug('[Electron BLE] acquire unsubscribe failed:', cleanupError);
428
+ });
429
+ }
430
+ await invokeNobleBle(nobleBle.disconnect(uuid));
433
431
  }
434
432
  } catch (cleanupError) {
435
433
  this.Log?.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
436
434
  }
437
435
  this.cleanupDeviceState(uuid);
438
- throw error;
436
+ this.handleBluetoothError(error);
439
437
  }
440
438
  }
441
439
 
@@ -458,13 +456,16 @@ export default class ElectronBleTransport {
458
456
  // Hard teardown, error paths only: a link presumed dead must not be reused.
459
457
  private async releaseNative(id: string) {
460
458
  try {
461
- if (this.connectedDevices.has(id)) {
462
- if (window.desktopApi?.nobleBle) {
463
- await window.desktopApi.nobleBle.unsubscribe(id);
464
- await window.desktopApi.nobleBle.disconnect(id);
459
+ const nobleBle = window.desktopApi?.nobleBle;
460
+ if (nobleBle) {
461
+ if (this.connectedDevices.has(id)) {
462
+ await invokeNobleBle(nobleBle.unsubscribe(id)).catch(error => {
463
+ this.Log?.debug('[Electron BLE] release unsubscribe failed:', error);
464
+ });
465
465
  }
466
- this.cleanupDeviceState(id);
466
+ await invokeNobleBle(nobleBle.disconnect(id));
467
467
  }
468
+ this.cleanupDeviceState(id);
468
469
  } catch (error) {
469
470
  this.Log?.error('[Electron BLE] release failed:', error);
470
471
  this.cleanupDeviceState(id);
@@ -490,19 +491,16 @@ export default class ElectronBleTransport {
490
491
  }
491
492
  return;
492
493
  }
493
- await release(id, keepSession);
494
+ await invokeNobleBle(release(id, keepSession));
494
495
  } catch (error) {
495
496
  this.Log?.error('[Electron BLE] logical release failed:', error);
496
497
  this.cleanupDeviceState(id);
497
498
  }
498
499
  }
499
500
 
500
- private createProtocolMismatchError(expected: ProtocolType, uuid: string) {
501
- // A generic Ping miss is not a bond failure. Only a later miss after this
502
- // endpoint already answered V2, or a native encryption/pairing error, is.
503
- const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
501
+ private createProtocolMismatchError(expected: ProtocolType) {
504
502
  return ERRORS.TypedError(
505
- isStaleV2Bond ? HardwareErrorCode.BleDeviceBondError : HardwareErrorCode.RuntimeError,
503
+ HardwareErrorCode.RuntimeError,
506
504
  `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
507
505
  );
508
506
  }
@@ -542,13 +540,12 @@ export default class ElectronBleTransport {
542
540
  }
543
541
 
544
542
  if (expectedProtocol === 'V2') {
545
- if (await this.probeProtocolV2(uuid)) {
543
+ if (await this.probeProtocolV2(uuid, expectedProtocol)) {
546
544
  this.deviceProtocol.set(uuid, 'V2');
547
- this.confirmedProtocolV2.add(uuid);
548
545
  this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
549
546
  return 'V2';
550
547
  }
551
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
548
+ throw this.createProtocolMismatchError(expectedProtocol);
552
549
  }
553
550
 
554
551
  // Protocol must be actively probed after connection. Name, PID, and descriptors only
@@ -567,9 +564,6 @@ export default class ElectronBleTransport {
567
564
  protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
568
565
  if (detected) {
569
566
  this.deviceProtocol.set(uuid, protocol);
570
- if (protocol === 'V2') {
571
- this.confirmedProtocolV2.add(uuid);
572
- }
573
567
  this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
574
568
  return protocol;
575
569
  }
@@ -640,7 +634,7 @@ export default class ElectronBleTransport {
640
634
  }
641
635
  }
642
636
 
643
- private async probeProtocolV2(uuid: string) {
637
+ private async probeProtocolV2(uuid: string, expectedProtocol?: ProtocolType) {
644
638
  if (!this._messages || !this._messagesV2) {
645
639
  return false;
646
640
  }
@@ -656,8 +650,11 @@ export default class ElectronBleTransport {
656
650
  this.v2Assemblers.get(uuid)?.reset();
657
651
  this.resetProtocolV2Frames(uuid);
658
652
  },
653
+ // A declared V2 protocol needs no fallback; preserve the actual link failure.
659
654
  shouldRethrow: error =>
660
- isBleStaleBondHardwareError(error) || isProtocolV2LinkDisabledError(error),
655
+ expectedProtocol === 'V2' ||
656
+ isBleStaleBondHardwareError(error) ||
657
+ isProtocolV2LinkDisabledError(error),
661
658
  });
662
659
  if (!detected) {
663
660
  this.clearProbeProtocol(uuid, 'V2');
@@ -672,18 +669,15 @@ export default class ElectronBleTransport {
672
669
  }
673
670
 
674
671
  try {
675
- await nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
672
+ await invokeNobleBle(nobleBle.write(uuid, hexData, { pacingDelayMs: 0 }));
676
673
  } catch (error) {
677
- const staleBondError = this.toStaleBondError(error);
678
- if (staleBondError) {
679
- throw staleBondError;
680
- }
681
- throw error;
674
+ this.handleBluetoothError(error);
682
675
  }
683
676
  }
684
677
 
685
678
  private async refreshBlePacketCapacity(uuid: string): Promise<void> {
686
- const device = await window.desktopApi?.nobleBle?.getDevice(uuid);
679
+ const nobleBle = window.desktopApi?.nobleBle;
680
+ const device = nobleBle ? await invokeNobleBle(nobleBle.getDevice(uuid)) : undefined;
687
681
  this.updateBlePacketCapacity(uuid, device?.mtu);
688
682
  }
689
683
 
@@ -941,7 +935,7 @@ export default class ElectronBleTransport {
941
935
  if (hexString.length === 0) {
942
936
  throw new Error(`Buffer ${i + 1} is empty`);
943
937
  }
944
- await window.desktopApi.nobleBle.write(uuid, hexString);
938
+ await invokeNobleBle(window.desktopApi.nobleBle.write(uuid, hexString));
945
939
  }
946
940
 
947
941
  const response = await Promise.race([
@@ -979,7 +973,7 @@ export default class ElectronBleTransport {
979
973
  await this.releaseNative(uuid);
980
974
  }
981
975
  }
982
- throw e;
976
+ throw this.normalizeBluetoothError(e);
983
977
  } finally {
984
978
  if (timeout) clearTimeout(timeout);
985
979
  if (this.runPromise === runPromise) {