@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/__tests__/webusb-protocol-cache.test.ts +197 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.js +377 -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 +496 -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,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.
|
|
771
|
+
yield this.runLoggedHandleOperation({
|
|
772
|
+
operation: 'reset',
|
|
773
|
+
device,
|
|
774
|
+
path,
|
|
775
|
+
task: () => device.reset(),
|
|
776
|
+
});
|
|
552
777
|
}
|
|
553
|
-
catch (
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
656
|
-
? this.identityProbeDevices.get(path)
|
|
657
|
-
:
|
|
658
|
-
|
|
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 = (
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
667
|
-
|
|
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.
|
|
947
|
+
yield this.runLoggedHandleOperation({
|
|
948
|
+
operation: 'close',
|
|
949
|
+
device: current,
|
|
950
|
+
path,
|
|
951
|
+
task: () => current.close(),
|
|
952
|
+
});
|
|
672
953
|
}
|
|
673
954
|
catch (error) {
|
|
674
|
-
(
|
|
675
|
-
|
|
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.
|
|
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
|
-
|
|
815
|
-
|
|
1100
|
+
if (this.getInterfaceClaimed(currentDevice, ifaceNum) === false) {
|
|
1101
|
+
this.logSkippedHandleOperation('releaseInterface', currentDevice, 'interface-not-claimed', ifaceNum);
|
|
816
1102
|
}
|
|
817
|
-
|
|
818
|
-
|
|
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.
|
|
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);
|