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

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,46 @@ 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 = {
761
+ interfaceNumber: this.interfaceId,
762
+ endpointIn: this.endpointId,
763
+ endpointOut: this.endpointId,
764
+ };
765
+ this.deviceEndpoints.set(path, legacyEndpoints);
766
+ yield this.claimDeviceInterface(path, device, legacyEndpoints);
767
+ return;
768
+ }
549
769
  if (first || this.deviceProtocol.get(path) !== 'V1') {
550
770
  try {
551
- yield this.runHandleOperation(() => device.reset());
771
+ yield this.runLoggedHandleOperation({
772
+ operation: 'reset',
773
+ device,
774
+ path,
775
+ task: () => device.reset(),
776
+ });
552
777
  }
553
- catch (error) {
778
+ catch (_b) {
554
779
  this.assertRunning();
555
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
556
780
  }
557
781
  }
558
782
  yield this.getConnectedDevices();
@@ -568,7 +792,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
568
792
  if (!device.opened) {
569
793
  openingHandles.add(device);
570
794
  try {
571
- yield this.runHandleOperation(() => device.open());
795
+ yield this.runLoggedHandleOperation({
796
+ operation: 'open',
797
+ device,
798
+ path,
799
+ task: () => device.open(),
800
+ });
572
801
  }
573
802
  catch (error) {
574
803
  return yield this.throwWebUsbIoError(path, 'open', error);
@@ -580,18 +809,34 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
580
809
  if (first ||
581
810
  !device.configuration ||
582
811
  device.configuration.configurationValue !== this.configurationId) {
583
- yield this.runHandleOperation(() => device.selectConfiguration(this.configurationId));
812
+ yield this.runLoggedHandleOperation({
813
+ operation: 'selectConfiguration',
814
+ device,
815
+ path,
816
+ task: () => device.selectConfiguration(this.configurationId),
817
+ });
584
818
  }
585
819
  const endpoints = this.discoverEndpoints(device);
586
820
  this.deviceEndpoints.set(path, endpoints);
821
+ yield this.claimDeviceInterface(path, device, endpoints);
822
+ yield this.clearEndpointHalt(path, device, 'in', endpoints.endpointIn);
823
+ yield this.clearEndpointHalt(path, device, 'out', endpoints.endpointOut);
824
+ });
825
+ }
826
+ claimDeviceInterface(path, device, endpoints) {
827
+ return __awaiter(this, void 0, void 0, function* () {
587
828
  try {
588
- yield this.runHandleOperation(() => device.claimInterface(endpoints.interfaceNumber));
829
+ yield this.runLoggedHandleOperation({
830
+ operation: 'claimInterface',
831
+ device,
832
+ path,
833
+ interfaceNumber: endpoints.interfaceNumber,
834
+ task: () => device.claimInterface(endpoints.interfaceNumber),
835
+ });
589
836
  }
590
837
  catch (error) {
591
838
  return this.throwWebUsbIoError(path, 'claimInterface', error);
592
839
  }
593
- yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
594
- yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
595
840
  });
596
841
  }
