@onekeyfe/hd-transport-web-device 1.2.8-alpha.2 → 1.2.8-alpha.20

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.
@@ -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
@@ -13,6 +13,7 @@ interface DeviceInfo extends OneKeyDeviceInfoBase {
13
13
  protocolType?: ProtocolType;
14
14
  }
15
15
  declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
16
+ private readonly identityProbe;
16
17
  messages: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
17
18
  /** Protobuf schema for Protocol V2 transports. */
18
19
  messagesV2: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
@@ -29,6 +30,11 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
29
30
  private staleProtocolPaths;
30
31
  /** Paths currently acquired (between a successful acquire() and its release()). */
31
32
  private acquiredPaths;
33
+ private identityProbeDevices;
34
+ private normalDevices;
35
+ private pendingHandleOperations;
36
+ private stopPromise?;
37
+ private identityProbeCleanupPaths;
32
38
  /**
33
39
  * The exact USBDevice object each cached protocol was probed against. The
34
40
  * browser returns the same object identity for a device as long as it stays
@@ -53,11 +59,15 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
53
59
  configurationId: number;
54
60
  endpointId: number;
55
61
  interfaceId: number;
56
- constructor();
62
+ constructor(identityProbe?: boolean);
57
63
  /**
58
64
  * Initialize WebUSB transport
59
65
  */
60
66
  init(logger: any, emitter?: EventEmitter): void;
67
+ stop(): Promise<void>;
68
+ private stopResources;
69
+ private assertRunning;
70
+ private runHandleOperation;
61
71
  /**
62
72
  * Announce that a USB device left. Called from the module-scoped disconnect
63
73
  * listener, which is why it is public rather than inlined.
@@ -129,6 +139,12 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
129
139
  * Discovers interface/endpoint numbers from USB descriptors on first connection.
130
140
  */
131
141
  connectToDevice(path: string, first: boolean): Promise<undefined>;
142
+ private assertHandleAvailable;
143
+ private claimNormalHandle;
144
+ private releaseNormalHandles;
145
+ private isHandleOwnershipError;
146
+ private claimIdentityProbeHandle;
147
+ private releaseIdentityProbeHandle;
132
148
  private closeOpenDevice;
133
149
  private clearEndpointHalt;
