@onekeyfe/hd-transport-web-device 1.2.8-alpha.101 → 1.2.8-alpha.103

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/dist/index.js CHANGED
@@ -52,11 +52,14 @@ 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
54
  const USB_DEVICE_IN_USE = 'USB device is already in use';
55
+ const WEBUSB_LIFECYCLE_LOG_PREFIX = '[WebUsbTransport][lifecycle]';
56
+ const WEBUSB_ERROR_MESSAGE_LIMIT = 240;
55
57
  const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
56
58
  const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
57
59
  const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
58
60
  let activeWebUsbTransport;
59
61
  let usbDisconnectListenerAttached = false;
62
+ let nextWebUsbTransportInstanceId = 0;
60
63
  const identityProbeHandles = new WeakSet();
61
64
  const openingHandles = new WeakSet();
62
65
  const normalHandleOwners = new WeakMap();
@@ -83,6 +86,10 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
83
86
  logPrefix: 'ProtocolV2 WebUSB',
84
87
  });
85
88
  this.identityProbe = identityProbe;
89
+ this.transportInstanceId = ++nextWebUsbTransportInstanceId;
90
+ this.handleOperationSequence = 0;
91
+ this.deviceLogSequence = 0;
92
+ this.deviceLogIds = new WeakMap();
86
93
  this.deviceProtocol = new Map();
87
94
  this.confirmedDeviceProtocols = new Map();
88
95
  this.deviceProtocolHints = new Map();
@@ -114,12 +121,20 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
114
121
  registerActiveWebUsbTransport(this, usb);
115
122
  }
116
123
  stop() {
124
+ const startedAt = Date.now();
117
125
  this.stopped = true;
118
126
  this.acquiredPaths.clear();
119
127
  if (activeWebUsbTransport === this)
120
128
  activeWebUsbTransport = undefined;
121
129
  if (!this.stopPromise) {
122
- this.stopPromise = this.stopResources().catch(error => {
130
+ this.logLifecycle('stop', { phase: 'start' });
131
+ this.stopPromise = this.stopResources().then(() => {
132
+ this.logLifecycle('stop', {
133
+ phase: 'success',
134
+ durationMs: Date.now() - startedAt,
135
+ });
136
+ }, error => {
137
+ this.logLifecycle('stop', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(error)));
123
138
  this.stopPromise = undefined;
124
139
  throw error;
125
140
  });
@@ -148,13 +163,31 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
148
163
  try {
149
164
  if (device.opened) {
150
165
  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);
166
+ if (this.getInterfaceClaimed(device, iface) === false) {
167
+ this.logSkippedHandleOperation('releaseInterface', device, 'interface-not-claimed', iface);
153
168
  }
154
- catch (error) {
155
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] stop releaseInterface failed:', error);
169
+ else {
170
+ try {
171
+ yield this.runLoggedHandleOperation({
172
+ operation: 'releaseInterface',
173
+ device,
174
+ path,
175
+ interfaceNumber: iface,
176
+ allowStopped: true,
177
+ task: () => device.releaseInterface(iface),
178
+ });
179
+ }
180
+ catch (error) {
181
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] stop releaseInterface failed:', error);
182
+ }
156
183
  }
157
- yield device.close();
184
+ yield this.runLoggedHandleOperation({
185
+ operation: 'close',
186
+ device,
187
+ path,
188
+ allowStopped: true,
189
+ task: () => device.close(),
190
+ });
158
191
  if (device.opened)
159
192
  throw new Error('WebUSB handle remains open after stop');
160
193
  }
@@ -209,6 +242,71 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
209
242
  }
210
243
  });
211
244
  }