597
842
  assertHandleAvailable(device) {
@@ -612,7 +857,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
612
857
  if (this.stopped)
613
858
  return;
614
859
  for (const [device, ownedPath] of this.normalDevices) {
615
- if (ownedPath === path) {
860
+ if (ownedPath === path && !device.opened) {
616
861
  normalHandleOwners.delete(device);
617
862
  this.normalDevices.delete(device);
618
863
  }
@@ -648,41 +893,82 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
648
893
  this.identityProbeCleanupPaths.delete(path);
649
894
  }
650
895
  closeOpenDevice(path) {
651
- var _a, _b, _c, _d;
652
896
  return __awaiter(this, void 0, void 0, function* () {
653
897
  if (this.stopped)
654
898
  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))
899
+ const devices = this.identityProbe
900
+ ? [this.identityProbeDevices.get(path)].filter((device) => device !== undefined)
901
+ : [...this.normalDevices]
902
+ .filter(([, ownedPath]) => ownedPath === path)
903
+ .map(([device]) => device);
904
+ let firstCloseError;
905
+ for (const device of devices) {
906
+ try {
907
+ yield this.closeDeviceHandle(path, device);
908
+ }
909
+ catch (error) {
910
+ firstCloseError !== null && firstCloseError !== void 0 ? firstCloseError : (firstCloseError = error);
911
+ }
912
+ }
913
+ if (firstCloseError) {
914
+ throw firstCloseError instanceof Error
915
+ ? firstCloseError
916
+ : new Error(this.getErrorMessage(firstCloseError));
917
+ }
918
+ });
919
+ }
920
+ closeDeviceHandle(path, current) {
921
+ var _a, _b, _c;
922
+ return __awaiter(this, void 0, void 0, function* () {
923
+ if (!current.opened)
659
924
  return;
660
925
  this.assertHandleAvailable(current);
661
926
  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));
927
+ const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
928
+ if (this.getInterfaceClaimed(current, ifaceNum) === false) {
929
+ this.logSkippedHandleOperation('releaseInterface', current, 'interface-not-claimed', ifaceNum);
665
930
  }
666
- catch (error) {
667
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
931
+ else {
932
+ try {
933
+ yield this.runLoggedHandleOperation({
934
+ operation: 'releaseInterface',
935
+ device: current,
936
+ path,
937
+ interfaceNumber: ifaceNum,
938
+ task: () => current.releaseInterface(ifaceNum),
939
+ });
940
+ }
941
+ catch (error) {
942
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
943
+ }
668
944
  }
669
945
  this.assertHandleAvailable(current);
670
946
  try {
671
- yield this.runHandleOperation(() => current.close());
947
+ yield this.runLoggedHandleOperation({
948
+ operation: 'close',
949
+ device: current,
950
+ path,
951
+ task: () => current.close(),
952
+ });
672
953
  }
673
954
  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;
955
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] close before reconnect failed:', error);
956
+ throw error;
677
957
  }
678
958
  });
679
959
  }
680
- clearEndpointHalt(device, direction, endpointNumber) {
960
+ clearEndpointHalt(path, device, direction, endpointNumber) {
681
961
  var _a;
682
962
  return __awaiter(this, void 0, void 0, function* () {
683
963
  this.assertHandleAvailable(device);
684
964
  try {
685
- yield this.runHandleOperation(() => device.clearHalt(direction, endpointNumber));
965
+ yield this.runLoggedHandleOperation({
966
+ operation: direction === 'in' ? 'clearHaltIn' : 'clearHaltOut',
967
+ device,
968
+ path,
969
+ endpointNumber,
970
+ task: () => device.clearHalt(direction, endpointNumber),
971
+ });
686
972
  }
687
973
  catch (error) {
688
974
  this.assertRunning();
@@ -811,14 +1097,30 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
811
1097
  if (currentDevice.opened) {
812
1098
  const endpoints = this.deviceEndpoints.get(path);
813
1099
  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));
1100
+ if (this.getInterfaceClaimed(currentDevice, ifaceNum) === false) {
1101
+ this.logSkippedHandleOperation('releaseInterface', currentDevice, 'interface-not-claimed', ifaceNum);
816
1102
  }
817
- catch (releaseError) {
818
- this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
1103
+ else {
1104
+ try {
1105
+ yield this.runLoggedHandleOperation({
1106
+ operation: 'releaseInterface',
1107
+ device: currentDevice,
1108
+ path,
1109
+ interfaceNumber: ifaceNum,
1110
+ task: () => currentDevice.releaseInterface(ifaceNum),
1111
+ });
1112
+ }
1113
+ catch (releaseError) {
1114
+ this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
1115
+ }
819
1116
  }
820
1117
  this.assertHandleAvailable(currentDevice);
821
- yield this.runHandleOperation(() => currentDevice.close());
1118
+ yield this.runLoggedHandleOperation({
1119
+ operation: 'close',
1120
+ device: currentDevice,
1121
+ path,
1122
+ task: () => currentDevice.close(),
1123
+ });
822
1124
  }
823
1125
  }
824
1126
  catch (closeError) {
@@ -1129,10 +1431,20 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
1129
1431
  }
1130
1432
  release(path) {
1131
1433
  return __awaiter(this, void 0, void 0, function* () {
1434
+ const startedAt = Date.now();
1435
+ this.logLifecycle('release', { phase: 'start' });
1132
1436
  this.acquiredPaths.delete(path);
1133
1437
  try {
1134
1438
  yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
1135
1439
  yield this.closeOpenDevice(path);
1440
+ this.logLifecycle('release', {
1441
+ phase: 'success',
1442
+ durationMs: Date.now() - startedAt,
1443
+ });
1444
+ }
1445
+ catch (error) {
1446
+ this.logLifecycle('release', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(error, path)));
1447
+ throw error;
1136
1448
  }
1137
1449
  finally {
1138
1450
  this.releaseIdentityProbeHandle(path);