@onekeyfe/hd-transport-web-device 1.2.8-alpha.104 → 1.2.8-alpha.106

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,31 +511,40 @@ 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 {
389
543
  yield this.closeOpenDevice(input.path);
390
544
  }
545
+ catch (cleanupError) {
546
+ this.logLifecycle('acquire-cleanup', Object.assign({ phase: 'error' }, this.getDiagnosticError(cleanupError, input.path)));
547
+ }
391
548
  finally {
392
549
  this.releaseIdentityProbeHandle(input.path);
393
550
  this.releaseNormalHandles(input.path);
@@ -472,20 +629,48 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
472
629
  return device.device;
473
630
  });
474
631
  }
475
- connect(path, first) {
632
+ connect(path, first, options = {}) {
476
633
  return __awaiter(this, void 0, void 0, function* () {
477
634
  const maxRetries = 5;
478
635
  for (let i = 0; i < maxRetries; i++) {
479
636
  this.assertRunning();
637
+ const attempt = i + 1;
638
+ const startedAt = Date.now();
639
+ const connectionStrategy = this.shouldUseLegacyV1Connection(path, options)
640
+ ? 'protocol-v1-legacy-1.1.31'
641
+ : 'current';
642
+ this.logLifecycle('connect-attempt', {
643
+ phase: 'start',
644
+ attempt,
645
+ maxAttempts: maxRetries,
646
+ first,
647
+ connectionStrategy,
648
+ });
480
649
  try {
481
- return yield this.connectToDevice(path, first);
650
+ const result = yield this.connectToDevice(path, first, options);
651
+ this.logLifecycle('connect-attempt', {
652
+ phase: 'success',
653
+ attempt,
654
+ maxAttempts: maxRetries,
655
+ first,
656
+ connectionStrategy,
657
+ durationMs: Date.now() - startedAt,
658
+ });
659
+ return result;
482
660
  }
483
661
  catch (e) {
484
662
  this.assertRunning();
485
- if (e instanceof hdShared.HardwareError && e.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission) {
663
+ const isTerminalError = this.isHandleOwnershipError(e) ||
664
+ (e instanceof hdShared.HardwareError &&
665
+ (e.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission ||
666
+ e.errorCode === hdShared.HardwareErrorCode.WebUsbDeviceAccessError));
667
+ const willRetry = !isTerminalError && attempt < maxRetries;
668
+ this.logLifecycle('connect-attempt', Object.assign({ phase: 'error', attempt, maxAttempts: maxRetries, first,
669
+ connectionStrategy, durationMs: Date.now() - startedAt, willRetry }, this.getDiagnosticError(e, path)));
670
+ if (isTerminalError) {
486
671
  throw e;
487
672
  }
488
- if (i === maxRetries - 1) {
673
+ if (!willRetry) {
489
674
  throw e;
490
675
  }
491
676
  yield hdShared.wait(i * 200);
@@ -493,6 +678,11 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
493
678
  }
494
679
  });
495
680
  }
681
+ shouldUseLegacyV1Connection(path, options) {
682
+ if (options.expectedProtocol)
683
+ return options.expectedProtocol === 'V1';
684
+ return !options.forceProtocolDetection && this.deviceProtocol.get(path) === 'V1';
685
+ }
496
686
  discoverEndpoints(device) {
497
687
  var _a, _b;
498
688
  for (const config of device.configurations) {
@@ -521,8 +711,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
521
711
  endpointOut: this.endpointId,
522
712
  };
523
713
  }
524
- connectToDevice(path, first) {
525
- var _a;
714
+ connectToDevice(path, first, options = {}) {
526
715
  return __awaiter(this, void 0, void 0, function* () {
527
716
  this.assertRunning();
528
717
  let device = yield this.findDevice(path);
@@ -537,7 +726,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
537
726
  if (!device.opened) {
538
727
  openingHandles.add(device);
539
728
  try {
540
- yield this.runHandleOperation(() => device.open());
729
+ yield this.runLoggedHandleOperation({
730
+ operation: 'open',
731
+ device,
732
+ path,
733
+ task: () => device.open(),
734
+ });
541
735
  }
542
736
  catch (error) {
543
737
  return yield this.throwWebUsbIoError(path, 'open', error);
@@ -546,13 +740,42 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
546
740
  openingHandles.delete(device);
547
741
  }
548
742
  }
743
+ if (this.shouldUseLegacyV1Connection(path, options)) {
744
+ if (first) {
745
+ yield this.runLoggedHandleOperation({
746
+ operation: 'selectConfiguration',
747
+ device,
748
+ path,
749
+ task: () => device.selectConfiguration(this.configurationId),
750
+ });
751
+ try {
752
+ yield this.runLoggedHandleOperation({
753
+ operation: 'reset',
754
+ device,
755
+ path,
756
+ task: () => device.reset(),
757
+ });
758
+ }
759
+ catch (_a) {
760
+ this.assertRunning();
761
+ }
762
+ }
763
+ const legacyEndpoints = this.discoverEndpoints(device);
764
+ this.deviceEndpoints.set(path, legacyEndpoints);
765
+ yield this.claimDeviceInterface(path, device, legacyEndpoints);
766
+ return;
767
+ }
549
768
  if (first || this.deviceProtocol.get(path) !== 'V1') {
550
769
  try {
551
- yield this.runHandleOperation(() => device.reset());
770
+ yield this.runLoggedHandleOperation({
771
+ operation: 'reset',
772
+ device,
773
+ path,
774
+ task: () => device.reset(),
775
+ });
552
776
  }
553
- catch (error) {
777
+ catch (_b) {
554
778
  this.assertRunning();
555
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
556
779
  }
557
780
  }
558
781
  yield this.getConnectedDevices();
@@ -568,7 +791,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
568
791
  if (!device.opened) {
569
792
  openingHandles.add(device);
570
793
  try {
571
- yield this.runHandleOperation(() => device.open());
794
+ yield this.runLoggedHandleOperation({
795
+ operation: 'open',
796
+ device,
797
+ path,
798
+ task: () => device.open(),
799
+ });
572
800
  }
573
801
  catch (error) {
574
802
  return yield this.throwWebUsbIoError(path, 'open', error);
@@ -580,18 +808,34 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
580
808
  if (first ||
581
809
  !device.configuration ||
582
810
  device.configuration.configurationValue !== this.configurationId) {
583
- yield this.runHandleOperation(() => device.selectConfiguration(this.configurationId));
811
+ yield this.runLoggedHandleOperation({
812
+ operation: 'selectConfiguration',
813
+ device,
814
+ path,
815
+ task: () => device.selectConfiguration(this.configurationId),
816
+ });
584
817
  }
585
818
  const endpoints = this.discoverEndpoints(device);
586
819
  this.deviceEndpoints.set(path, endpoints);
820
+ yield this.claimDeviceInterface(path, device, endpoints);
821
+ yield this.clearEndpointHalt(path, device, 'in', endpoints.endpointIn);
822
+ yield this.clearEndpointHalt(path, device, 'out', endpoints.endpointOut);
823
+ });
824
+ }
825
+ claimDeviceInterface(path, device, endpoints) {
826
+ return __awaiter(this, void 0, void 0, function* () {
587
827
  try {
588
- yield this.runHandleOperation(() => device.claimInterface(endpoints.interfaceNumber));
828
+ yield this.runLoggedHandleOperation({
829
+ operation: 'claimInterface',
830
+ device,
831
+ path,
832
+ interfaceNumber: endpoints.interfaceNumber,
833
+ task: () => device.claimInterface(endpoints.interfaceNumber),
834
+ });
589
835
  }
590
836
  catch (error) {
591
837
  return this.throwWebUsbIoError(path, 'claimInterface', error);
592
838
  }
593
- yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
594
- yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
595
839
  });
596
840
  }
597
841
  assertHandleAvailable(device) {
@@ -612,7 +856,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
612
856
  if (this.stopped)
613
857
  return;
614
858
  for (const [device, ownedPath] of this.normalDevices) {
615
- if (ownedPath === path) {
859
+ if (ownedPath === path && !device.opened) {
616
860
  normalHandleOwners.delete(device);
617
861
  this.normalDevices.delete(device);
618
862
  }
@@ -648,41 +892,82 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
648
892
  this.identityProbeCleanupPaths.delete(path);
649
893
  }
650
894
  closeOpenDevice(path) {
651
- var _a, _b, _c, _d;
652
895
  return __awaiter(this, void 0, void 0, function* () {
653
896
  if (this.stopped)
654
897
  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))
898
+ const devices = this.identityProbe
899
+ ? [this.identityProbeDevices.get(path)].filter((device) => device !== undefined)
900
+ : [...this.normalDevices]
901
+ .filter(([, ownedPath]) => ownedPath === path)
902
+ .map(([device]) => device);
903
+ let firstCloseError;
904
+ for (const device of devices) {
905
+ try {
906
+ yield this.closeDeviceHandle(path, device);
907
+ }
908
+ catch (error) {
909
+ firstCloseError !== null && firstCloseError !== void 0 ? firstCloseError : (firstCloseError = error);
910
+ }
911
+ }
912
+ if (firstCloseError) {
913
+ throw firstCloseError instanceof Error
914
+ ? firstCloseError
915
+ : new Error(this.getErrorMessage(firstCloseError));
916
+ }
917
+ });
918
+ }
919
+ closeDeviceHandle(path, current) {
920
+ var _a, _b, _c;
921
+ return __awaiter(this, void 0, void 0, function* () {
922
+ if (!current.opened)
659
923
  return;
660
924
  this.assertHandleAvailable(current);
661
925
  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));
926
+ const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
927
+ if (this.getInterfaceClaimed(current, ifaceNum) === false) {
928
+ this.logSkippedHandleOperation('releaseInterface', current, 'interface-not-claimed', ifaceNum);
665
929
  }
666
- catch (error) {
667
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
930
+ else {
931
+ try {
932
+ yield this.runLoggedHandleOperation({
933
+ operation: 'releaseInterface',
934
+ device: current,
935
+ path,
936
+ interfaceNumber: ifaceNum,
937
+ task: () => current.releaseInterface(ifaceNum),
938
+ });
939
+ }
940
+ catch (error) {
941
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
942
+ }
668
943
  }
669
944
  this.assertHandleAvailable(current);
670
945
  try {
671
- yield this.runHandleOperation(() => current.close());
946
+ yield this.runLoggedHandleOperation({
947
+ operation: 'close',
948
+ device: current,
949
+ path,
950
+ task: () => current.close(),
951
+ });
672
952
  }
673
953
  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;
954
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] close before reconnect failed:', error);
955
+ throw error;
677
956
  }
678
957
  });
679
958
  }
680
- clearEndpointHalt(device, direction, endpointNumber) {
959
+ clearEndpointHalt(path, device, direction, endpointNumber) {
681
960
  var _a;
682
961
  return __awaiter(this, void 0, void 0, function* () {
683
962
  this.assertHandleAvailable(device);
684
963
  try {
685
- yield this.runHandleOperation(() => device.clearHalt(direction, endpointNumber));
964
+ yield this.runLoggedHandleOperation({
965
+ operation: direction === 'in' ? 'clearHaltIn' : 'clearHaltOut',
966
+ device,
967
+ path,
968
+ endpointNumber,
969
+ task: () => device.clearHalt(direction, endpointNumber),
970
+ });
686
971
  }
687
972
  catch (error) {
688
973
  this.assertRunning();
@@ -811,14 +1096,30 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
811
1096
  if (currentDevice.opened) {
812
1097
  const endpoints = this.deviceEndpoints.get(path);
813
1098
  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));
1099
+ if (this.getInterfaceClaimed(currentDevice, ifaceNum) === false) {
1100
+ this.logSkippedHandleOperation('releaseInterface', currentDevice, 'interface-not-claimed', ifaceNum);
816
1101
  }
817
- catch (releaseError) {
818
- this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
1102
+ else {
1103
+ try {
1104
+ yield this.runLoggedHandleOperation({
1105
+ operation: 'releaseInterface',
1106
+ device: currentDevice,
1107
+ path,
1108
+ interfaceNumber: ifaceNum,
1109
+ task: () => currentDevice.releaseInterface(ifaceNum),
1110
+ });
1111
+ }
1112
+ catch (releaseError) {
1113
+ this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
1114
+ }
819
1115
  }
820
1116
  this.assertHandleAvailable(currentDevice);
821
- yield this.runHandleOperation(() => currentDevice.close());
1117
+ yield this.runLoggedHandleOperation({
1118
+ operation: 'close',
1119
+ device: currentDevice,
1120
+ path,
1121
+ task: () => currentDevice.close(),
1122
+ });
822
1123
  }
823
1124
  }
824
1125
  catch (closeError) {
@@ -1129,10 +1430,20 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
1129
1430
  }
1130
1431
  release(path) {
1131
1432
  return __awaiter(this, void 0, void 0, function* () {
1433
+ const startedAt = Date.now();
1434
+ this.logLifecycle('release', { phase: 'start' });
1132
1435
  this.acquiredPaths.delete(path);
1133
1436
  try {
1134
1437
  yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
1135
1438
  yield this.closeOpenDevice(path);
1439
+ this.logLifecycle('release', {
1440
+ phase: 'success',
1441
+ durationMs: Date.now() - startedAt,
1442
+ });
1443
+ }
1444
+ catch (error) {
1445
+ this.logLifecycle('release', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(error, path)));
1446
+ throw error;
1136
1447
  }
1137
1448
  finally {
1138
1449
  this.releaseIdentityProbeHandle(path);