245
+ getInterfaceClaimed(device, interfaceNumber) {
246
+ var _a, _b, _c;
247
+ return (_c = (_b = (_a = device.configuration) === null || _a === void 0 ? void 0 : _a.interfaces) === null || _b === void 0 ? void 0 : _b.find(usbInterface => usbInterface.interfaceNumber === interfaceNumber)) === null || _c === void 0 ? void 0 : _c.claimed;
248
+ }
249
+ getDeviceLogId(device) {
250
+ const existing = this.deviceLogIds.get(device);
251
+ if (existing)
252
+ return existing;
253
+ const next = ++this.deviceLogSequence;
254
+ this.deviceLogIds.set(device, next);
255
+ return next;
256
+ }
257
+ getHandleLogState(device, interfaceNumber) {
258
+ var _a, _b;
259
+ const interfaceClaimed = interfaceNumber === undefined ? undefined : this.getInterfaceClaimed(device, interfaceNumber);
260
+ return {
261
+ deviceRefId: this.getDeviceLogId(device),
262
+ opened: device.opened,
263
+ configurationValue: (_b = (_a = device.configuration) === null || _a === void 0 ? void 0 : _a.configurationValue) !== null && _b !== void 0 ? _b : null,
264
+ interfaceNumber: interfaceNumber !== null && interfaceNumber !== void 0 ? interfaceNumber : null,
265
+ interfaceClaimed: interfaceClaimed !== null && interfaceClaimed !== void 0 ? interfaceClaimed : null,
266
+ identityProbe: this.identityProbe,
267
+ stopped: this.stopped,
268
+ acquiredPathCount: this.acquiredPaths.size,
269
+ normalHandleCount: this.normalDevices.size,
270
+ identityProbeHandleCount: this.identityProbeDevices.size,
271
+ pendingHandleOperationCount: this.pendingHandleOperations.size,
272
+ };
273
+ }
274
+ getDiagnosticError(error, path) {
275
+ const errorMessage = this.getErrorMessage(error);
276
+ const redactedMessage = path ? errorMessage.split(path).join('<device>') : errorMessage;
277
+ return {
278
+ errorName: this.getErrorName(error) || 'Error',
279
+ errorMessage: redactedMessage.slice(0, WEBUSB_ERROR_MESSAGE_LIMIT),
280
+ errorCode: error instanceof hdShared.HardwareError ? error.errorCode : undefined,
281
+ };
282
+ }
283
+ logLifecycle(event, payload = {}) {
284
+ var _a;
285
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(WEBUSB_LIFECYCLE_LOG_PREFIX, Object.assign({ event, transportInstanceId: this.transportInstanceId }, payload));
286
+ }
287
+ logSkippedHandleOperation(operation, device, reason, interfaceNumber) {
288
+ const operationId = ++this.handleOperationSequence;
289
+ this.logLifecycle('handle-operation', Object.assign({ operationId,
290
+ operation, phase: 'skipped', reason }, this.getHandleLogState(device, interfaceNumber)));
291
+ }
292
+ runLoggedHandleOperation({ operation, device, task, path, interfaceNumber, endpointNumber, allowStopped = false, }) {
293
+ return __awaiter(this, void 0, void 0, function* () {
294
+ const operationId = ++this.handleOperationSequence;
295
+ const startedAt = Date.now();
296
+ const details = Object.assign({ operationId,
297
+ operation }, (endpointNumber === undefined ? {} : { endpointNumber }));
298
+ this.logLifecycle('handle-operation', Object.assign(Object.assign(Object.assign({}, details), { phase: 'start' }), this.getHandleLogState(device, interfaceNumber)));
299
+ try {
300
+ const result = allowStopped ? yield task() : yield this.runHandleOperation(task);
301
+ this.logLifecycle('handle-operation', Object.assign(Object.assign(Object.assign({}, details), { phase: 'success', durationMs: Date.now() - startedAt }), this.getHandleLogState(device, interfaceNumber)));
302
+ return result;
303
+ }
304
+ catch (error) {
305
+ this.logLifecycle('handle-operation', Object.assign(Object.assign(Object.assign(Object.assign({}, details), { phase: 'error', durationMs: Date.now() - startedAt }), this.getHandleLogState(device, interfaceNumber)), this.getDiagnosticError(error, path)));
306
+ throw error;
307
+ }
308
+ });
309
+ }
212
310
  emitDeviceDisconnect(path, device) {
213
311
  var _a, _b;
214
312
  (_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit(transport.TRANSPORT_EVENT.DEVICE_DISCONNECT, {
@@ -244,11 +342,19 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
244
342
  return __awaiter(this, void 0, void 0, function* () {
245
343
  if (!this.usb)
246
344
  return null;
345
+ const startedAt = Date.now();
346
+ this.logLifecycle('prompt-device-access', { phase: 'start' });
247
347
  try {
248
348
  const device = yield this.usb.requestDevice({ filters: hdShared.ONEKEY_WEBUSB_FILTER });
349
+ this.logLifecycle('prompt-device-access', {
350
+ phase: 'success',
351
+ durationMs: Date.now() - startedAt,
352
+ selected: Boolean(device),
353
+ });
249
354
  return device;
250
355
  }
251
356
  catch (e) {
357
+ this.logLifecycle('prompt-device-access', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(e)));
252
358
  this.Log.debug('requestDevice error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
253
359
  return null;
254
360
  }
@@ -256,15 +362,31 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
256
362
  }
257
363
  enumerate() {
258
364
  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) {
365
+ const startedAt = Date.now();
366
+ this.logLifecycle('enumerate', {
367
+ phase: 'start',
368
+ pendingProbeCleanupCount: this.identityProbeCleanupPaths.size,
369
+ });
370
+ try {
371
+ for (const path of this.identityProbeCleanupPaths) {
372
+ try {
373
+ yield this.release(path);
374
+ }
375
+ catch (_a) {
376
+ }
264
377
  }
378
+ yield this.getConnectedDevices();
379
+ this.logLifecycle('enumerate', {
380
+ phase: 'success',
381
+ durationMs: Date.now() - startedAt,
382
+ deviceCount: this.deviceList.length,
383
+ });
384
+ return this.deviceList;
385
+ }
386
+ catch (error) {
387
+ this.logLifecycle('enumerate', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(error)));
388
+ throw error;
265
389
  }
266
- yield this.getConnectedDevices();
267
- return this.deviceList;
268
390
  });
269
391
  }
270
392
  getConnectedDevices() {
@@ -273,7 +395,21 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
273
395
  this.assertRunning();
274
396
  if (!this.usb)
275
397
  return [];
276
- const devices = yield this.usb.getDevices();
398
+ const startedAt = Date.now();
399
+ this.logLifecycle('get-devices', { phase: 'start' });
400
+ let devices;
401
+ try {
402
+ devices = yield this.usb.getDevices();
403
+ this.logLifecycle('get-devices', {
404
+ phase: 'success',
405
+ durationMs: Date.now() - startedAt,
406
+ authorizedDeviceCount: devices.length,
407
+ });
408
+ }
409
+ catch (error) {
410
+ this.logLifecycle('get-devices', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(error)));
411
+ throw error;
412
+ }
277
413
  this.assertRunning();
278
414
  for (const owner of new Set(devices.map(device => normalHandleOwners.get(device)))) {
279
415
  if (owner === null || owner === void 0 ? void 0 : owner.stopped) {
@@ -310,22 +446,34 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
310
446
  });
311
447
  }
312
448
  acquire(input) {
313
- var _a, _b, _c, _d, _e, _f, _g;
449
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
314
450
  return __awaiter(this, void 0, void 0, function* () {
315
451
  this.assertRunning();
316
452
  if (!input.path)
317
453
  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
- }
454
+ const startedAt = Date.now();
455
+ this.logLifecycle('acquire', {
456
+ phase: 'start',
457
+ expectedProtocol: (_a = input.expectedProtocol) !== null && _a !== void 0 ? _a : null,
458
+ protocolHint: (_b = input.protocolHint) !== null && _b !== void 0 ? _b : null,
459
+ forceProtocolDetection: Boolean(input.forceProtocolDetection),
460
+ skipProtocolProbe: Boolean(input.skipProtocolProbe),
461
+ hasCachedProtocol: this.deviceProtocol.has(input.path),
462
+ });
325
463
  try {
464
+ if (this.identityProbe) {
465
+ yield this.getConnectedDevices();
466
+ this.claimIdentityProbeHandle(input.path, yield this.findDevice(input.path));
467
+ }
468
+ else {
469
+ this.claimNormalHandle(input.path, yield this.findDevice(input.path));
470
+ }
326
471
  yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
327
472
  yield this.closeOpenDevice(input.path);
328
- yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
473
+ yield this.connect((_c = input.path) !== null && _c !== void 0 ? _c : '', true, {
474
+ expectedProtocol: input.expectedProtocol,
475
+ forceProtocolDetection: input.forceProtocolDetection,
476
+ });
329
477
  if (input.skipProtocolProbe) {
330
478
  if (!input.expectedProtocol) {
331
479
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires an expected protocol');
@@ -335,7 +483,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
335
483
  }
336
484
  this.staleProtocolPaths.delete(input.path);
337
485
  this.deviceProtocol.set(input.path, input.expectedProtocol);
338
- const currentDevice = (_b = this.deviceList.find(d => d.path === input.path)) === null || _b === void 0 ? void 0 : _b.device;
486
+ const currentDevice = (_d = this.deviceList.find(d => d.path === input.path)) === null || _d === void 0 ? void 0 : _d.device;
339
487
  if (currentDevice) {
340
488
  this.probedDeviceObjects.set(input.path, currentDevice);
341
489
  }
@@ -350,7 +498,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
350
498
  this.deviceProtocol.delete(input.path);
351
499
  }
352
500
  if (!input.skipProtocolProbe && this.deviceProtocol.has(input.path)) {
353
- const currentDevice = (_c = this.deviceList.find(d => d.path === input.path)) === null || _c === void 0 ? void 0 : _c.device;
501
+ const currentDevice = (_e = this.deviceList.find(d => d.path === input.path)) === null || _e === void 0 ? void 0 : _e.device;
354
502
  if (!currentDevice || currentDevice !== this.probedDeviceObjects.get(input.path)) {
355
503
  this.deviceProtocol.delete(input.path);
356
504
  }
@@ -363,26 +511,32 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
363
511
  this.deviceProtocol.delete(input.path);
364
512
  }
365
513
  if (!this.deviceProtocol.has(input.path)) {
366
- const usbDevice = (_d = this.deviceList.find(device => device.path === input.path)) === null || _d === void 0 ? void 0 : _d.device;
514
+ const usbDevice = (_f = this.deviceList.find(device => device.path === input.path)) === null || _f === void 0 ? void 0 : _f.device;
367
515
  const protocolHint = input.expectedProtocol
368
516
  ? undefined
369
- : (_f = (_e = input.protocolHint) !== null && _e !== void 0 ? _e : this.deviceProtocolHints.get(input.path)) !== null && _f !== void 0 ? _f : hdShared.inferProtocolHintFromUsbId(usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.vendorId, usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.productId);
517
+ : (_h = (_g = input.protocolHint) !== null && _g !== void 0 ? _g : this.deviceProtocolHints.get(input.path)) !== null && _h !== void 0 ? _h : hdShared.inferProtocolHintFromUsbId(usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.vendorId, usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.productId);
370
518
  if (protocolHint) {
371
519
  this.deviceProtocolHints.set(input.path, protocolHint);
372
520
  }
373
521
  const detectedProtocol = yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
374
522
  this.assertRunning();
375
523
  this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
376
- const probedDevice = (_g = this.deviceList.find(d => d.path === input.path)) === null || _g === void 0 ? void 0 : _g.device;
524
+ const probedDevice = (_j = this.deviceList.find(d => d.path === input.path)) === null || _j === void 0 ? void 0 : _j.device;
377
525
  if (probedDevice) {
378
526
  this.probedDeviceObjects.set(input.path, probedDevice);
379
527
  }
380
528
  }
381
529
  this.assertRunning();
382
530
  this.acquiredPaths.add(input.path);
531
+ this.logLifecycle('acquire', {
532
+ phase: 'success',
533
+ durationMs: Date.now() - startedAt,
534
+ protocol: (_k = this.deviceProtocol.get(input.path)) !== null && _k !== void 0 ? _k : null,
535
+ });
383
536
  return yield Promise.resolve(input.path);
384
537
  }
385
538
  catch (e) {
539
+ this.logLifecycle('acquire', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(e, input.path)));
386
540
  this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
387
541
  this.acquiredPaths.delete(input.path);
388
542
  try {
@@ -472,20 +626,48 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
472
626
  return device.device;
473
627
  });
474
628
  }
475
- connect(path, first) {
629
+ connect(path, first, options = {}) {
476
630
  return __awaiter(this, void 0, void 0, function* () {
477
631
  const maxRetries = 5;
478
632
  for (let i = 0; i < maxRetries; i++) {
479
633
  this.assertRunning();
634
+ const attempt = i + 1;
635
+ const startedAt = Date.now();
636
+ const connectionStrategy = this.shouldUseLegacyV1Connection(path, options)
637
+ ? 'protocol-v1-legacy-1.1.31'
638
+ : 'current';
639
+ this.logLifecycle('connect-attempt', {
640
+ phase: 'start',
641
+ attempt,
642
+ maxAttempts: maxRetries,
643
+ first,
644
+ connectionStrategy,
645
+ });
480
646
  try {
481
- return yield this.connectToDevice(path, first);
647
+ const result = yield this.connectToDevice(path, first, options);
648
+ this.logLifecycle('connect-attempt', {
649
+ phase: 'success',
650
+ attempt,
651
+ maxAttempts: maxRetries,
652
+ first,
653
+ connectionStrategy,
654
+ durationMs: Date.now() - startedAt,
655
+ });
656
+ return result;
482
657
  }
483
658
  catch (e) {
484
659
  this.assertRunning();
485
- if (e instanceof hdShared.HardwareError && e.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission) {
660
+ const isTerminalError = this.isHandleOwnershipError(e) ||
661
+ (e instanceof hdShared.HardwareError &&
662
+ (e.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission ||
663
+ e.errorCode === hdShared.HardwareErrorCode.WebUsbDeviceAccessError));
664
+ const willRetry = !isTerminalError && attempt < maxRetries;
665
+ this.logLifecycle('connect-attempt', Object.assign({ phase: 'error', attempt, maxAttempts: maxRetries, first,
666
+ connectionStrategy, durationMs: Date.now() - startedAt, willRetry }, this.getDiagnosticError(e, path)));
667
+ if (isTerminalError) {
486
668
  throw e;
487
669
  }
488
- if (i === maxRetries - 1) {
670
+ if (!willRetry) {
489
671
  throw e;
490
672
  }
491
673
  yield hdShared.wait(i * 200);
@@ -493,6 +675,11 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
493
675
  }
494
676
  });
495
677
  }
678
+ shouldUseLegacyV1Connection(path, options) {
679
+ if (options.expectedProtocol)
680
+ return options.expectedProtocol === 'V1';
681
+ return !options.forceProtocolDetection && this.deviceProtocol.get(path) === 'V1';
682
+ }
496
683
  discoverEndpoints(device) {
497
684
  var _a, _b;
498
685
  for (const config of device.configurations) {
@@ -521,8 +708,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
521
708
  endpointOut: this.endpointId,
522
709
  };
523
710
  }
524
- connectToDevice(path, first) {
525
- var _a;
711
+ connectToDevice(path, first, options = {}) {
526
712
  return __awaiter(this, void 0, void 0, function* () {
527
713
  this.assertRunning();
528
714
  let device = yield this.findDevice(path);
@@ -537,7 +723,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
537
723
  if (!device.opened) {
538
724
  openingHandles.add(device);
539
725
  try {
540
- yield this.runHandleOperation(() => device.open());
726
+ yield this.runLoggedHandleOperation({
727
+ operation: 'open',
728
+ device,
729
+ path,
730
+ task: () => device.open(),
731
+ });
541
732
  }
542
733
  catch (error) {
543
734
  return yield this.throwWebUsbIoError(path, 'open', error);
@@ -546,13 +737,42 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
546
737
  openingHandles.delete(device);
547
738
  }
548
739
  }
740
+ if (this.shouldUseLegacyV1Connection(path, options)) {
741
+ if (first) {
742
+ yield this.runLoggedHandleOperation({
743
+ operation: 'selectConfiguration',
744
+ device,
745
+ path,
746
+ task: () => device.selectConfiguration(this.configurationId),
747
+ });
748
+ try {
749
+ yield this.runLoggedHandleOperation({
750
+ operation: 'reset',
751
+ device,
752
+ path,
753
+ task: () => device.reset(),
754
+ });
755
+ }
756
+ catch (_a) {
757
+ this.assertRunning();
758
+ }
759
+ }
760
+ const legacyEndpoints = this.discoverEndpoints(device);
761
+ this.deviceEndpoints.set(path, legacyEndpoints);
762
+ yield this.claimDeviceInterface(path, device, legacyEndpoints);
763
+ return;
764
+ }
549
765
  if (first || this.deviceProtocol.get(path) !== 'V1') {
550
766
  try {
551
- yield this.runHandleOperation(() => device.reset());
767
+ yield this.runLoggedHandleOperation({
768
+ operation: 'reset',
769
+ device,
770
+ path,
771
+ task: () => device.reset(),
772
+ });
552
773
  }
553
- catch (error) {
774
+ catch (_b) {
554
775
  this.assertRunning();
555
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
556
776
  }
557
777
  }
558
778
  yield this.getConnectedDevices();
@@ -568,7 +788,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
568
788
  if (!device.opened) {
569
789
  openingHandles.add(device);
570
790
  try {
571
- yield this.runHandleOperation(() => device.open());
791
+ yield this.runLoggedHandleOperation({
792
+ operation: 'open',
793
+ device,
794
+ path,
795
+ task: () => device.open(),
796
+ });
572
797
  }
573
798
  catch (error) {
574
799
  return yield this.throwWebUsbIoError(path, 'open', error);
@@ -580,18 +805,34 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
580
805
  if (first ||
581
806
  !device.configuration ||
582
807
  device.configuration.configurationValue !== this.configurationId) {
583
- yield this.runHandleOperation(() => device.selectConfiguration(this.configurationId));
808
+ yield this.runLoggedHandleOperation({
809
+ operation: 'selectConfiguration',
810
+ device,
811
+ path,
812
+ task: () => device.selectConfiguration(this.configurationId),
813
+ });
584
814
  }
585
815
  const endpoints = this.discoverEndpoints(device);
586
816
  this.deviceEndpoints.set(path, endpoints);
817
+ yield this.claimDeviceInterface(path, device, endpoints);
818
+ yield this.clearEndpointHalt(path, device, 'in', endpoints.endpointIn);
819
+ yield this.clearEndpointHalt(path, device, 'out', endpoints.endpointOut);
820
+ });
821
+ }
822
+ claimDeviceInterface(path, device, endpoints) {
823
+ return __awaiter(this, void 0, void 0, function* () {
587
824
  try {
588
- yield this.runHandleOperation(() => device.claimInterface(endpoints.interfaceNumber));
825
+ yield this.runLoggedHandleOperation({
826
+ operation: 'claimInterface',
827
+ device,
828
+ path,
829
+ interfaceNumber: endpoints.interfaceNumber,
830
+ task: () => device.claimInterface(endpoints.interfaceNumber),
831
+ });
589
832
  }
590
833
  catch (error) {
591
834
  return this.throwWebUsbIoError(path, 'claimInterface', error);
592
835
  }
593
- yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
594
- yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
595
836
  });
596
837
  }
597
838
  assertHandleAvailable(device) {
@@ -612,7 +853,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
612
853
  if (this.stopped)
613
854
  return;
614
855
  for (const [device, ownedPath] of this.normalDevices) {
615
- if (ownedPath === path) {
856
+ if (ownedPath === path && !device.opened) {
616
857
  normalHandleOwners.delete(device);
617
858
  this.normalDevices.delete(device);
618
859
  }
@@ -648,41 +889,82 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
648
889
  this.identityProbeCleanupPaths.delete(path);
649
890
  }
650
891
  closeOpenDevice(path) {
651
- var _a, _b, _c, _d;
652
892
  return __awaiter(this, void 0, void 0, function* () {
653
893
  if (this.stopped)
654
894
  return;
655
- const current = this.identityProbe
656
- ? this.identityProbeDevices.get(path)
657
- : (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
658
- if (!(current === null || current === void 0 ? void 0 : current.opened))
895
+ const devices = this.identityProbe
896
+ ? [this.identityProbeDevices.get(path)].filter((device) => device !== undefined)
897
+ : [...this.normalDevices]
898
+ .filter(([, ownedPath]) => ownedPath === path)
899
+ .map(([device]) => device);
900
+ let firstCloseError;
901
+ for (const device of devices) {
902
+ try {
903
+ yield this.closeDeviceHandle(path, device);
904
+ }
905
+ catch (error) {
906
+ firstCloseError !== null && firstCloseError !== void 0 ? firstCloseError : (firstCloseError = error);
907
+ }
908
+ }
909
+ if (firstCloseError) {
910
+ throw firstCloseError instanceof Error
911
+ ? firstCloseError
912
+ : new Error(this.getErrorMessage(firstCloseError));
913
+ }
914
+ });
915
+ }
916
+ closeDeviceHandle(path, current) {
917
+ var _a, _b, _c;
918
+ return __awaiter(this, void 0, void 0, function* () {
919
+ if (!current.opened)
659
920
  return;
660
921
  this.assertHandleAvailable(current);
661
922
  const endpoints = this.deviceEndpoints.get(path);
662
- const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
663
- try {
664
- yield this.runHandleOperation(() => current.releaseInterface(ifaceNum));
923
+ const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
924
+ if (this.getInterfaceClaimed(current, ifaceNum) === false) {
925
+ this.logSkippedHandleOperation('releaseInterface', current, 'interface-not-claimed', ifaceNum);
665
926
  }
666
- catch (error) {
667
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
927
+ else {
928
+ try {
929
+ yield this.runLoggedHandleOperation({
930
+ operation: 'releaseInterface',
931
+ device: current,
932
+ path,
933
+ interfaceNumber: ifaceNum,
934
+ task: () => current.releaseInterface(ifaceNum),
935
+ });
936
+ }
937
+ catch (error) {
938
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
939
+ }
668
940
  }
669
941
  this.assertHandleAvailable(current);
670
942
  try {
671
- yield this.runHandleOperation(() => current.close());
943
+ yield this.runLoggedHandleOperation({
944
+ operation: 'close',
945
+ device: current,
946
+ path,
947
+ task: () => current.close(),
948
+ });
672
949
  }
673
950
  catch (error) {
674
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
675
- if (this.identityProbe)
676
- throw error;
951
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] close before reconnect failed:', error);
952
+ throw error;
677
953
  }
678
954
  });
679
955
  }
680
- clearEndpointHalt(device, direction, endpointNumber) {
956
+ clearEndpointHalt(path, device, direction, endpointNumber) {
681
957
  var _a;
682
958
  return __awaiter(this, void 0, void 0, function* () {
683
959
  this.assertHandleAvailable(device);
684
960
  try {
685
- yield this.runHandleOperation(() => device.clearHalt(direction, endpointNumber));
961
+ yield this.runLoggedHandleOperation({
962
+ operation: direction === 'in' ? 'clearHaltIn' : 'clearHaltOut',
963
+ device,
964
+ path,
965
+ endpointNumber,
966
+ task: () => device.clearHalt(direction, endpointNumber),
967
+ });
686
968
  }
687
969
  catch (error) {
688
970
  this.assertRunning();
@@ -811,14 +1093,30 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
811
1093
  if (currentDevice.opened) {
812
1094
  const endpoints = this.deviceEndpoints.get(path);
813
1095
  const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
814
- try {
815
- yield this.runHandleOperation(() => currentDevice.releaseInterface(ifaceNum));
1096
+ if (this.getInterfaceClaimed(currentDevice, ifaceNum) === false) {
1097
+ this.logSkippedHandleOperation('releaseInterface', currentDevice, 'interface-not-claimed', ifaceNum);
816
1098
  }
817
- catch (releaseError) {
818
- this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
1099
+ else {
1100
+ try {
1101
+ yield this.runLoggedHandleOperation({
1102
+ operation: 'releaseInterface',
1103
+ device: currentDevice,
1104
+ path,
1105
+ interfaceNumber: ifaceNum,
1106
+ task: () => currentDevice.releaseInterface(ifaceNum),
1107
+ });
1108
+ }
1109
+ catch (releaseError) {
1110
+ this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
1111
+ }
819
1112
  }
820
1113
  this.assertHandleAvailable(currentDevice);
821
- yield this.runHandleOperation(() => currentDevice.close());
1114
+ yield this.runLoggedHandleOperation({
1115
+ operation: 'close',
1116
+ device: currentDevice,
1117
+ path,
1118
+ task: () => currentDevice.close(),
1119
+ });
822
1120
  }
823
1121
  }
824
1122
  catch (closeError) {
@@ -1129,10 +1427,20 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
1129
1427
  }
1130
1428
  release(path) {
1131
1429
  return __awaiter(this, void 0, void 0, function* () {
1430
+ const startedAt = Date.now();
1431
+ this.logLifecycle('release', { phase: 'start' });
1132
1432
  this.acquiredPaths.delete(path);
1133
1433
  try {
1134
1434
  yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
1135
1435
  yield this.closeOpenDevice(path);
1436
+ this.logLifecycle('release', {
1437
+ phase: 'success',
1438
+ durationMs: Date.now() - startedAt,
1439
+ });
1440
+ }
1441
+ catch (error) {
1442
+ this.logLifecycle('release', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(error, path)));
1443
+ throw error;
1136
1444
  }
1137
1445
  finally {
1138
1446
  this.releaseIdentityProbeHandle(path);