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