@onekeyfe/hd-transport-web-device 1.2.8-alpha.12 → 1.2.8-alpha.121
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 +38 -7
- package/__tests__/webusb-identity-probe.test.ts +935 -0
- package/__tests__/webusb-protocol-cache.test.ts +301 -1
- package/__tests__/webusb-protocol-v2-timeout.test.ts +18 -5
- package/dist/electron-ble-transport.d.ts +1 -0
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +38 -3
- package/dist/index.js +643 -59
- 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 +6 -9
- package/src/webusb.ts +758 -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,61 @@ 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
|
+
catch (cleanupError) {
|
|
546
|
+
this.logLifecycle('acquire-cleanup', Object.assign({ phase: 'error' }, this.getDiagnosticError(cleanupError, input.path)));
|
|
547
|
+
}
|
|
548
|
+
finally {
|
|
549
|
+
this.releaseIdentityProbeHandle(input.path);
|
|
550
|
+
this.releaseNormalHandles(input.path);
|
|
551
|
+
}
|
|
252
552
|
throw e;
|
|
253
553
|
}
|
|
254
554
|
});
|
|
255
555
|
}
|
|
256
556
|
createProtocolMismatchError(expected) {
|
|
257
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
557
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
558
|
+
expectedProtocol: expected,
|
|
559
|
+
});
|
|
258
560
|
}
|
|
259
561
|
createProtocolProbeTimeoutError(expected, attempts) {
|
|
260
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
562
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
563
|
+
expectedProtocol: expected,
|
|
564
|
+
attempts,
|
|
565
|
+
});
|
|
261
566
|
}
|
|
262
567
|
createProtocolDetectionError() {
|
|
263
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
568
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed);
|
|
264
569
|
}
|
|
265
570
|
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
266
571
|
var _a;
|
|
@@ -324,18 +629,48 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
324
629
|
return device.device;
|
|
325
630
|
});
|
|
326
631
|
}
|
|
327
|
-
connect(path, first) {
|
|
632
|
+
connect(path, first, options = {}) {
|
|
328
633
|
return __awaiter(this, void 0, void 0, function* () {
|
|
329
634
|
const maxRetries = 5;
|
|
330
635
|
for (let i = 0; i < maxRetries; i++) {
|
|
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
|
+
});
|
|
331
649
|
try {
|
|
332
|
-
|
|
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;
|
|
333
660
|
}
|
|
334
661
|
catch (e) {
|
|
335
|
-
|
|
662
|
+
this.assertRunning();
|
|
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) {
|
|
336
671
|
throw e;
|
|
337
672
|
}
|
|
338
|
-
if (
|
|
673
|
+
if (!willRetry) {
|
|
339
674
|
throw e;
|
|
340
675
|
}
|
|
341
676
|
yield hdShared.wait(i * 200);
|
|
@@ -343,6 +678,11 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
343
678
|
}
|
|
344
679
|
});
|
|
345
680
|
}
|
|
681
|
+
shouldUseLegacyV1Connection(path, options) {
|
|
682
|
+
if (options.expectedProtocol)
|
|
683
|
+
return options.expectedProtocol === 'V1';
|
|
684
|
+
return !options.forceProtocolDetection && this.deviceProtocol.get(path) === 'V1';
|
|
685
|
+
}
|
|
346
686
|
discoverEndpoints(device) {
|
|
347
687
|
var _a, _b;
|
|
348
688
|
for (const config of device.configurations) {
|
|
@@ -371,87 +711,276 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
371
711
|
endpointOut: this.endpointId,
|
|
372
712
|
};
|
|
373
713
|
}
|
|
374
|
-
connectToDevice(path, first) {
|
|
375
|
-
var _a;
|
|
714
|
+
connectToDevice(path, first, options = {}) {
|
|
376
715
|
return __awaiter(this, void 0, void 0, function* () {
|
|
716
|
+
this.assertRunning();
|
|
377
717
|
let device = yield this.findDevice(path);
|
|
718
|
+
if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
|
|
719
|
+
yield this.closeOpenDevice(path);
|
|
720
|
+
this.releaseIdentityProbeHandle(path);
|
|
721
|
+
this.claimIdentityProbeHandle(path, device);
|
|
722
|
+
}
|
|
723
|
+
if (!this.identityProbe)
|
|
724
|
+
this.claimNormalHandle(path, device);
|
|
725
|
+
this.assertHandleAvailable(device);
|
|
378
726
|
if (!device.opened) {
|
|
727
|
+
openingHandles.add(device);
|
|
379
728
|
try {
|
|
380
|
-
yield
|
|
729
|
+
yield this.runLoggedHandleOperation({
|
|
730
|
+
operation: 'open',
|
|
731
|
+
device,
|
|
732
|
+
path,
|
|
733
|
+
task: () => device.open(),
|
|
734
|
+
});
|
|
381
735
|
}
|
|
382
736
|
catch (error) {
|
|
383
|
-
return this.throwWebUsbIoError(path, 'open', error);
|
|
737
|
+
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
738
|
+
}
|
|
739
|
+
finally {
|
|
740
|
+
openingHandles.delete(device);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (this.shouldUseLegacyV1Connection(path, options)) {
|
|
744
|
+
if (first ||
|
|
745
|
+
!device.configuration ||
|
|
746
|
+
device.configuration.configurationValue !== this.configurationId) {
|
|
747
|
+
yield this.runLoggedHandleOperation({
|
|
748
|
+
operation: 'selectConfiguration',
|
|
749
|
+
device,
|
|
750
|
+
path,
|
|
751
|
+
task: () => device.selectConfiguration(this.configurationId),
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
if (first) {
|
|
755
|
+
try {
|
|
756
|
+
yield this.runLoggedHandleOperation({
|
|
757
|
+
operation: 'reset',
|
|
758
|
+
device,
|
|
759
|
+
path,
|
|
760
|
+
task: () => device.reset(),
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
catch (_a) {
|
|
764
|
+
this.assertRunning();
|
|
765
|
+
}
|
|
384
766
|
}
|
|
767
|
+
const legacyEndpoints = this.discoverEndpoints(device);
|
|
768
|
+
this.deviceEndpoints.set(path, legacyEndpoints);
|
|
769
|
+
yield this.claimDeviceInterface(path, device, legacyEndpoints);
|
|
770
|
+
return;
|
|
385
771
|
}
|
|
386
772
|
if (first || this.deviceProtocol.get(path) !== 'V1') {
|
|
387
773
|
try {
|
|
388
|
-
yield
|
|
774
|
+
yield this.runLoggedHandleOperation({
|
|
775
|
+
operation: 'reset',
|
|
776
|
+
device,
|
|
777
|
+
path,
|
|
778
|
+
task: () => device.reset(),
|
|
779
|
+
});
|
|
389
780
|
}
|
|
390
|
-
catch (
|
|
391
|
-
|
|
781
|
+
catch (_b) {
|
|
782
|
+
this.assertRunning();
|
|
392
783
|
}
|
|
393
784
|
}
|
|
394
785
|
yield this.getConnectedDevices();
|
|
395
786
|
device = yield this.findDevice(path);
|
|
787
|
+
if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
|
|
788
|
+
yield this.closeOpenDevice(path);
|
|
789
|
+
this.releaseIdentityProbeHandle(path);
|
|
790
|
+
this.claimIdentityProbeHandle(path, device);
|
|
791
|
+
}
|
|
792
|
+
if (!this.identityProbe)
|
|
793
|
+
this.claimNormalHandle(path, device);
|
|
794
|
+
this.assertHandleAvailable(device);
|
|
396
795
|
if (!device.opened) {
|
|
796
|
+
openingHandles.add(device);
|
|
397
797
|
try {
|
|
398
|
-
yield
|
|
798
|
+
yield this.runLoggedHandleOperation({
|
|
799
|
+
operation: 'open',
|
|
800
|
+
device,
|
|
801
|
+
path,
|
|
802
|
+
task: () => device.open(),
|
|
803
|
+
});
|
|
399
804
|
}
|
|
400
805
|
catch (error) {
|
|
401
|
-
return this.throwWebUsbIoError(path, 'open', error);
|
|
806
|
+
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
807
|
+
}
|
|
808
|
+
finally {
|
|
809
|
+
openingHandles.delete(device);
|
|
402
810
|
}
|
|
403
811
|
}
|
|
404
812
|
if (first ||
|
|
405
813
|
!device.configuration ||
|
|
406
814
|
device.configuration.configurationValue !== this.configurationId) {
|
|
407
|
-
yield
|
|
815
|
+
yield this.runLoggedHandleOperation({
|
|
816
|
+
operation: 'selectConfiguration',
|
|
817
|
+
device,
|
|
818
|
+
path,
|
|
819
|
+
task: () => device.selectConfiguration(this.configurationId),
|
|
820
|
+
});
|
|
408
821
|
}
|
|
409
822
|
const endpoints = this.discoverEndpoints(device);
|
|
410
823
|
this.deviceEndpoints.set(path, endpoints);
|
|
824
|
+
yield this.claimDeviceInterface(path, device, endpoints);
|
|
825
|
+
yield this.clearEndpointHalt(path, device, 'in', endpoints.endpointIn);
|
|
826
|
+
yield this.clearEndpointHalt(path, device, 'out', endpoints.endpointOut);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
claimDeviceInterface(path, device, endpoints) {
|
|
830
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
411
831
|
try {
|
|
412
|
-
yield
|
|
832
|
+
yield this.runLoggedHandleOperation({
|
|
833
|
+
operation: 'claimInterface',
|
|
834
|
+
device,
|
|
835
|
+
path,
|
|
836
|
+
interfaceNumber: endpoints.interfaceNumber,
|
|
837
|
+
task: () => device.claimInterface(endpoints.interfaceNumber),
|
|
838
|
+
});
|
|
413
839
|
}
|
|
414
840
|
catch (error) {
|
|
415
841
|
return this.throwWebUsbIoError(path, 'claimInterface', error);
|
|
416
842
|
}
|
|
417
|
-
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
418
|
-
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
419
843
|
});
|
|
420
844
|
}
|
|
845
|
+
assertHandleAvailable(device) {
|
|
846
|
+
this.assertRunning();
|
|
847
|
+
const normalOwner = normalHandleOwners.get(device);
|
|
848
|
+
if ((normalOwner && normalOwner !== this) ||
|
|
849
|
+
(identityProbeHandles.has(device) &&
|
|
850
|
+
![...this.identityProbeDevices.values()].includes(device))) {
|
|
851
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, USB_DEVICE_IN_USE);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
claimNormalHandle(path, device) {
|
|
855
|
+
this.assertHandleAvailable(device);
|
|
856
|
+
normalHandleOwners.set(device, this);
|
|
857
|
+
this.normalDevices.set(device, path);
|
|
858
|
+
}
|
|
859
|
+
releaseNormalHandles(path) {
|
|
860
|
+
if (this.stopped)
|
|
861
|
+
return;
|
|
862
|
+
for (const [device, ownedPath] of this.normalDevices) {
|
|
863
|
+
if (ownedPath === path && !device.opened) {
|
|
864
|
+
normalHandleOwners.delete(device);
|
|
865
|
+
this.normalDevices.delete(device);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
isHandleOwnershipError(error) {
|
|
870
|
+
return (error instanceof hdShared.HardwareError &&
|
|
871
|
+
error.errorCode === hdShared.HardwareErrorCode.RuntimeError &&
|
|
872
|
+
this.getErrorMessage(error).includes(USB_DEVICE_IN_USE));
|
|
873
|
+
}
|
|
874
|
+
claimIdentityProbeHandle(path, device) {
|
|
875
|
+
this.assertRunning();
|
|
876
|
+
if (device.opened ||
|
|
877
|
+
openingHandles.has(device) ||
|
|
878
|
+
identityProbeHandles.has(device) ||
|
|
879
|
+
normalHandleOwners.has(device)) {
|
|
880
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, USB_DEVICE_IN_USE);
|
|
881
|
+
}
|
|
882
|
+
identityProbeHandles.add(device);
|
|
883
|
+
this.identityProbeDevices.set(path, device);
|
|
884
|
+
}
|
|
885
|
+
releaseIdentityProbeHandle(path) {
|
|
886
|
+
if (this.stopped)
|
|
887
|
+
return;
|
|
888
|
+
const device = this.identityProbeDevices.get(path);
|
|
889
|
+
if (device === null || device === void 0 ? void 0 : device.opened) {
|
|
890
|
+
this.identityProbeCleanupPaths.add(path);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
if (device)
|
|
894
|
+
identityProbeHandles.delete(device);
|
|
895
|
+
this.identityProbeDevices.delete(path);
|
|
896
|
+
this.identityProbeCleanupPaths.delete(path);
|
|
897
|
+
}
|
|
421
898
|
closeOpenDevice(path) {
|
|
422
|
-
var _a, _b, _c, _d;
|
|
423
899
|
return __awaiter(this, void 0, void 0, function* () {
|
|
424
|
-
|
|
425
|
-
|
|
900
|
+
if (this.stopped)
|
|
901
|
+
return;
|
|
902
|
+
const devices = this.identityProbe
|
|
903
|
+
? [this.identityProbeDevices.get(path)].filter((device) => device !== undefined)
|
|
904
|
+
: [...this.normalDevices]
|
|
905
|
+
.filter(([, ownedPath]) => ownedPath === path)
|
|
906
|
+
.map(([device]) => device);
|
|
907
|
+
let firstCloseError;
|
|
908
|
+
for (const device of devices) {
|
|
909
|
+
try {
|
|
910
|
+
yield this.closeDeviceHandle(path, device);
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
firstCloseError !== null && firstCloseError !== void 0 ? firstCloseError : (firstCloseError = error);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (firstCloseError) {
|
|
917
|
+
throw firstCloseError instanceof Error
|
|
918
|
+
? firstCloseError
|
|
919
|
+
: new Error(this.getErrorMessage(firstCloseError));
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
closeDeviceHandle(path, current) {
|
|
924
|
+
var _a, _b, _c;
|
|
925
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
926
|
+
if (!current.opened)
|
|
426
927
|
return;
|
|
928
|
+
this.assertHandleAvailable(current);
|
|
427
929
|
const endpoints = this.deviceEndpoints.get(path);
|
|
428
|
-
const ifaceNum = (
|
|
429
|
-
|
|
430
|
-
|
|
930
|
+
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
931
|
+
if (this.getInterfaceClaimed(current, ifaceNum) === false) {
|
|
932
|
+
this.logSkippedHandleOperation('releaseInterface', current, 'interface-not-claimed', ifaceNum);
|
|
431
933
|
}
|
|
432
|
-
|
|
433
|
-
|
|
934
|
+
else {
|
|
935
|
+
try {
|
|
936
|
+
yield this.runLoggedHandleOperation({
|
|
937
|
+
operation: 'releaseInterface',
|
|
938
|
+
device: current,
|
|
939
|
+
path,
|
|
940
|
+
interfaceNumber: ifaceNum,
|
|
941
|
+
task: () => current.releaseInterface(ifaceNum),
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
catch (error) {
|
|
945
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
946
|
+
}
|
|
434
947
|
}
|
|
948
|
+
this.assertHandleAvailable(current);
|
|
435
949
|
try {
|
|
436
|
-
yield
|
|
950
|
+
yield this.runLoggedHandleOperation({
|
|
951
|
+
operation: 'close',
|
|
952
|
+
device: current,
|
|
953
|
+
path,
|
|
954
|
+
task: () => current.close(),
|
|
955
|
+
});
|
|
437
956
|
}
|
|
438
957
|
catch (error) {
|
|
439
|
-
(
|
|
958
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
959
|
+
throw error;
|
|
440
960
|
}
|
|
441
961
|
});
|
|
442
962
|
}
|
|
443
|
-
clearEndpointHalt(device, direction, endpointNumber) {
|
|
963
|
+
clearEndpointHalt(path, device, direction, endpointNumber) {
|
|
444
964
|
var _a;
|
|
445
965
|
return __awaiter(this, void 0, void 0, function* () {
|
|
966
|
+
this.assertHandleAvailable(device);
|
|
446
967
|
try {
|
|
447
|
-
yield
|
|
968
|
+
yield this.runLoggedHandleOperation({
|
|
969
|
+
operation: direction === 'in' ? 'clearHaltIn' : 'clearHaltOut',
|
|
970
|
+
device,
|
|
971
|
+
path,
|
|
972
|
+
endpointNumber,
|
|
973
|
+
task: () => device.clearHalt(direction, endpointNumber),
|
|
974
|
+
});
|
|
448
975
|
}
|
|
449
976
|
catch (error) {
|
|
977
|
+
this.assertRunning();
|
|
450
978
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] clearHalt ${direction} endpoint ${endpointNumber} failed, continuing:`, error);
|
|
451
979
|
}
|
|
452
980
|
});
|
|
453
981
|
}
|
|
454
982
|
assertAcquired(path) {
|
|
983
|
+
this.assertRunning();
|
|
455
984
|
if (!this.acquiredPaths.has(path)) {
|
|
456
985
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
|
|
457
986
|
}
|
|
@@ -508,7 +1037,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
508
1037
|
throwWebUsbIoError(path, operation, error) {
|
|
509
1038
|
var _a;
|
|
510
1039
|
return __awaiter(this, void 0, void 0, function* () {
|
|
511
|
-
|
|
1040
|
+
this.assertRunning();
|
|
1041
|
+
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
512
1042
|
throw error;
|
|
513
1043
|
if (error instanceof transport.ProtocolV2LinkError && error.code !== 'io')
|
|
514
1044
|
throw error;
|
|
@@ -542,7 +1072,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
542
1072
|
return /LIBUSB_ERROR_ACCESS|\b(?:access|permission) denied\b/i.test(message);
|
|
543
1073
|
}
|
|
544
1074
|
isRetryablePacketIoError(error) {
|
|
545
|
-
if (this.
|
|
1075
|
+
if (this.stopped)
|
|
1076
|
+
return false;
|
|
1077
|
+
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
546
1078
|
return false;
|
|
547
1079
|
const message = this.getErrorMessage(error).toLowerCase();
|
|
548
1080
|
if (this.isWebUsbPermissionDeniedMessage(message))
|
|
@@ -561,21 +1093,43 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
561
1093
|
this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
562
1094
|
this.staleProtocolPaths.add(path);
|
|
563
1095
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
1096
|
+
this.assertRunning();
|
|
564
1097
|
try {
|
|
565
1098
|
const currentDevice = yield this.findDevice(path);
|
|
1099
|
+
this.assertHandleAvailable(currentDevice);
|
|
566
1100
|
if (currentDevice.opened) {
|
|
567
1101
|
const endpoints = this.deviceEndpoints.get(path);
|
|
568
1102
|
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
569
|
-
|
|
570
|
-
|
|
1103
|
+
if (this.getInterfaceClaimed(currentDevice, ifaceNum) === false) {
|
|
1104
|
+
this.logSkippedHandleOperation('releaseInterface', currentDevice, 'interface-not-claimed', ifaceNum);
|
|
571
1105
|
}
|
|
572
|
-
|
|
573
|
-
|
|
1106
|
+
else {
|
|
1107
|
+
try {
|
|
1108
|
+
yield this.runLoggedHandleOperation({
|
|
1109
|
+
operation: 'releaseInterface',
|
|
1110
|
+
device: currentDevice,
|
|
1111
|
+
path,
|
|
1112
|
+
interfaceNumber: ifaceNum,
|
|
1113
|
+
task: () => currentDevice.releaseInterface(ifaceNum),
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
catch (releaseError) {
|
|
1117
|
+
this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
|
|
1118
|
+
}
|
|
574
1119
|
}
|
|
575
|
-
|
|
1120
|
+
this.assertHandleAvailable(currentDevice);
|
|
1121
|
+
yield this.runLoggedHandleOperation({
|
|
1122
|
+
operation: 'close',
|
|
1123
|
+
device: currentDevice,
|
|
1124
|
+
path,
|
|
1125
|
+
task: () => currentDevice.close(),
|
|
1126
|
+
});
|
|
576
1127
|
}
|
|
577
1128
|
}
|
|
578
1129
|
catch (closeError) {
|
|
1130
|
+
this.assertRunning();
|
|
1131
|
+
if (this.isHandleOwnershipError(closeError))
|
|
1132
|
+
throw closeError;
|
|
579
1133
|
this.Log.debug('[WebUsbTransport] close device before retry error:', closeError);
|
|
580
1134
|
}
|
|
581
1135
|
yield this.getConnectedDevices();
|
|
@@ -617,6 +1171,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
617
1171
|
yield this.reconnectForPacketIoRetry(path, 'out', attempt, error);
|
|
618
1172
|
}
|
|
619
1173
|
catch (reconnectError) {
|
|
1174
|
+
this.assertRunning();
|
|
1175
|
+
if (this.isHandleOwnershipError(reconnectError))
|
|
1176
|
+
throw reconnectError;
|
|
620
1177
|
lastError = reconnectError;
|
|
621
1178
|
this.Log.debug(`[WebUsbTransport] transferout reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
622
1179
|
}
|
|
@@ -635,7 +1192,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
635
1192
|
const endpoints = this.deviceEndpoints.get(path);
|
|
636
1193
|
const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
637
1194
|
const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
1195
|
+
this.assertHandleAvailable(device);
|
|
638
1196
|
const result = yield device.transferOut(endpointOut, transferBuffer);
|
|
1197
|
+
this.assertHandleAvailable(device);
|
|
639
1198
|
if (result.status !== 'ok') {
|
|
640
1199
|
return this.throwWebUsbIoError(path, 'transferOut', new Error(`transferOut status: ${String(result.status)}`));
|
|
641
1200
|
}
|
|
@@ -656,7 +1215,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
656
1215
|
}
|
|
657
1216
|
const endpoints = this.deviceEndpoints.get(path);
|
|
658
1217
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
1218
|
+
this.assertHandleAvailable(device);
|
|
659
1219
|
const result = yield device.transferIn(endpointIn, length);
|
|
1220
|
+
this.assertHandleAvailable(device);
|
|
660
1221
|
return this.getTransferInData(result);
|
|
661
1222
|
}
|
|
662
1223
|
catch (error) {
|
|
@@ -672,6 +1233,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
672
1233
|
yield this.reconnectForPacketIoRetry(path, 'in', attempt, error);
|
|
673
1234
|
}
|
|
674
1235
|
catch (reconnectError) {
|
|
1236
|
+
this.assertRunning();
|
|
1237
|
+
if (this.isHandleOwnershipError(reconnectError))
|
|
1238
|
+
throw reconnectError;
|
|
675
1239
|
lastError = reconnectError;
|
|
676
1240
|
this.Log.debug(`[WebUsbTransport] transferin reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
677
1241
|
}
|
|
@@ -689,7 +1253,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
689
1253
|
}
|
|
690
1254
|
const endpoints = this.deviceEndpoints.get(path);
|
|
691
1255
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
1256
|
+
this.assertHandleAvailable(device);
|
|
692
1257
|
const result = yield device.transferIn(endpointIn, length);
|
|
1258
|
+
this.assertHandleAvailable(device);
|
|
693
1259
|
return this.getTransferInData(result);
|
|
694
1260
|
});
|
|
695
1261
|
}
|
|
@@ -749,10 +1315,10 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
749
1315
|
return false;
|
|
750
1316
|
}
|
|
751
1317
|
try {
|
|
752
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
1318
|
+
const response = yield this.callProtocolV1(path, this.identityProbe ? 'GetFeatures' : 'Initialize', {}, {
|
|
753
1319
|
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
754
1320
|
});
|
|
755
|
-
return
|
|
1321
|
+
return !this.identityProbe || response.type === 'Features';
|
|
756
1322
|
}
|
|
757
1323
|
catch (error) {
|
|
758
1324
|
if (this.isWebUsbIoHardwareError(error))
|
|
@@ -868,9 +1434,25 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
868
1434
|
}
|
|
869
1435
|
release(path) {
|
|
870
1436
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1437
|
+
const startedAt = Date.now();
|
|
1438
|
+
this.logLifecycle('release', { phase: 'start' });
|
|
871
1439
|
this.acquiredPaths.delete(path);
|
|
872
|
-
|
|
873
|
-
|
|
1440
|
+
try {
|
|
1441
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
1442
|
+
yield this.closeOpenDevice(path);
|
|
1443
|
+
this.logLifecycle('release', {
|
|
1444
|
+
phase: 'success',
|
|
1445
|
+
durationMs: Date.now() - startedAt,
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
catch (error) {
|
|
1449
|
+
this.logLifecycle('release', Object.assign({ phase: 'error', durationMs: Date.now() - startedAt }, this.getDiagnosticError(error, path)));
|
|
1450
|
+
throw error;
|
|
1451
|
+
}
|
|
1452
|
+
finally {
|
|
1453
|
+
this.releaseIdentityProbeHandle(path);
|
|
1454
|
+
this.releaseNormalHandles(path);
|
|
1455
|
+
}
|
|
874
1456
|
this.deviceEndpoints.delete(path);
|
|
875
1457
|
});
|
|
876
1458
|
}
|
|
@@ -924,7 +1506,7 @@ function resolveBlePacketCapacity(mtu, maximumPacketCapacity, fallbackPacketCapa
|
|
|
924
1506
|
}
|
|
925
1507
|
|
|
926
1508
|
const { parseConfigure, ProtocolV1, check } = transport__default["default"];
|
|
927
|
-
const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
|
|
1509
|
+
const toBleDescriptor = (device, protocolType) => (Object.assign(Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (device.bleColorCode ? { bleColorCode: device.bleColorCode } : {})), (protocolType ? { protocolType } : {})));
|
|
928
1510
|
const invokeNobleBle = (request) => __awaiter(void 0, void 0, void 0, function* () {
|
|
929
1511
|
const response = yield request;
|
|
930
1512
|
if (response &&
|
|
@@ -1277,10 +1859,12 @@ class ElectronBleTransport {
|
|
|
1277
1859
|
});
|
|
1278
1860
|
}
|
|
1279
1861
|
createProtocolMismatchError(expected) {
|
|
1280
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
1862
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
1863
|
+
expectedProtocol: expected,
|
|
1864
|
+
});
|
|
1281
1865
|
}
|
|
1282
1866
|
createProtocolDetectionError() {
|
|
1283
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
1867
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed);
|
|
1284
1868
|
}
|
|
1285
1869
|
clearProbeProtocol(uuid, protocol) {
|
|
1286
1870
|
if (this.deviceProtocol.get(uuid) === protocol) {
|