134
150
  /**
package/dist/index.js CHANGED
@@ -51,11 +51,15 @@ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
51
51
  const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
52
52
  const PACKET_IO_MAX_RETRIES = 3;
53
53
  const PACKET_IO_RETRY_DELAY = 300;
54
+ const USB_DEVICE_IN_USE = 'USB device is already in use';
54
55
  const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
55
56
  const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
56
57
  const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
57
58
  let activeWebUsbTransport;
58
59
  let usbDisconnectListenerAttached = false;
60
+ const identityProbeHandles = new WeakSet();
61
+ const openingHandles = new WeakSet();
62
+ const normalHandleOwners = new WeakMap();
59
63
  function registerActiveWebUsbTransport(instance, usb) {
60
64
  activeWebUsbTransport = instance;
61
65
  if (usbDisconnectListenerAttached)
@@ -72,17 +76,22 @@ function registerActiveWebUsbTransport(instance, usb) {
72
76
  });
73
77
  }
74
78
  class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
75
- constructor() {
79
+ constructor(identityProbe = false) {
76
80
  super({
77
81
  router: transport.PROTOCOL_V2_CHANNEL_USB,
78
82
  maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
79
83
  logPrefix: 'ProtocolV2 WebUSB',
80
84
  });
85
+ this.identityProbe = identityProbe;
81
86
  this.deviceProtocol = new Map();
82
87
  this.confirmedDeviceProtocols = new Map();
83
88
  this.deviceProtocolHints = new Map();
84
89
  this.staleProtocolPaths = new Set();
85
90
  this.acquiredPaths = new Set();
91
+ this.identityProbeDevices = new Map();
92
+ this.normalDevices = new Map();
93
+ this.pendingHandleOperations = new Set();
94
+ this.identityProbeCleanupPaths = new Set();
86
95
  this.probedDeviceObjects = new Map();
87
96
  this.deviceEndpoints = new Map();
88
97
  this.name = 'WebUsbTransport';
@@ -101,7 +110,104 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
101
110
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'WebUSB is not supported by current browsers');
102
111
  }
103
112
  this.usb = usb;
104
- registerActiveWebUsbTransport(this, usb);
113
+ if (!this.identityProbe)
114
+ registerActiveWebUsbTransport(this, usb);
115
+ }
116
+ stop() {
117
+ this.stopped = true;
118
+ this.acquiredPaths.clear();
119
+ if (activeWebUsbTransport === this)
120
+ activeWebUsbTransport = undefined;
121
+ if (!this.stopPromise) {
122
+ this.stopPromise = this.stopResources().catch(error => {
123
+ this.stopPromise = undefined;
124
+ throw error;
125
+ });
126
+ }
127
+ return this.stopPromise;
128
+ }
129
+ stopResources() {
130
+ var _a, _b, _c;
131
+ return __awaiter(this, void 0, void 0, function* () {
132
+ let cleanupError;
133
+ try {
134
+ yield this.disposeProtocolV2UsbLinks('WebUSB transport stopped');
135
+ }
136
+ catch (error) {
137
+ cleanupError = error;
138
+ }
139
+ yield Promise.allSettled([...this.pendingHandleOperations]);
140
+ const handles = this.identityProbe
141
+ ? [...this.identityProbeDevices].map(([path, device]) => [device, path])
142
+ : [...this.normalDevices];
143
+ for (const [device, path] of handles) {
144
+ if (!this.identityProbe && normalHandleOwners.get(device) !== this) {
145
+ this.normalDevices.delete(device);
146
+ }
147
+ else {
148
+ try {
149
+ if (device.opened) {
150
+ const iface = (_b = (_a = this.deviceEndpoints.get(path)) === null || _a === void 0 ? void 0 : _a.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
151
+ try {
152
+ yield device.releaseInterface(iface);
153
+ }
154
+ catch (error) {
155
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] stop releaseInterface failed:', error);
156
+ }
157
+ yield device.close();
158
+ if (device.opened)
159
+ throw new Error('WebUSB handle remains open after stop');
160
+ }
161
+ if (this.identityProbe) {
162
+ identityProbeHandles.delete(device);
163
+ this.identityProbeDevices.delete(path);
164
+ this.identityProbeCleanupPaths.delete(path);
165
+ }
166
+ else {
167
+ normalHandleOwners.delete(device);
168
+ this.normalDevices.delete(device);
169
+ }
170
+ }
171
+ catch (error) {
172
+ cleanupError !== null && cleanupError !== void 0 ? cleanupError : (cleanupError = error);
173
+ }
174
+ }
175
+ }
176
+ this.deviceList = [];
177
+ this.deviceProtocol.clear();
178
+ this.confirmedDeviceProtocols.clear();
179
+ this.deviceProtocolHints.clear();
180
+ this.staleProtocolPaths.clear();
181
+ this.probedDeviceObjects.clear();
182
+ if (!this.normalDevices.size && !this.identityProbeDevices.size) {
183
+ this.deviceEndpoints.clear();
184
+ }
185
+ if (cleanupError) {
186
+ throw cleanupError instanceof Error
187
+ ? cleanupError
188
+ : new Error(this.getErrorMessage(cleanupError));
189
+ }
190
+ });
191
+ }
192
+ assertRunning() {
193
+ if (this.stopped) {
194
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'WebUSB transport is stopped');
195
+ }
196
+ }
197
+ runHandleOperation(operation) {
198
+ return __awaiter(this, void 0, void 0, function* () {
199
+ this.assertRunning();
200
+ const pending = Promise.resolve(operation());
201
+ this.pendingHandleOperations.add(pending);
202
+ try {
203
+ const result = yield pending;
204
+ this.assertRunning();
205
+ return result;
206
+ }
207
+ finally {
208
+ this.pendingHandleOperations.delete(pending);
209
+ }
210
+ });
105
211
  }
106
212
  emitDeviceDisconnect(path, device) {
107
213
  var _a, _b;
@@ -150,15 +256,36 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
150
256
  }
151
257
  enumerate() {
152
258
  return __awaiter(this, void 0, void 0, function* () {
259
+ for (const path of this.identityProbeCleanupPaths) {
260
+ try {
261
+ yield this.release(path);
262
+ }
263
+ catch (_a) {
264
+ }
265
+ }
153
266
  yield this.getConnectedDevices();
154
267
  return this.deviceList;
155
268
  });
156
269
  }
157
270
  getConnectedDevices() {
271
+ var _a;
158
272
  return __awaiter(this, void 0, void 0, function* () {
273
+ this.assertRunning();
159
274
  if (!this.usb)
160
275
  return [];
161
276
  const devices = yield this.usb.getDevices();
277
+ this.assertRunning();
278
+ for (const owner of new Set(devices.map(device => normalHandleOwners.get(device)))) {
279
+ if (owner === null || owner === void 0 ? void 0 : owner.stopped) {
280
+ try {
281
+ yield owner.stop();
282
+ }
283
+ catch (error) {
284
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] stopped owner cleanup failed:', error);
285
+ }
286
+ }
287
+ }
288
+ this.assertRunning();
162
289
  const onekeyDevices = devices.filter(dev => {
163
290
  const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
164
291
  return isOneKey && !hdShared.isKnownTrezorWebUsbDevice(dev);
@@ -185,8 +312,16 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
185
312
  acquire(input) {
186
313
  var _a, _b, _c, _d, _e, _f, _g;
187
314
  return __awaiter(this, void 0, void 0, function* () {
315
+ this.assertRunning();
188
316
  if (!input.path)
189
317
  return;
318
+ if (this.identityProbe) {
319
+ yield this.getConnectedDevices();
320
+ this.claimIdentityProbeHandle(input.path, yield this.findDevice(input.path));
321
+ }
322
+ else {
323
+ this.claimNormalHandle(input.path, yield this.findDevice(input.path));
324
+ }
190
325
  try {
191
326
  yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
192
327
  yield this.closeOpenDevice(input.path);
@@ -236,19 +371,27 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
236
371
  this.deviceProtocolHints.set(input.path, protocolHint);
237
372
  }
238
373
  const detectedProtocol = yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
374
+ this.assertRunning();
239
375
  this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
240
376
  const probedDevice = (_g = this.deviceList.find(d => d.path === input.path)) === null || _g === void 0 ? void 0 : _g.device;
241
377
  if (probedDevice) {
242
378
  this.probedDeviceObjects.set(input.path, probedDevice);
243
379
  }
244
380
  }
381
+ this.assertRunning();
245
382
  this.acquiredPaths.add(input.path);
246
383
  return yield Promise.resolve(input.path);
247
384
  }
248
385
  catch (e) {
249
386
  this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
250
387
  this.acquiredPaths.delete(input.path);
251
- yield this.closeOpenDevice(input.path);
388
+ try {
389
+ yield this.closeOpenDevice(input.path);
390
+ }
391
+ finally {
392
+ this.releaseIdentityProbeHandle(input.path);
393
+ this.releaseNormalHandles(input.path);
394
+ }
252
395
  throw e;
253
396
  }
254
397
  });
@@ -328,10 +471,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
328
471
  return __awaiter(this, void 0, void 0, function* () {
329
472
  const maxRetries = 5;
330
473
  for (let i = 0; i < maxRetries; i++) {
474
+ this.assertRunning();
331
475
  try {
332
476
  return yield this.connectToDevice(path, first);
333
477
  }
334
478
  catch (e) {
479
+ this.assertRunning();
335
480
  if (e instanceof hdShared.HardwareError && e.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission) {
336
481
  throw e;
337
482
  }
@@ -374,42 +519,68 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
374
519
  connectToDevice(path, first) {
375
520
  var _a;
376
521
  return __awaiter(this, void 0, void 0, function* () {
522
+ this.assertRunning();
377
523
  let device = yield this.findDevice(path);
524
+ if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
525
+ yield this.closeOpenDevice(path);
526
+ this.releaseIdentityProbeHandle(path);
527
+ this.claimIdentityProbeHandle(path, device);
528
+ }
529
+ if (!this.identityProbe)
530
+ this.claimNormalHandle(path, device);
531
+ this.assertHandleAvailable(device);
378
532
  if (!device.opened) {
533
+ openingHandles.add(device);
379
534
  try {
380
- yield device.open();
535
+ yield this.runHandleOperation(() => device.open());
381
536
  }
382
537
  catch (error) {
383
- return this.throwWebUsbIoError(path, 'open', error);
538
+ return yield this.throwWebUsbIoError(path, 'open', error);
539
+ }
540
+ finally {
541
+ openingHandles.delete(device);
384
542
  }
385
543
  }
386
544
  if (first || this.deviceProtocol.get(path) !== 'V1') {
387
545
  try {
388
- yield device.reset();
546
+ yield this.runHandleOperation(() => device.reset());
389
547
  }
390
548
  catch (error) {
549
+ this.assertRunning();
391
550
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
392
551
  }
393
552
  }
394
553
  yield this.getConnectedDevices();
395
554
  device = yield this.findDevice(path);
555
+ if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
556
+ yield this.closeOpenDevice(path);
557
+ this.releaseIdentityProbeHandle(path);
558
+ this.claimIdentityProbeHandle(path, device);
559
+ }
560
+ if (!this.identityProbe)
561
+ this.claimNormalHandle(path, device);
562
+ this.assertHandleAvailable(device);
396
563
  if (!device.opened) {
564
+ openingHandles.add(device);
397
565
  try {
398
- yield device.open();
566
+ yield this.runHandleOperation(() => device.open());
399
567
  }
400
568
  catch (error) {
401
- return this.throwWebUsbIoError(path, 'open', error);
569
+ return yield this.throwWebUsbIoError(path, 'open', error);
570
+ }
571
+ finally {
572
+ openingHandles.delete(device);
402
573
  }
403
574
  }
404
575
  if (first ||
405
576
  !device.configuration ||
406
577
  device.configuration.configurationValue !== this.configurationId) {
407
- yield device.selectConfiguration(this.configurationId);
578
+ yield this.runHandleOperation(() => device.selectConfiguration(this.configurationId));
408
579
  }
409
580
  const endpoints = this.discoverEndpoints(device);
410
581
  this.deviceEndpoints.set(path, endpoints);
411
582
  try {
412
- yield device.claimInterface(endpoints.interfaceNumber);
583
+ yield this.runHandleOperation(() => device.claimInterface(endpoints.interfaceNumber));
413
584
  }
414
585
  catch (error) {
415
586
  return this.throwWebUsbIoError(path, 'claimInterface', error);
@@ -418,40 +589,104 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
418
589
  yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
419
590
  });
420
591
  }
592
+ assertHandleAvailable(device) {
593
+ this.assertRunning();
594
+ const normalOwner = normalHandleOwners.get(device);
595
+ if ((normalOwner && normalOwner !== this) ||
596
+ (identityProbeHandles.has(device) &&
597
+ ![...this.identityProbeDevices.values()].includes(device))) {
598
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, USB_DEVICE_IN_USE);
599
+ }
600
+ }
601
+ claimNormalHandle(path, device) {
602
+ this.assertHandleAvailable(device);
603
+ normalHandleOwners.set(device, this);
604
+ this.normalDevices.set(device, path);
605
+ }
606
+ releaseNormalHandles(path) {
607
+ if (this.stopped)
608
+ return;
609
+ for (const [device, ownedPath] of this.normalDevices) {
610
+ if (ownedPath === path) {
611
+ normalHandleOwners.delete(device);
612
+ this.normalDevices.delete(device);
613
+ }
614
+ }
615
+ }
616
+ isHandleOwnershipError(error) {
617
+ return (error instanceof hdShared.HardwareError &&
618
+ error.errorCode === hdShared.HardwareErrorCode.RuntimeError &&
619
+ this.getErrorMessage(error).includes(USB_DEVICE_IN_USE));
620
+ }
621
+ claimIdentityProbeHandle(path, device) {
622
+ this.assertRunning();
623
+ if (device.opened ||
624
+ openingHandles.has(device) ||
625
+ identityProbeHandles.has(device) ||
626
+ normalHandleOwners.has(device)) {
627
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, USB_DEVICE_IN_USE);
628
+ }
629
+ identityProbeHandles.add(device);
630
+ this.identityProbeDevices.set(path, device);
631
+ }
632
+ releaseIdentityProbeHandle(path) {
633
+ if (this.stopped)
634
+ return;
635
+ const device = this.identityProbeDevices.get(path);
636
+ if (device === null || device === void 0 ? void 0 : device.opened) {
637
+ this.identityProbeCleanupPaths.add(path);
638
+ return;
639
+ }
640
+ if (device)
641
+ identityProbeHandles.delete(device);
642
+ this.identityProbeDevices.delete(path);
643
+ this.identityProbeCleanupPaths.delete(path);
644
+ }
421
645
  closeOpenDevice(path) {
422
646
  var _a, _b, _c, _d;
423
647
  return __awaiter(this, void 0, void 0, function* () {
424
- const current = (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
648
+ if (this.stopped)
649
+ return;
650
+ const current = this.identityProbe
651
+ ? this.identityProbeDevices.get(path)
652
+ : (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
425
653
  if (!(current === null || current === void 0 ? void 0 : current.opened))
426
654
  return;
655
+ this.assertHandleAvailable(current);
427
656
  const endpoints = this.deviceEndpoints.get(path);
428
657
  const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
429
658
  try {
430
- yield current.releaseInterface(ifaceNum);
659
+ yield this.runHandleOperation(() => current.releaseInterface(ifaceNum));
431
660
  }
432
661
  catch (error) {
433
662
  (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
434
663
  }
664
+ this.assertHandleAvailable(current);
435
665
  try {
436
- yield current.close();
666
+ yield this.runHandleOperation(() => current.close());
437
667
  }
438
668
  catch (error) {
439
669
  (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
670
+ if (this.identityProbe)
671
+ throw error;
440
672
  }
441
673
  });
442
674
  }
443
675
  clearEndpointHalt(device, direction, endpointNumber) {
444
676
  var _a;
445
677
  return __awaiter(this, void 0, void 0, function* () {
678
+ this.assertHandleAvailable(device);
446
679
  try {
447
- yield device.clearHalt(direction, endpointNumber);
680
+ yield this.runHandleOperation(() => device.clearHalt(direction, endpointNumber));
448
681
  }
449
682
  catch (error) {
683
+ this.assertRunning();
450
684
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] clearHalt ${direction} endpoint ${endpointNumber} failed, continuing:`, error);
451
685
  }
452
686
  });
453
687
  }
454
688
  assertAcquired(path) {
689
+ this.assertRunning();
455
690
  if (!this.acquiredPaths.has(path)) {
456
691
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
457
692
  }
@@ -508,7 +743,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
508
743
  throwWebUsbIoError(path, operation, error) {
509
744
  var _a;
510
745
  return __awaiter(this, void 0, void 0, function* () {
511
- if (this.isWebUsbIoHardwareError(error))
746
+ this.assertRunning();
747
+ if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
512
748
  throw error;
513
749
  if (error instanceof transport.ProtocolV2LinkError && error.code !== 'io')
514
750
  throw error;
@@ -542,7 +778,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
542
778
  return /LIBUSB_ERROR_ACCESS|\b(?:access|permission) denied\b/i.test(message);
543
779
  }
544
780
  isRetryablePacketIoError(error) {
545
- if (this.isWebUsbIoHardwareError(error))
781
+ if (this.stopped)
782
+ return false;
783
+ if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
546
784
  return false;
547
785
  const message = this.getErrorMessage(error).toLowerCase();
548
786
  if (this.isWebUsbPermissionDeniedMessage(message))
@@ -561,21 +799,27 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
561
799
  this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
562
800
  this.staleProtocolPaths.add(path);
563
801
  yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
802
+ this.assertRunning();
564
803
  try {
565
804
  const currentDevice = yield this.findDevice(path);
805
+ this.assertHandleAvailable(currentDevice);
566
806
  if (currentDevice.opened) {
567
807
  const endpoints = this.deviceEndpoints.get(path);
568
808
  const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
569
809
  try {
570
- yield currentDevice.releaseInterface(ifaceNum);
810
+ yield this.runHandleOperation(() => currentDevice.releaseInterface(ifaceNum));
571
811
  }
572
812
  catch (releaseError) {
573
813
  this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
574
814
  }
575
- yield currentDevice.close();
815
+ this.assertHandleAvailable(currentDevice);
816
+ yield this.runHandleOperation(() => currentDevice.close());
576
817
  }
577
818
  }
578
819
  catch (closeError) {
820
+ this.assertRunning();
821
+ if (this.isHandleOwnershipError(closeError))
822
+ throw closeError;
579
823
  this.Log.debug('[WebUsbTransport] close device before retry error:', closeError);
580
824
  }
581
825
  yield this.getConnectedDevices();
@@ -617,6 +861,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
617
861
  yield this.reconnectForPacketIoRetry(path, 'out', attempt, error);
618
862
  }
619
863
  catch (reconnectError) {
864
+ this.assertRunning();
865
+ if (this.isHandleOwnershipError(reconnectError))
866
+ throw reconnectError;
620
867
  lastError = reconnectError;
621
868
  this.Log.debug(`[WebUsbTransport] transferout reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
622
869
  }
@@ -635,7 +882,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
635
882
  const endpoints = this.deviceEndpoints.get(path);
636
883
  const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
637
884
  const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
885
+ this.assertHandleAvailable(device);
638
886
  const result = yield device.transferOut(endpointOut, transferBuffer);
887
+ this.assertHandleAvailable(device);
639
888
  if (result.status !== 'ok') {
640
889
  return this.throwWebUsbIoError(path, 'transferOut', new Error(`transferOut status: ${String(result.status)}`));
641
890
  }
@@ -656,7 +905,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
656
905
  }
657
906
  const endpoints = this.deviceEndpoints.get(path);
658
907
  const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
908
+ this.assertHandleAvailable(device);
659
909
  const result = yield device.transferIn(endpointIn, length);
910
+ this.assertHandleAvailable(device);
660
911
  return this.getTransferInData(result);
661
912
  }
662
913
  catch (error) {
@@ -672,6 +923,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
672
923
  yield this.reconnectForPacketIoRetry(path, 'in', attempt, error);
673
924
  }
674
925
  catch (reconnectError) {
926
+ this.assertRunning();
927
+ if (this.isHandleOwnershipError(reconnectError))
928
+ throw reconnectError;
675
929
  lastError = reconnectError;
676
930
  this.Log.debug(`[WebUsbTransport] transferin reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
677
931
  }
@@ -689,7 +943,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
689
943
  }
690
944
  const endpoints = this.deviceEndpoints.get(path);
691
945
  const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
946
+ this.assertHandleAvailable(device);
692
947
  const result = yield device.transferIn(endpointIn, length);
948
+ this.assertHandleAvailable(device);
693
949
  return this.getTransferInData(result);
694
950
  });
695
951
  }
@@ -749,10 +1005,10 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
749
1005
  return false;
750
1006
  }
751
1007
  try {
752
- yield this.callProtocolV1(path, 'Initialize', {}, {
1008
+ const response = yield this.callProtocolV1(path, this.identityProbe ? 'GetFeatures' : 'Initialize', {}, {
753
1009
  timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
754
1010
  });
755
- return true;
1011
+ return !this.identityProbe || response.type === 'Features';
756
1012
  }
757
1013
  catch (error) {
758
1014
  if (this.isWebUsbIoHardwareError(error))
@@ -869,8 +1125,14 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
869
1125
  release(path) {
870
1126
  return __awaiter(this, void 0, void 0, function* () {
871
1127
  this.acquiredPaths.delete(path);
872
- yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
873
- yield this.closeOpenDevice(path);
1128
+ try {
1129
+ yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
1130
+ yield this.closeOpenDevice(path);
1131
+ }
1132
+ finally {
1133
+ this.releaseIdentityProbeHandle(path);
1134
+ this.releaseNormalHandles(path);
1135
+ }
874
1136
  this.deviceEndpoints.delete(path);
875
1137
  });
876
1138
  }
@@ -1001,11 +1263,14 @@ class ElectronBleTransport {
1001
1263
  if (error.code === hdShared.HardwareErrorCode.BlePermissionError) {
1002
1264
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
1003
1265
  }
1266
+ if (error.code === hdShared.HardwareErrorCode.BleLocationError) {
1267
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError);
1268
+ }
1004
1269
  }
1005
1270
  const errorMessage = error.message || String(error);
1006
1271
  const poweredOffMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BlePoweredOff];
1007
1272
  const unsupportedMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleUnsupported];
1008
- const permissionMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BlePermissionError];
1273
+ const permissionMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleLocationError];
1009
1274
  if (errorMessage.includes(poweredOffMessage) || errorMessage.includes('poweredOff')) {
1010
1275
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff);
1011
1276
  }
@@ -1013,7 +1278,7 @@ class ElectronBleTransport {
1013
1278
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported);
1014
1279
  }
1015
1280
  if (errorMessage.includes(permissionMessage) || errorMessage.includes('unauthorized')) {
1016
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
1281
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError);
1017
1282
  }
1018
1283
  }
1019
1284
  return error;