@onekeyfe/hd-transport-web-device 1.2.8-alpha.104 → 1.2.8-alpha.105
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/__tests__/webusb-identity-probe.test.ts +47 -0
- package/__tests__/webusb-protocol-cache.test.ts +219 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.js +373 -65
- package/dist/webusb.d.ts +21 -2
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/webusb.ts +494 -57
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.
|
|
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
|
-
|
|
152
|
-
|
|
166
|
+
if (this.getInterfaceClaimed(device, iface) === false) {
|
|
167
|
+
this.logSkippedHandleOperation('releaseInterface', device, 'interface-not-claimed', iface);
|
|
153
168
|
}
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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((
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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
|
-
: (
|
|
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 = (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
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.
|
|
767
|
+
yield this.runLoggedHandleOperation({
|
|
768
|
+
operation: 'reset',
|
|
769
|
+
device,
|
|
770
|
+
path,
|
|
771
|
+
task: () => device.reset(),
|
|
772
|
+
});
|
|
552
773
|
}
|
|
553
|
-
catch (
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
656
|
-
? this.identityProbeDevices.get(path)
|
|
657
|
-
:
|
|
658
|
-
|
|
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 = (
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
667
|
-
|
|
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.
|
|
943
|
+
yield this.runLoggedHandleOperation({
|
|
944
|
+
operation: 'close',
|
|
945
|
+
device: current,
|
|
946
|
+
path,
|
|
947
|
+
task: () => current.close(),
|
|
948
|
+
});
|
|
672
949
|
}
|
|
673
950
|
catch (error) {
|
|
674
|
-
(
|
|
675
|
-
|
|
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.
|
|
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
|
-
|
|
815
|
-
|
|
1096
|
+
if (this.getInterfaceClaimed(currentDevice, ifaceNum) === false) {
|
|
1097
|
+
this.logSkippedHandleOperation('releaseInterface', currentDevice, 'interface-not-claimed', ifaceNum);
|
|
816
1098
|
}
|
|
817
|
-
|
|
818
|
-
|
|
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.
|
|
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);
|