@onekeyfe/hd-transport-web-device 1.2.8-alpha.100 → 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__/electron-ble-transport.test.ts +11 -7
- package/__tests__/webusb-identity-probe.test.ts +887 -0
- package/__tests__/webusb-protocol-cache.test.ts +197 -1
- package/__tests__/webusb-protocol-v2-timeout.test.ts +18 -5
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +37 -3
- package/dist/index.js +639 -58
- package/dist/webusb.d.ts +38 -3
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +4 -8
- package/src/webusb.ts +749 -52
package/dist/index.js
CHANGED
|
@@ -51,11 +51,18 @@ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
|
|
|
51
51
|
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
|
+
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;
|
|
54
57
|
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
55
58
|
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
56
59
|
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
57
60
|
let activeWebUsbTransport;
|
|
58
61
|
let usbDisconnectListenerAttached = false;
|
|
62
|
+
let nextWebUsbTransportInstanceId = 0;
|
|
63
|
+
const identityProbeHandles = new WeakSet();
|
|
64
|
+
const openingHandles = new WeakSet();
|
|
65
|
+
const normalHandleOwners = new WeakMap();
|
|
59
66
|
function registerActiveWebUsbTransport(instance, usb) {
|
|
60
67
|
activeWebUsbTransport = instance;
|
|
61
68
|
if (usbDisconnectListenerAttached)
|
|
@@ -72,17 +79,26 @@ function registerActiveWebUsbTransport(instance, usb) {
|
|
|
72
79
|
});
|
|
73
80
|
}
|
|
74
81
|
class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
75
|
-
constructor() {
|
|
82
|
+
constructor(identityProbe = false) {
|
|
76
83
|
super({
|
|
77
84
|
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
78
85
|
maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
79
86
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
80
87
|
});
|
|
88
|
+
this.identityProbe = identityProbe;
|
|
89
|
+
this.transportInstanceId = ++nextWebUsbTransportInstanceId;
|
|
90
|
+
this.handleOperationSequence = 0;
|
|
91
|
+
this.deviceLogSequence = 0;
|
|
92
|
+
this.deviceLogIds = new WeakMap();
|
|
81
93
|
this.deviceProtocol = new Map();
|
|
82
94
|
this.confirmedDeviceProtocols = new Map();
|
|
83
95
|
this.deviceProtocolHints = new Map();
|
|
84
96
|
this.staleProtocolPaths = new Set();
|
|
85
97
|
this.acquiredPaths = new Set();
|
|
98
|
+
this.identityProbeDevices = new Map();
|
|
99
|
+
this.normalDevices = new Map();
|
|
100
|
+
this.pendingHandleOperations = new Set();
|
|
101
|
+
this.identityProbeCleanupPaths = new Set();
|
|
86
102
|
this.probedDeviceObjects = new Map();
|
|
87
103
|
this.deviceEndpoints = new Map();
|
|
88
104
|
this.name = 'WebUsbTransport';
|
|
@@ -101,7 +117,195 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
101
117
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'WebUSB is not supported by current browsers');
|
|
102
118
|
}
|
|
103
119
|
this.usb = usb;
|
|
104
|
-
|
|
120
|
+
if (!this.identityProbe)
|
|
121
|
+
registerActiveWebUsbTransport(this, usb);
|
|
122
|
+
}
|
|
123
|
+
stop() {
|
|
124
|
+
const startedAt = Date.now();
|
|
125
|
+
this.stopped = true;
|
|
126
|
+
this.acquiredPaths.clear();
|
|
127
|
+
if (activeWebUsbTransport === this)
|
|
128
|
+
activeWebUsbTransport = undefined;
|
|
129
|
+
if (!this.stopPromise) {
|
|
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)));
|
|
138
|
+
this.stopPromise = undefined;
|
|
139
|
+
throw error;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return this.stopPromise;
|
|
143
|
+
}
|
|
144
|
+
stopResources() {
|
|
145
|
+
var _a, _b, _c;
|
|
146
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
147
|
+
let cleanupError;
|
|
148
|
+
try {
|
|
149
|
+
yield this.disposeProtocolV2UsbLinks('WebUSB transport stopped');
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
cleanupError = error;
|
|
153
|
+
}
|
|
154
|
+
yield Promise.allSettled([...this.pendingHandleOperations]);
|
|
155
|
+
const handles = this.identityProbe
|
|
156
|
+
? [...this.identityProbeDevices].map(([path, device]) => [device, path])
|
|
157
|
+
: [...this.normalDevices];
|
|
158
|
+
for (const [device, path] of handles) {
|
|
159
|
+
if (!this.identityProbe && normalHandleOwners.get(device) !== this) {
|
|
160
|
+
this.normalDevices.delete(device);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
try {
|
|
164
|
+
if (device.opened) {
|
|
165
|
+
const iface = (_b = (_a = this.deviceEndpoints.get(path)) === null || _a === void 0 ? void 0 : _a.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
166
|
+
if (this.getInterfaceClaimed(device, iface) === false) {
|
|
167
|
+
this.logSkippedHandleOperation('releaseInterface', device, 'interface-not-claimed', iface);
|
|
168
|
+
}
|
|
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
|
+
}
|
|
183
|
+
}
|
|
184
|
+
yield this.runLoggedHandleOperation({
|
|
185
|
+
operation: 'close',
|
|
186
|
+
device,
|
|
187
|
+
path,
|
|
188
|
+
allowStopped: true,
|
|
189
|
+
task: () => device.close(),
|
|
190
|
+
});
|
|
191
|
+
if (device.opened)
|
|
192
|
+
throw new Error('WebUSB handle remains open after stop');
|
|
193
|
+
}
|
|
194
|
+
if (this.identityProbe) {
|
|
195
|
+
identityProbeHandles.delete(device);
|
|
196
|
+
this.identityProbeDevices.delete(path);
|
|
197
|
+
this.identityProbeCleanupPaths.delete(path);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
normalHandleOwners.delete(device);
|
|
201
|
+
this.normalDevices.delete(device);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
cleanupError !== null && cleanupError !== void 0 ? cleanupError : (cleanupError = error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.deviceList = [];
|
|
210
|
+
this.deviceProtocol.clear();
|
|
211
|
+
this.confirmedDeviceProtocols.clear();
|
|
212
|
+
this.deviceProtocolHints.clear();
|
|
213
|
+
this.staleProtocolPaths.clear();
|
|
214
|
+
this.probedDeviceObjects.clear();
|
|
215
|
+
if (!this.normalDevices.size && !this.identityProbeDevices.size) {
|
|
216
|
+
this.deviceEndpoints.clear();
|
|
217
|
+
}
|
|
218
|
+
if (cleanupError) {
|
|
219
|
+
throw cleanupError instanceof Error
|
|
220
|
+
? cleanupError
|
|
221
|
+
: new Error(this.getErrorMessage(cleanupError));
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
assertRunning() {
|
|
226
|
+
if (this.stopped) {
|
|
227
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'WebUSB transport is stopped');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
runHandleOperation(operation) {
|
|
231
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
this.assertRunning();
|
|
233
|
+
const pending = Promise.resolve(operation());
|
|
234
|
+
this.pendingHandleOperations.add(pending);
|
|
235
|
+
try {
|
|
236
|
+
const result = yield pending;
|
|
237
|
+
this.assertRunning();
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
this.pendingHandleOperations.delete(pending);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
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
|
+
});
|
|
105
309
|
}
|
|
106
310
|
emitDeviceDisconnect(path, device) {
|
|
107
311
|
var _a, _b;
|
|
@@ -138,11 +342,19 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
138
342
|
return __awaiter(this, void 0, void 0, function* () {
|
|
139
343
|
if (!this.usb)
|
|
140
344
|
return null;
|
|
345
|
+
const startedAt = Date.now();
|
|
346
|
+
this.logLifecycle('prompt-device-access', { phase: 'start' });
|
|
141
347
|
try {
|
|
142
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
|
+
});
|
|
143
354
|
return device;
|
|
144
355
|
}
|
|
145
356
|
catch (e) {
|
|
357
|
+
this.logLifecycle('prompt-device-access', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(e)));
|
|
146
358
|
this.Log.debug('requestDevice error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
147
359
|
return null;
|
|
148
360
|
}
|
|
@@ -150,15 +362,66 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
150
362
|
}
|
|
151
363
|
enumerate() {
|
|
152
364
|
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
}
|
|
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;
|
|
389
|
+
}
|
|
155
390
|
});
|
|
156
391
|
}
|
|
157
392
|
getConnectedDevices() {
|
|
393
|
+
var _a;
|
|
158
394
|
return __awaiter(this, void 0, void 0, function* () {
|
|
395
|
+
this.assertRunning();
|
|
159
396
|
if (!this.usb)
|
|
160
397
|
return [];
|
|
161
|
-
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
|
+
}
|
|
413
|
+
this.assertRunning();
|
|
414
|
+
for (const owner of new Set(devices.map(device => normalHandleOwners.get(device)))) {
|
|
415
|
+
if (owner === null || owner === void 0 ? void 0 : owner.stopped) {
|
|
416
|
+
try {
|
|
417
|
+
yield owner.stop();
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] stopped owner cleanup failed:', error);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
this.assertRunning();
|
|
162
425
|
const onekeyDevices = devices.filter(dev => {
|
|
163
426
|
const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
|
|
164
427
|
return isOneKey && !hdShared.isKnownTrezorWebUsbDevice(dev);
|
|
@@ -183,14 +446,34 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
183
446
|
});
|
|
184
447
|
}
|
|
185
448
|
acquire(input) {
|
|
186
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
449
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
187
450
|
return __awaiter(this, void 0, void 0, function* () {
|
|
451
|
+
this.assertRunning();
|
|
188
452
|
if (!input.path)
|
|
189
453
|
return;
|
|
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
|
+
});
|
|
190
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
|
+
}
|
|
191
471
|
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
192
472
|
yield this.closeOpenDevice(input.path);
|
|
193
|
-
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
|
+
});
|
|
194
477
|
if (input.skipProtocolProbe) {
|
|
195
478
|
if (!input.expectedProtocol) {
|
|
196
479
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires an expected protocol');
|
|
@@ -200,7 +483,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
200
483
|
}
|
|
201
484
|
this.staleProtocolPaths.delete(input.path);
|
|
202
485
|
this.deviceProtocol.set(input.path, input.expectedProtocol);
|
|
203
|
-
const currentDevice = (
|
|
486
|
+
const currentDevice = (_d = this.deviceList.find(d => d.path === input.path)) === null || _d === void 0 ? void 0 : _d.device;
|
|
204
487
|
if (currentDevice) {
|
|
205
488
|
this.probedDeviceObjects.set(input.path, currentDevice);
|
|
206
489
|
}
|
|
@@ -215,7 +498,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
215
498
|
this.deviceProtocol.delete(input.path);
|
|
216
499
|
}
|
|
217
500
|
if (!input.skipProtocolProbe && this.deviceProtocol.has(input.path)) {
|
|
218
|
-
const currentDevice = (
|
|
501
|
+
const currentDevice = (_e = this.deviceList.find(d => d.path === input.path)) === null || _e === void 0 ? void 0 : _e.device;
|
|
219
502
|
if (!currentDevice || currentDevice !== this.probedDeviceObjects.get(input.path)) {
|
|
220
503
|
this.deviceProtocol.delete(input.path);
|
|
221
504
|
}
|
|
@@ -228,39 +511,58 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
228
511
|
this.deviceProtocol.delete(input.path);
|
|
229
512
|
}
|
|
230
513
|
if (!this.deviceProtocol.has(input.path)) {
|
|
231
|
-
const usbDevice = (
|
|
514
|
+
const usbDevice = (_f = this.deviceList.find(device => device.path === input.path)) === null || _f === void 0 ? void 0 : _f.device;
|
|
232
515
|
const protocolHint = input.expectedProtocol
|
|
233
516
|
? undefined
|
|
234
|
-
: (
|
|
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);
|
|
235
518
|
if (protocolHint) {
|
|
236
519
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
237
520
|
}
|
|
238
521
|
const detectedProtocol = yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
522
|
+
this.assertRunning();
|
|
239
523
|
this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
|
|
240
|
-
const probedDevice = (
|
|
524
|
+
const probedDevice = (_j = this.deviceList.find(d => d.path === input.path)) === null || _j === void 0 ? void 0 : _j.device;
|
|
241
525
|
if (probedDevice) {
|
|
242
526
|
this.probedDeviceObjects.set(input.path, probedDevice);
|
|
243
527
|
}
|
|
244
528
|
}
|
|
529
|
+
this.assertRunning();
|
|
245
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
|
+
});
|
|
246
536
|
return yield Promise.resolve(input.path);
|
|
247
537
|
}
|
|
248
538
|
catch (e) {
|
|
539
|
+
this.logLifecycle('acquire', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(e, input.path)));
|
|
249
540
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
250
541
|
this.acquiredPaths.delete(input.path);
|
|
251
|
-
|
|
542
|
+
try {
|
|
543
|
+
yield this.closeOpenDevice(input.path);
|
|
544
|
+
}
|
|
545
|
+
finally {
|
|
546
|
+
this.releaseIdentityProbeHandle(input.path);
|
|
547
|
+
this.releaseNormalHandles(input.path);
|
|
548
|
+
}
|
|
252
549
|
throw e;
|
|
253
550
|
}
|
|
254
551
|
});
|
|
255
552
|
}
|
|
256
553
|
createProtocolMismatchError(expected) {
|
|
257
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
554
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
555
|
+
expectedProtocol: expected,
|
|
556
|
+
});
|
|
258
557
|
}
|
|
259
558
|
createProtocolProbeTimeoutError(expected, attempts) {
|
|
260
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
559
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
560
|
+
expectedProtocol: expected,
|
|
561
|
+
attempts,
|
|
562
|
+
});
|
|
261
563
|
}
|
|
262
564
|
createProtocolDetectionError() {
|
|
263
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
565
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed);
|
|
264
566
|
}
|
|
265
567
|
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
266
568
|
var _a;
|
|
@@ -324,18 +626,48 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
324
626
|
return device.device;
|
|
325
627
|
});
|
|
326
628
|
}
|
|
327
|
-
connect(path, first) {
|
|
629
|
+
connect(path, first, options = {}) {
|
|
328
630
|
return __awaiter(this, void 0, void 0, function* () {
|
|
329
631
|
const maxRetries = 5;
|
|
330
632
|
for (let i = 0; i < maxRetries; i++) {
|
|
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
|
+
});
|
|
331
646
|
try {
|
|
332
|
-
|
|
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;
|
|
333
657
|
}
|
|
334
658
|
catch (e) {
|
|
335
|
-
|
|
659
|
+
this.assertRunning();
|
|
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) {
|
|
336
668
|
throw e;
|
|
337
669
|
}
|
|
338
|
-
if (
|
|
670
|
+
if (!willRetry) {
|
|
339
671
|
throw e;
|
|
340
672
|
}
|
|
341
673
|
yield hdShared.wait(i * 200);
|
|
@@ -343,6 +675,11 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
343
675
|
}
|
|
344
676
|
});
|
|
345
677
|
}
|
|
678
|
+
shouldUseLegacyV1Connection(path, options) {
|
|
679
|
+
if (options.expectedProtocol)
|
|
680
|
+
return options.expectedProtocol === 'V1';
|
|
681
|
+
return !options.forceProtocolDetection && this.deviceProtocol.get(path) === 'V1';
|
|
682
|
+
}
|
|
346
683
|
discoverEndpoints(device) {
|
|
347
684
|
var _a, _b;
|
|
348
685
|
for (const config of device.configurations) {
|
|
@@ -371,87 +708,276 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
371
708
|
endpointOut: this.endpointId,
|
|
372
709
|
};
|
|
373
710
|
}
|
|
374
|
-
connectToDevice(path, first) {
|
|
375
|
-
var _a;
|
|
711
|
+
connectToDevice(path, first, options = {}) {
|
|
376
712
|
return __awaiter(this, void 0, void 0, function* () {
|
|
713
|
+
this.assertRunning();
|
|
377
714
|
let device = yield this.findDevice(path);
|
|
715
|
+
if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
|
|
716
|
+
yield this.closeOpenDevice(path);
|
|
717
|
+
this.releaseIdentityProbeHandle(path);
|
|
718
|
+
this.claimIdentityProbeHandle(path, device);
|
|
719
|
+
}
|
|
720
|
+
if (!this.identityProbe)
|
|
721
|
+
this.claimNormalHandle(path, device);
|
|
722
|
+
this.assertHandleAvailable(device);
|
|
378
723
|
if (!device.opened) {
|
|
724
|
+
openingHandles.add(device);
|
|
379
725
|
try {
|
|
380
|
-
yield
|
|
726
|
+
yield this.runLoggedHandleOperation({
|
|
727
|
+
operation: 'open',
|
|
728
|
+
device,
|
|
729
|
+
path,
|
|
730
|
+
task: () => device.open(),
|
|
731
|
+
});
|
|
381
732
|
}
|
|
382
733
|
catch (error) {
|
|
383
|
-
return this.throwWebUsbIoError(path, 'open', error);
|
|
734
|
+
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
735
|
+
}
|
|
736
|
+
finally {
|
|
737
|
+
openingHandles.delete(device);
|
|
738
|
+
}
|
|
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
|
+
}
|
|
384
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;
|
|
385
768
|
}
|
|
386
769
|
if (first || this.deviceProtocol.get(path) !== 'V1') {
|
|
387
770
|
try {
|
|
388
|
-
yield
|
|
771
|
+
yield this.runLoggedHandleOperation({
|
|
772
|
+
operation: 'reset',
|
|
773
|
+
device,
|
|
774
|
+
path,
|
|
775
|
+
task: () => device.reset(),
|
|
776
|
+
});
|
|
389
777
|
}
|
|
390
|
-
catch (
|
|
391
|
-
|
|
778
|
+
catch (_b) {
|
|
779
|
+
this.assertRunning();
|
|
392
780
|
}
|
|
393
781
|
}
|
|
394
782
|
yield this.getConnectedDevices();
|
|
395
783
|
device = yield this.findDevice(path);
|
|
784
|
+
if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
|
|
785
|
+
yield this.closeOpenDevice(path);
|
|
786
|
+
this.releaseIdentityProbeHandle(path);
|
|
787
|
+
this.claimIdentityProbeHandle(path, device);
|
|
788
|
+
}
|
|
789
|
+
if (!this.identityProbe)
|
|
790
|
+
this.claimNormalHandle(path, device);
|
|
791
|
+
this.assertHandleAvailable(device);
|
|
396
792
|
if (!device.opened) {
|
|
793
|
+
openingHandles.add(device);
|
|
397
794
|
try {
|
|
398
|
-
yield
|
|
795
|
+
yield this.runLoggedHandleOperation({
|
|
796
|
+
operation: 'open',
|
|
797
|
+
device,
|
|
798
|
+
path,
|
|
799
|
+
task: () => device.open(),
|
|
800
|
+
});
|
|
399
801
|
}
|
|
400
802
|
catch (error) {
|
|
401
|
-
return this.throwWebUsbIoError(path, 'open', error);
|
|
803
|
+
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
804
|
+
}
|
|
805
|
+
finally {
|
|
806
|
+
openingHandles.delete(device);
|
|
402
807
|
}
|
|
403
808
|
}
|
|
404
809
|
if (first ||
|
|
405
810
|
!device.configuration ||
|
|
406
811
|
device.configuration.configurationValue !== this.configurationId) {
|
|
407
|
-
yield
|
|
812
|
+
yield this.runLoggedHandleOperation({
|
|
813
|
+
operation: 'selectConfiguration',
|
|
814
|
+
device,
|
|
815
|
+
path,
|
|
816
|
+
task: () => device.selectConfiguration(this.configurationId),
|
|
817
|
+
});
|
|
408
818
|
}
|
|
409
819
|
const endpoints = this.discoverEndpoints(device);
|
|
410
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* () {
|
|
411
828
|
try {
|
|
412
|
-
yield
|
|
829
|
+
yield this.runLoggedHandleOperation({
|
|
830
|
+
operation: 'claimInterface',
|
|
831
|
+
device,
|
|
832
|
+
path,
|
|
833
|
+
interfaceNumber: endpoints.interfaceNumber,
|
|
834
|
+
task: () => device.claimInterface(endpoints.interfaceNumber),
|
|
835
|
+
});
|
|
413
836
|
}
|
|
414
837
|
catch (error) {
|
|
415
838
|
return this.throwWebUsbIoError(path, 'claimInterface', error);
|
|
416
839
|
}
|
|
417
|
-
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
418
|
-
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
419
840
|
});
|
|
420
841
|
}
|
|
842
|
+
assertHandleAvailable(device) {
|
|
843
|
+
this.assertRunning();
|
|
844
|
+
const normalOwner = normalHandleOwners.get(device);
|
|
845
|
+
if ((normalOwner && normalOwner !== this) ||
|
|
846
|
+
(identityProbeHandles.has(device) &&
|
|
847
|
+
![...this.identityProbeDevices.values()].includes(device))) {
|
|
848
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, USB_DEVICE_IN_USE);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
claimNormalHandle(path, device) {
|
|
852
|
+
this.assertHandleAvailable(device);
|
|
853
|
+
normalHandleOwners.set(device, this);
|
|
854
|
+
this.normalDevices.set(device, path);
|
|
855
|
+
}
|
|
856
|
+
releaseNormalHandles(path) {
|
|
857
|
+
if (this.stopped)
|
|
858
|
+
return;
|
|
859
|
+
for (const [device, ownedPath] of this.normalDevices) {
|
|
860
|
+
if (ownedPath === path && !device.opened) {
|
|
861
|
+
normalHandleOwners.delete(device);
|
|
862
|
+
this.normalDevices.delete(device);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
isHandleOwnershipError(error) {
|
|
867
|
+
return (error instanceof hdShared.HardwareError &&
|
|
868
|
+
error.errorCode === hdShared.HardwareErrorCode.RuntimeError &&
|
|
869
|
+
this.getErrorMessage(error).includes(USB_DEVICE_IN_USE));
|
|
870
|
+
}
|
|
871
|
+
claimIdentityProbeHandle(path, device) {
|
|
872
|
+
this.assertRunning();
|
|
873
|
+
if (device.opened ||
|
|
874
|
+
openingHandles.has(device) ||
|
|
875
|
+
identityProbeHandles.has(device) ||
|
|
876
|
+
normalHandleOwners.has(device)) {
|
|
877
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, USB_DEVICE_IN_USE);
|
|
878
|
+
}
|
|
879
|
+
identityProbeHandles.add(device);
|
|
880
|
+
this.identityProbeDevices.set(path, device);
|
|
881
|
+
}
|
|
882
|
+
releaseIdentityProbeHandle(path) {
|
|
883
|
+
if (this.stopped)
|
|
884
|
+
return;
|
|
885
|
+
const device = this.identityProbeDevices.get(path);
|
|
886
|
+
if (device === null || device === void 0 ? void 0 : device.opened) {
|
|
887
|
+
this.identityProbeCleanupPaths.add(path);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (device)
|
|
891
|
+
identityProbeHandles.delete(device);
|
|
892
|
+
this.identityProbeDevices.delete(path);
|
|
893
|
+
this.identityProbeCleanupPaths.delete(path);
|
|
894
|
+
}
|
|
421
895
|
closeOpenDevice(path) {
|
|
422
|
-
var _a, _b, _c, _d;
|
|
423
896
|
return __awaiter(this, void 0, void 0, function* () {
|
|
424
|
-
|
|
425
|
-
|
|
897
|
+
if (this.stopped)
|
|
898
|
+
return;
|
|
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)
|
|
426
924
|
return;
|
|
925
|
+
this.assertHandleAvailable(current);
|
|
427
926
|
const endpoints = this.deviceEndpoints.get(path);
|
|
428
|
-
const ifaceNum = (
|
|
429
|
-
|
|
430
|
-
|
|
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);
|
|
431
930
|
}
|
|
432
|
-
|
|
433
|
-
|
|
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
|
+
}
|
|
434
944
|
}
|
|
945
|
+
this.assertHandleAvailable(current);
|
|
435
946
|
try {
|
|
436
|
-
yield
|
|
947
|
+
yield this.runLoggedHandleOperation({
|
|
948
|
+
operation: 'close',
|
|
949
|
+
device: current,
|
|
950
|
+
path,
|
|
951
|
+
task: () => current.close(),
|
|
952
|
+
});
|
|
437
953
|
}
|
|
438
954
|
catch (error) {
|
|
439
|
-
(
|
|
955
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
956
|
+
throw error;
|
|
440
957
|
}
|
|
441
958
|
});
|
|
442
959
|
}
|
|
443
|
-
clearEndpointHalt(device, direction, endpointNumber) {
|
|
960
|
+
clearEndpointHalt(path, device, direction, endpointNumber) {
|
|
444
961
|
var _a;
|
|
445
962
|
return __awaiter(this, void 0, void 0, function* () {
|
|
963
|
+
this.assertHandleAvailable(device);
|
|
446
964
|
try {
|
|
447
|
-
yield
|
|
965
|
+
yield this.runLoggedHandleOperation({
|
|
966
|
+
operation: direction === 'in' ? 'clearHaltIn' : 'clearHaltOut',
|
|
967
|
+
device,
|
|
968
|
+
path,
|
|
969
|
+
endpointNumber,
|
|
970
|
+
task: () => device.clearHalt(direction, endpointNumber),
|
|
971
|
+
});
|
|
448
972
|
}
|
|
449
973
|
catch (error) {
|
|
974
|
+
this.assertRunning();
|
|
450
975
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] clearHalt ${direction} endpoint ${endpointNumber} failed, continuing:`, error);
|
|
451
976
|
}
|
|
452
977
|
});
|
|
453
978
|
}
|
|
454
979
|
assertAcquired(path) {
|
|
980
|
+
this.assertRunning();
|
|
455
981
|
if (!this.acquiredPaths.has(path)) {
|
|
456
982
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
|
|
457
983
|
}
|
|
@@ -508,7 +1034,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
508
1034
|
throwWebUsbIoError(path, operation, error) {
|
|
509
1035
|
var _a;
|
|
510
1036
|
return __awaiter(this, void 0, void 0, function* () {
|
|
511
|
-
|
|
1037
|
+
this.assertRunning();
|
|
1038
|
+
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
512
1039
|
throw error;
|
|
513
1040
|
if (error instanceof transport.ProtocolV2LinkError && error.code !== 'io')
|
|
514
1041
|
throw error;
|
|
@@ -542,7 +1069,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
542
1069
|
return /LIBUSB_ERROR_ACCESS|\b(?:access|permission) denied\b/i.test(message);
|
|
543
1070
|
}
|
|
544
1071
|
isRetryablePacketIoError(error) {
|
|
545
|
-
if (this.
|
|
1072
|
+
if (this.stopped)
|
|
1073
|
+
return false;
|
|
1074
|
+
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
546
1075
|
return false;
|
|
547
1076
|
const message = this.getErrorMessage(error).toLowerCase();
|
|
548
1077
|
if (this.isWebUsbPermissionDeniedMessage(message))
|
|
@@ -561,21 +1090,43 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
561
1090
|
this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
562
1091
|
this.staleProtocolPaths.add(path);
|
|
563
1092
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
1093
|
+
this.assertRunning();
|
|
564
1094
|
try {
|
|
565
1095
|
const currentDevice = yield this.findDevice(path);
|
|
1096
|
+
this.assertHandleAvailable(currentDevice);
|
|
566
1097
|
if (currentDevice.opened) {
|
|
567
1098
|
const endpoints = this.deviceEndpoints.get(path);
|
|
568
1099
|
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
569
|
-
|
|
570
|
-
|
|
1100
|
+
if (this.getInterfaceClaimed(currentDevice, ifaceNum) === false) {
|
|
1101
|
+
this.logSkippedHandleOperation('releaseInterface', currentDevice, 'interface-not-claimed', ifaceNum);
|
|
571
1102
|
}
|
|
572
|
-
|
|
573
|
-
|
|
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
|
+
}
|
|
574
1116
|
}
|
|
575
|
-
|
|
1117
|
+
this.assertHandleAvailable(currentDevice);
|
|
1118
|
+
yield this.runLoggedHandleOperation({
|
|
1119
|
+
operation: 'close',
|
|
1120
|
+
device: currentDevice,
|
|
1121
|
+
path,
|
|
1122
|
+
task: () => currentDevice.close(),
|
|
1123
|
+
});
|
|
576
1124
|
}
|
|
577
1125
|
}
|
|
578
1126
|
catch (closeError) {
|
|
1127
|
+
this.assertRunning();
|
|
1128
|
+
if (this.isHandleOwnershipError(closeError))
|
|
1129
|
+
throw closeError;
|
|
579
1130
|
this.Log.debug('[WebUsbTransport] close device before retry error:', closeError);
|
|
580
1131
|
}
|
|
581
1132
|
yield this.getConnectedDevices();
|
|
@@ -617,6 +1168,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
617
1168
|
yield this.reconnectForPacketIoRetry(path, 'out', attempt, error);
|
|
618
1169
|
}
|
|
619
1170
|
catch (reconnectError) {
|
|
1171
|
+
this.assertRunning();
|
|
1172
|
+
if (this.isHandleOwnershipError(reconnectError))
|
|
1173
|
+
throw reconnectError;
|
|
620
1174
|
lastError = reconnectError;
|
|
621
1175
|
this.Log.debug(`[WebUsbTransport] transferout reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
622
1176
|
}
|
|
@@ -635,7 +1189,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
635
1189
|
const endpoints = this.deviceEndpoints.get(path);
|
|
636
1190
|
const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
637
1191
|
const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
1192
|
+
this.assertHandleAvailable(device);
|
|
638
1193
|
const result = yield device.transferOut(endpointOut, transferBuffer);
|
|
1194
|
+
this.assertHandleAvailable(device);
|
|
639
1195
|
if (result.status !== 'ok') {
|
|
640
1196
|
return this.throwWebUsbIoError(path, 'transferOut', new Error(`transferOut status: ${String(result.status)}`));
|
|
641
1197
|
}
|
|
@@ -656,7 +1212,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
656
1212
|
}
|
|
657
1213
|
const endpoints = this.deviceEndpoints.get(path);
|
|
658
1214
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
1215
|
+
this.assertHandleAvailable(device);
|
|
659
1216
|
const result = yield device.transferIn(endpointIn, length);
|
|
1217
|
+
this.assertHandleAvailable(device);
|
|
660
1218
|
return this.getTransferInData(result);
|
|
661
1219
|
}
|
|
662
1220
|
catch (error) {
|
|
@@ -672,6 +1230,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
672
1230
|
yield this.reconnectForPacketIoRetry(path, 'in', attempt, error);
|
|
673
1231
|
}
|
|
674
1232
|
catch (reconnectError) {
|
|
1233
|
+
this.assertRunning();
|
|
1234
|
+
if (this.isHandleOwnershipError(reconnectError))
|
|
1235
|
+
throw reconnectError;
|
|
675
1236
|
lastError = reconnectError;
|
|
676
1237
|
this.Log.debug(`[WebUsbTransport] transferin reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
677
1238
|
}
|
|
@@ -689,7 +1250,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
689
1250
|
}
|
|
690
1251
|
const endpoints = this.deviceEndpoints.get(path);
|
|
691
1252
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
1253
|
+
this.assertHandleAvailable(device);
|
|
692
1254
|
const result = yield device.transferIn(endpointIn, length);
|
|
1255
|
+
this.assertHandleAvailable(device);
|
|
693
1256
|
return this.getTransferInData(result);
|
|
694
1257
|
});
|
|
695
1258
|
}
|
|
@@ -749,10 +1312,10 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
749
1312
|
return false;
|
|
750
1313
|
}
|
|
751
1314
|
try {
|
|
752
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
1315
|
+
const response = yield this.callProtocolV1(path, this.identityProbe ? 'GetFeatures' : 'Initialize', {}, {
|
|
753
1316
|
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
754
1317
|
});
|
|
755
|
-
return
|
|
1318
|
+
return !this.identityProbe || response.type === 'Features';
|
|
756
1319
|
}
|
|
757
1320
|
catch (error) {
|
|
758
1321
|
if (this.isWebUsbIoHardwareError(error))
|
|
@@ -868,9 +1431,25 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
868
1431
|
}
|
|
869
1432
|
release(path) {
|
|
870
1433
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1434
|
+
const startedAt = Date.now();
|
|
1435
|
+
this.logLifecycle('release', { phase: 'start' });
|
|
871
1436
|
this.acquiredPaths.delete(path);
|
|
872
|
-
|
|
873
|
-
|
|
1437
|
+
try {
|
|
1438
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
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;
|
|
1448
|
+
}
|
|
1449
|
+
finally {
|
|
1450
|
+
this.releaseIdentityProbeHandle(path);
|
|
1451
|
+
this.releaseNormalHandles(path);
|
|
1452
|
+
}
|
|
874
1453
|
this.deviceEndpoints.delete(path);
|
|
875
1454
|
});
|
|
876
1455
|
}
|
|
@@ -1277,10 +1856,12 @@ class ElectronBleTransport {
|
|
|
1277
1856
|
});
|
|
1278
1857
|
}
|
|
1279
1858
|
createProtocolMismatchError(expected) {
|
|
1280
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
1859
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
1860
|
+
expectedProtocol: expected,
|
|
1861
|
+
});
|
|
1281
1862
|
}
|
|
1282
1863
|
createProtocolDetectionError() {
|
|
1283
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
1864
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed);
|
|
1284
1865
|
}
|
|
1285
1866
|
clearProbeProtocol(uuid, protocol) {
|
|
1286
1867
|
if (this.deviceProtocol.get(uuid) === protocol) {
|