@onekeyfe/hd-transport-web-device 1.2.3-alpha.1 → 1.2.3-alpha.10

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)
@@ -205,12 +205,52 @@ describe('ElectronBleTransport protocol detection', () => {
205
205
  expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
206
206
  });
207
207
 
208
- test('sends a native disconnect even before acquire marks the device connected', async () => {
208
+ test('cancels a pending native connect acquire has not registered yet', async () => {
209
209
  const device = { id: 'pending-connect-id', name: 'OneKey Pro 2' };
210
210
  const nobleBle = createNobleBle(device);
211
+ // Never calls back: the field case the Core acquire deadline has to break.
212
+ nobleBle.connect.mockImplementation(
213
+ () =>
214
+ new Promise<void>(() => {
215
+ // intentionally never settles
216
+ })
217
+ );
218
+ const bleTransport = configureTransport(nobleBle) as any;
219
+
220
+ const pendingAcquire = bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
221
+ pendingAcquire.catch(() => undefined);
222
+ await new Promise(resolve => {
223
+ setTimeout(resolve, 10);
224
+ });
225
+
226
+ // What the deadline / user abort calls. connectedDevices is still empty
227
+ // here, so this only works because the acquire is tracked as in flight.
228
+ await bleTransport.disconnect(device.id);
229
+
230
+ expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
231
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
232
+ });
233
+
234
+ test('leaves a kept-alive link up when a released device is disconnected', async () => {
235
+ const device = { id: 'kept-alive-id', name: 'OneKey Pro 2' };
236
+ const nobleBle = createNobleBle(device);
237
+ const bleTransport = configureTransport(nobleBle) as any;
238
+
239
+ // No acquire in flight and nothing in connectedDevices: the link was
240
+ // released logically and belongs to the main-process keep-alive timer.
241
+ // The routine post-call cancel must not cost the next call a cold connect.
242
+ await bleTransport.disconnect(device.id);
243
+
244
+ expect(nobleBle.disconnect).not.toHaveBeenCalled();
245
+ expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
246
+ });
247
+
248
+ test('forceNative disconnects a device the renderer no longer tracks', async () => {
249
+ const device = { id: 'presumed-dead-id', name: 'OneKey Pro 2' };
250
+ const nobleBle = createNobleBle(device);
211
251
  const bleTransport = configureTransport(nobleBle) as any;
212
252
 
213
- await bleTransport.releaseNative(device.id);
253
+ await bleTransport.releaseNative(device.id, { forceNative: true });
214
254
 
215
255
  expect(nobleBle.unsubscribe).not.toHaveBeenCalled();
216
256
  expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
@@ -498,7 +538,7 @@ describe('ElectronBleTransport protocol detection', () => {
498
538
  await expect(result).resolves.toBe('rejected');
499
539
  });
500
540
 
501
- test('throws when both protocol probes fail', async () => {
541
+ test('disconnects when both protocol probes fail and reconnects for the next acquire', async () => {
502
542
  const device = { id: 'dead-device-id', name: 'Unknown Device' };
503
543
  const nobleBle = createNobleBle(device);
504
544
 
@@ -510,7 +550,25 @@ describe('ElectronBleTransport protocol detection', () => {
510
550
  await expect(transport.acquire({ uuid: device.id })).rejects.toThrow(
511
551
  /Unable to detect BLE protocol/
512
552
  );
553
+ expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
554
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
513
555
  expect(transport.getProtocolType(device.id)).toBeUndefined();
556
+
557
+ echoProtocolV2(nobleBle, device.id);
558
+ await expect(
559
+ transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
560
+ ).resolves.toMatchObject({ uuid: device.id });
561
+ expect(nobleBle.connect).toHaveBeenCalledTimes(2);
562
+ expect(nobleBle.disconnect.mock.invocationCallOrder.at(-1)).toBeLessThan(
563
+ nobleBle.connect.mock.invocationCallOrder[1]
564
+ );
565
+ await expect(
566
+ transport.call(device.id, 'Ping', { message: 'after-reconnect' })
567
+ ).resolves.toMatchObject({
568
+ type: 'Success',
569
+ message: { message: 'ok' },
570
+ });
571
+ await transport.release(device.id);
514
572
  });
515
573
 
516
574
  test('surfaces Protocol V2 link disabled while the initial V1 probe is active', async () => {
@@ -673,7 +731,7 @@ describe('ElectronBleTransport protocol detection', () => {
673
731
  }
674
732
  });
675
733
 
676
- test('reports a stale bond when a previously confirmed Protocol V2 device stops responding', async () => {
734
+ test('keeps a probe miss retryable after a previously confirmed Protocol V2 connection', async () => {
677
735
  const device = { id: 'reset-pro2-id', name: 'OneKey Pro 2' };
678
736
  const nobleBle = createNobleBle(device);
679
737
  const transport = configureTransport(nobleBle);
@@ -686,7 +744,7 @@ describe('ElectronBleTransport protocol detection', () => {
686
744
  await expect(
687
745
  transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
688
746
  ).rejects.toMatchObject({
689
- errorCode: HardwareErrorCode.BleDeviceBondError,
747
+ errorCode: HardwareErrorCode.RuntimeError,
690
748
  });
691
749
 
692
750
  expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
@@ -694,6 +752,52 @@ describe('ElectronBleTransport protocol detection', () => {
694
752
  expect(transport.getProtocolType(device.id)).toBeUndefined();
695
753
  });
696
754
 
755
+ test.each([false, true])(
756
+ 'preserves an expected Protocol V2 Ping timeout with a prior successful connection: %s',
757
+ async previouslyConnected => {
758
+ const device = { id: 'timeout-pro2-id', name: 'OneKey Pro 2' };
759
+ const nobleBle = createNobleBle(device);
760
+ const transport = configureTransport(nobleBle);
761
+
762
+ if (previouslyConnected) {
763
+ echoProtocolV2(nobleBle, device.id);
764
+ await transport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
765
+ await transport.release(device.id);
766
+ }
767
+ // Keep the real probe and response timer, but drop its notification.
768
+ nobleBle.write.mockImplementation(() => Promise.resolve());
769
+
770
+ await expect(
771
+ transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
772
+ ).rejects.toMatchObject({
773
+ errorCode: HardwareErrorCode.BleTimeoutError,
774
+ message: 'BLE response timeout after 5000ms for Ping',
775
+ });
776
+
777
+ expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
778
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
779
+ expect(transport.getProtocolType(device.id)).toBeUndefined();
780
+ }
781
+ );
782
+
783
+ test('preserves a native stale-bond error during an expected Protocol V2 probe', async () => {
784
+ const device = { id: 'stale-bond-probe-id', name: 'OneKey Pro 2' };
785
+ const nobleBle = createNobleBle(device);
786
+ nobleBle.write.mockRejectedValue({
787
+ name: 'HardwareError',
788
+ message: 'Bluetooth pairing information is no longer valid',
789
+ errorCode: HardwareErrorCode.BleBondInvalid,
790
+ });
791
+ const transport = configureTransport(nobleBle);
792
+
793
+ await expect(
794
+ transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
795
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleBondInvalid });
796
+
797
+ expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
798
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
799
+ });
800
+
697
801
  test('does not take a Protocol V2 hint from the BLE name', async () => {
698
802
  const device = { id: 'named-pro2-id', name: 'OneKey Pro 2' };
699
803
  const nobleBle = createNobleBle(device);
@@ -31,6 +31,39 @@ function buildAcquirableTransport(path = 'pro-webusb') {
31
31
  }
32
32
 
33
33
  describe('WebUsbTransport protocol probe cache', () => {
34
+ test.each([
35
+ ['V1', false, 0],
36
+ ['V1', true, 1],
37
+ ['V2', false, 1],
38
+ [undefined, false, 1],
39
+ ] as const)(
40
+ 'reconnect protocol=%s first=%s resets USB %s times',
41
+ async (protocol, first, resets) => {
42
+ const webusb = new WebUsbTransport();
43
+ const path = 'connected-usb-device';
44
+ const device = {
45
+ opened: true,
46
+ configuration: { configurationValue: 1 },
47
+ configurations: [],
48
+ reset: jest.fn().mockResolvedValue(undefined),
49
+ selectConfiguration: jest.fn().mockResolvedValue(undefined),
50
+ claimInterface: jest.fn().mockResolvedValue(undefined),
51
+ clearHalt: jest.fn().mockResolvedValue(undefined),
52
+ };
53
+ jest.spyOn(webusb, 'findDevice').mockResolvedValue(device as unknown as USBDevice);
54
+ jest.spyOn(webusb, 'getConnectedDevices').mockResolvedValue([]);
55
+ if (protocol) {
56
+ const state = webusb as unknown as { deviceProtocol: Map<string, 'V1' | 'V2'> };
57
+ state.deviceProtocol.set(path, protocol);
58
+ }
59
+
60
+ await webusb.connectToDevice(path, first);
61
+
62
+ expect(device.reset).toHaveBeenCalledTimes(resets);
63
+ expect(device.claimInterface).toHaveBeenCalledWith(0);
64
+ }
65
+ );
66
+
34
67
  test('acquire skips the wire probe when the protocol is already cached', async () => {
35
68
  const webusb = buildAcquirableTransport();
36
69
  const path = 'pro-webusb';
@@ -25,9 +25,9 @@ export default class ElectronBleTransport {
25
25
  Log?: any;
26
26
  emitter?: EventEmitter;
27
27
  private connectedDevices;
28
+ private acquiringDevices;
28
29
  private deviceProtocol;
29
30
  private deviceProtocolHints;
30
- private confirmedProtocolV2;
31
31
  private deviceMtus;
32
32
  private devicePacketCapacities;
33
33
  private v1Buffers;
@@ -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
@@ -216,10 +216,16 @@ declare class ElectronBleTransport {
216
216
  Log?: any;
217
217
  emitter?: EventEmitter;
218
218
  private connectedDevices;
219
+ /**
220
+ * Devices whose acquire() is still in flight.
221
+ *
222
+ * A native connect that never calls back has not reached `connectedDevices`
223
+ * yet, so this is the only record that the renderer is still trying to own
224
+ * the link; `releaseNative()` needs it to cancel that pending connect.
225
+ */
226
+ private acquiringDevices;
219
227
  private deviceProtocol;
220
228
  private deviceProtocolHints;
221
- /** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
222
- private confirmedProtocolV2;
223
229
  private deviceMtus;
224
230
  private devicePacketCapacities;
225
231
  private v1Buffers;
package/dist/index.js CHANGED
@@ -375,11 +375,13 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
375
375
  if (!device.opened) {
376
376
  yield device.open();
377
377
  }
378
- try {
379
- yield device.reset();
380
- }
381
- catch (error) {
382
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
378
+ if (first || this.deviceProtocol.get(path) !== 'V1') {
379
+ try {
380
+ yield device.reset();
381
+ }
382
+ catch (error) {
383
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
384
+ }
383
385
  }
384
386
  yield this.getConnectedDevices();
385
387
  device = yield this.findDevice(path);
@@ -848,9 +850,9 @@ class ElectronBleTransport {
848
850
  this.runPromise = null;
849
851
  this.runPromiseDeviceId = null;
850
852
  this.connectedDevices = new Set();
853
+ this.acquiringDevices = new Set();
851
854
  this.deviceProtocol = new Map();
852
855
  this.deviceProtocolHints = new Map();
853
- this.confirmedProtocolV2 = new Set();
854
856
  this.deviceMtus = new Map();
855
857
  this.devicePacketCapacities = new Map();
856
858
  this.v1Buffers = new Map();
@@ -874,7 +876,7 @@ class ElectronBleTransport {
874
876
  this.rejectProtocolV2Frames(uuid, new Error(reason));
875
877
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
876
878
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
877
- yield this.releaseNative(uuid);
879
+ yield this.releaseNative(uuid, { forceNative: true });
878
880
  }
879
881
  }),
880
882
  });
@@ -1033,6 +1035,7 @@ class ElectronBleTransport {
1033
1035
  this.runPromise = null;
1034
1036
  this.runPromiseDeviceId = null;
1035
1037
  }
1038
+ this.acquiringDevices.add(uuid);
1036
1039
  try {
1037
1040
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
1038
1041
  throw new Error('Noble BLE API not available');
@@ -1099,6 +1102,9 @@ class ElectronBleTransport {
1099
1102
  this.cleanupDeviceState(uuid);
1100
1103
  this.handleBluetoothError(error);
1101
1104
  }
1105
+ finally {
1106
+ this.acquiringDevices.delete(uuid);
1107
+ }
1102
1108
  });
1103
1109
  }
1104
1110
  release(id, _onclose, keepSession) {
@@ -1119,12 +1125,14 @@ class ElectronBleTransport {
1119
1125
  return this.releaseNative(id);
1120
1126
  });
1121
1127
  }
1122
- releaseNative(id) {
1128
+ releaseNative(id, options) {
1123
1129
  var _a, _b;
1124
1130
  return __awaiter(this, void 0, void 0, function* () {
1131
+ const rendererOwnsLink = this.connectedDevices.has(id) || this.acquiringDevices.has(id);
1132
+ const shouldDisconnect = (options === null || options === void 0 ? void 0 : options.forceNative) === true || rendererOwnsLink;
1125
1133
  try {
1126
1134
  const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
1127
- if (nobleBle) {
1135
+ if (nobleBle && shouldDisconnect) {
1128
1136
  if (this.connectedDevices.has(id)) {
1129
1137
  yield invokeNobleBle(nobleBle.unsubscribe(id)).catch(error => {
1130
1138
  var _a;
@@ -1165,9 +1173,8 @@ class ElectronBleTransport {
1165
1173
  }
1166
1174
  });
1167
1175
  }
1168
- createProtocolMismatchError(expected, uuid) {
1169
- const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
1170
- return hdShared.ERRORS.TypedError(isStaleV2Bond ? hdShared.HardwareErrorCode.BleDeviceBondError : hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1176
+ createProtocolMismatchError(expected) {
1177
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
1171
1178
  }
1172
1179
  createProtocolDetectionError() {
1173
1180
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
@@ -1186,13 +1193,12 @@ class ElectronBleTransport {
1186
1193
  return 'V1';
1187
1194
  }
1188
1195
  if (expectedProtocol === 'V2') {
1189
- if (yield this.probeProtocolV2(uuid)) {
1196
+ if (yield this.probeProtocolV2(uuid, expectedProtocol)) {
1190
1197
  this.deviceProtocol.set(uuid, 'V2');
1191
- this.confirmedProtocolV2.add(uuid);
1192
1198
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
1193
1199
  return 'V2';
1194
1200
  }
1195
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
1201
+ throw this.createProtocolMismatchError(expectedProtocol);
1196
1202
  }
1197
1203
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
1198
1204
  for (let i = 0; i < probeOrder.length; i += 1) {
@@ -1203,9 +1209,6 @@ class ElectronBleTransport {
1203
1209
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
1204
1210
  if (detected) {
1205
1211
  this.deviceProtocol.set(uuid, protocol);
1206
- if (protocol === 'V2') {
1207
- this.confirmedProtocolV2.add(uuid);
1208
- }
1209
1212
  (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
1210
1213
  return protocol;
1211
1214
  }
@@ -1268,7 +1271,7 @@ class ElectronBleTransport {
1268
1271
  }
1269
1272
  });
1270
1273
  }
1271
- probeProtocolV2(uuid) {
1274
+ probeProtocolV2(uuid, expectedProtocol) {
1272
1275
  var _a;
1273
1276
  return __awaiter(this, void 0, void 0, function* () {
1274
1277
  if (!this._messages || !this._messagesV2) {
@@ -1286,7 +1289,9 @@ class ElectronBleTransport {
1286
1289
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1287
1290
  this.resetProtocolV2Frames(uuid);
1288
1291
  },
1289
- shouldRethrow: error => hdShared.isBleStaleBondHardwareError(error) || transport.isProtocolV2LinkDisabledError(error),
1292
+ shouldRethrow: error => expectedProtocol === 'V2' ||
1293
+ hdShared.isBleStaleBondHardwareError(error) ||
1294
+ transport.isProtocolV2LinkDisabledError(error),
1290
1295
  });
1291
1296
  if (!detected) {
1292
1297
  this.clearProbeProtocol(uuid, 'V2');
@@ -1566,7 +1571,7 @@ class ElectronBleTransport {
1566
1571
  this.notificationCleanups.delete(uuid);
1567
1572
  this.notificationTokens.delete(uuid);
1568
1573
  if (!isProbeTimeout) {
1569
- yield this.releaseNative(uuid);
1574
+ yield this.releaseNative(uuid, { forceNative: true });
1570
1575
  }
1571
1576
  }
1572
1577
  throw this.normalizeBluetoothError(e);
@@ -1 +1 @@
1
- {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-web-device",
3
- "version": "1.2.3-alpha.1",
3
+ "version": "1.2.3-alpha.10",
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.3-alpha.1",
25
- "@onekeyfe/hd-transport": "1.2.3-alpha.1"
24
+ "@onekeyfe/hd-shared": "1.2.3-alpha.10",
25
+ "@onekeyfe/hd-transport": "1.2.3-alpha.10"
26
26
  },
27
27
  "devDependencies": {
28
- "@onekeyfe/hd-transport-electron": "1.2.3-alpha.1",
28
+ "@onekeyfe/hd-transport-electron": "1.2.3-alpha.10",
29
29
  "@types/w3c-web-usb": "^1.0.6",
30
30
  "@types/web-bluetooth": "^0.0.17"
31
31
  },
32
- "gitHead": "fd4221863c00240fc4870fc50f8c517d97426de2"
32
+ "gitHead": "2295285a846281941c21955684281446f587d993"
33
33
  }
@@ -117,13 +117,19 @@ export default class ElectronBleTransport {
117
117
 
118
118
  private connectedDevices: Set<string> = new Set();
119
119
 
120
+ /**
121
+ * Devices whose acquire() is still in flight.
122
+ *
123
+ * A native connect that never calls back has not reached `connectedDevices`
124
+ * yet, so this is the only record that the renderer is still trying to own
125
+ * the link; `releaseNative()` needs it to cancel that pending connect.
126
+ */
127
+ private acquiringDevices: Set<string> = new Set();
128
+
120
129
  private deviceProtocol: Map<string, ProtocolType> = new Map();
121
130
 
122
131
  private deviceProtocolHints: Map<string, ProtocolType> = new Map();
123
132
 
124
- /** Endpoints that answered a V2 probe in this transport lifetime. Survives disconnect. */
125
- private confirmedProtocolV2 = new Set<string>();
126
-
127
133
  private deviceMtus: Map<string, number> = new Map();
128
134
 
129
135
  private devicePacketCapacities: Map<string, number> = new Map();
@@ -152,7 +158,7 @@ export default class ElectronBleTransport {
152
158
  this.rejectProtocolV2Frames(uuid, new Error(reason));
153
159
  this.Log?.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
154
160
  if (reason.startsWith('Protocol V2 link-fatal error:')) {
155
- await this.releaseNative(uuid);
161
+ await this.releaseNative(uuid, { forceNative: true });
156
162
  }
157
163
  },
158
164
  });
@@ -366,6 +372,7 @@ export default class ElectronBleTransport {
366
372
  this.runPromiseDeviceId = null;
367
373
  }
368
374
 
375
+ this.acquiringDevices.add(uuid);
369
376
  try {
370
377
  if (!window.desktopApi?.nobleBle) {
371
378
  throw new Error('Noble BLE API not available');
@@ -437,6 +444,8 @@ export default class ElectronBleTransport {
437
444
  }
438
445
  this.cleanupDeviceState(uuid);
439
446
  this.handleBluetoothError(error);
447
+ } finally {
448
+ this.acquiringDevices.delete(uuid);
440
449
  }
441
450
  }
442
451
 
@@ -457,10 +466,23 @@ export default class ElectronBleTransport {
457
466
  }
458
467
 
459
468
  // Hard teardown, error paths only: a link presumed dead must not be reused.
460
- private async releaseNative(id: string) {
469
+ //
470
+ // The native disconnect is sent while the renderer still owns the link, or is
471
+ // still trying to get it: a stuck acquire has not reached `connectedDevices`
472
+ // yet, and its pending native connect can only be cancelled from here, so the
473
+ // Core acquire deadline would otherwise be unable to terminate it.
474
+ //
475
+ // It is NOT sent for a device the renderer has already released logically.
476
+ // That link belongs to the main-process keep-alive timer, and dropping it on
477
+ // the routine post-call `cancel()` (Device.interruptionFromUser, no acquire
478
+ // held, empty request queue) costs a full cold reconnect on the very next
479
+ // operation — measured 2.93s on Pro and 17-26s on Pro 2.
480
+ private async releaseNative(id: string, options?: { forceNative?: boolean }) {
481
+ const rendererOwnsLink = this.connectedDevices.has(id) || this.acquiringDevices.has(id);
482
+ const shouldDisconnect = options?.forceNative === true || rendererOwnsLink;
461
483
  try {
462
484
  const nobleBle = window.desktopApi?.nobleBle;
463
- if (nobleBle) {
485
+ if (nobleBle && shouldDisconnect) {
464
486
  if (this.connectedDevices.has(id)) {
465
487
  await invokeNobleBle(nobleBle.unsubscribe(id)).catch(error => {
466
488
  this.Log?.debug('[Electron BLE] release unsubscribe failed:', error);
@@ -501,12 +523,9 @@ export default class ElectronBleTransport {
501
523
  }
502
524
  }
503
525
 
504
- private createProtocolMismatchError(expected: ProtocolType, uuid: string) {
505
- // A generic Ping miss is not a bond failure. Only a later miss after this
506
- // endpoint already answered V2, or a native encryption/pairing error, is.
507
- const isStaleV2Bond = expected === 'V2' && this.confirmedProtocolV2.has(uuid);
526
+ private createProtocolMismatchError(expected: ProtocolType) {
508
527
  return ERRORS.TypedError(
509
- isStaleV2Bond ? HardwareErrorCode.BleDeviceBondError : HardwareErrorCode.RuntimeError,
528
+ HardwareErrorCode.RuntimeError,
510
529
  `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`
511
530
  );
512
531
  }
@@ -546,13 +565,12 @@ export default class ElectronBleTransport {
546
565
  }
547
566
 
548
567
  if (expectedProtocol === 'V2') {
549
- if (await this.probeProtocolV2(uuid)) {
568
+ if (await this.probeProtocolV2(uuid, expectedProtocol)) {
550
569
  this.deviceProtocol.set(uuid, 'V2');
551
- this.confirmedProtocolV2.add(uuid);
552
570
  this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
553
571
  return 'V2';
554
572
  }
555
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
573
+ throw this.createProtocolMismatchError(expectedProtocol);
556
574
  }
557
575
 
558
576
  // Protocol must be actively probed after connection. Name, PID, and descriptors only
@@ -571,9 +589,6 @@ export default class ElectronBleTransport {
571
589
  protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
572
590
  if (detected) {
573
591
  this.deviceProtocol.set(uuid, protocol);
574
- if (protocol === 'V2') {
575
- this.confirmedProtocolV2.add(uuid);
576
- }
577
592
  this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> ${protocol}`);
578
593
  return protocol;
579
594
  }
@@ -644,7 +659,7 @@ export default class ElectronBleTransport {
644
659
  }
645
660
  }
646
661
 
647
- private async probeProtocolV2(uuid: string) {
662
+ private async probeProtocolV2(uuid: string, expectedProtocol?: ProtocolType) {
648
663
  if (!this._messages || !this._messagesV2) {
649
664
  return false;
650
665
  }
@@ -660,8 +675,11 @@ export default class ElectronBleTransport {
660
675
  this.v2Assemblers.get(uuid)?.reset();
661
676
  this.resetProtocolV2Frames(uuid);
662
677
  },
678
+ // A declared V2 protocol needs no fallback; preserve the actual link failure.
663
679
  shouldRethrow: error =>
664
- isBleStaleBondHardwareError(error) || isProtocolV2LinkDisabledError(error),
680
+ expectedProtocol === 'V2' ||
681
+ isBleStaleBondHardwareError(error) ||
682
+ isProtocolV2LinkDisabledError(error),
665
683
  });
666
684
  if (!detected) {
667
685
  this.clearProbeProtocol(uuid, 'V2');
@@ -977,7 +995,7 @@ export default class ElectronBleTransport {
977
995
  this.notificationCleanups.delete(uuid);
978
996
  this.notificationTokens.delete(uuid);
979
997
  if (!isProbeTimeout) {
980
- await this.releaseNative(uuid);
998
+ await this.releaseNative(uuid, { forceNative: true });
981
999
  }
982
1000
  }
983
1001
  throw this.normalizeBluetoothError(e);
package/src/webusb.ts CHANGED
@@ -566,10 +566,14 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
566
566
  if (!device.opened) {
567
567
  await device.open();
568
568
  }
569
- try {
570
- await device.reset();
571
- } catch (error) {
572
- this.Log?.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
569
+ // A V1 packet retry continues an in-flight exchange. Preserve its endpoint
570
+ // state as in the legacy transport; probing and V2 recovery still reset.
571
+ if (first || this.deviceProtocol.get(path) !== 'V1') {
572
+ try {
573
+ await device.reset();
574
+ } catch (error) {
575
+ this.Log?.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
576
+ }
573
577
  }
574
578
  await this.getConnectedDevices();
575
579
  device = await this.findDevice(path);