@onekeyfe/hd-transport-web-device 1.2.8-alpha.10 → 1.2.8-alpha.101
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 +45 -30
- package/__tests__/webusb-identity-probe.test.ts +887 -0
- package/__tests__/webusb-protocol-cache.test.ts +121 -0
- package/__tests__/webusb-protocol-v2-timeout.test.ts +27 -8
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +24 -3
- package/dist/index.js +405 -35
- package/dist/webusb.d.ts +24 -3
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/electron-ble-transport.ts +10 -15
- package/src/webusb.ts +400 -35
package/dist/index.js
CHANGED
|
@@ -51,11 +51,15 @@ 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';
|
|
54
55
|
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
55
56
|
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
56
57
|
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
57
58
|
let activeWebUsbTransport;
|
|
58
59
|
let usbDisconnectListenerAttached = false;
|
|
60
|
+
const identityProbeHandles = new WeakSet();
|
|
61
|
+
const openingHandles = new WeakSet();
|
|
62
|
+
const normalHandleOwners = new WeakMap();
|
|
59
63
|
function registerActiveWebUsbTransport(instance, usb) {
|
|
60
64
|
activeWebUsbTransport = instance;
|
|
61
65
|
if (usbDisconnectListenerAttached)
|
|
@@ -72,17 +76,22 @@ function registerActiveWebUsbTransport(instance, usb) {
|
|
|
72
76
|
});
|
|
73
77
|
}
|
|
74
78
|
class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
75
|
-
constructor() {
|
|
79
|
+
constructor(identityProbe = false) {
|
|
76
80
|
super({
|
|
77
81
|
router: transport.PROTOCOL_V2_CHANNEL_USB,
|
|
78
82
|
maxFrameBytes: transport.PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
79
83
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
80
84
|
});
|
|
85
|
+
this.identityProbe = identityProbe;
|
|
81
86
|
this.deviceProtocol = new Map();
|
|
82
87
|
this.confirmedDeviceProtocols = new Map();
|
|
83
88
|
this.deviceProtocolHints = new Map();
|
|
84
89
|
this.staleProtocolPaths = new Set();
|
|
85
90
|
this.acquiredPaths = new Set();
|
|
91
|
+
this.identityProbeDevices = new Map();
|
|
92
|
+
this.normalDevices = new Map();
|
|
93
|
+
this.pendingHandleOperations = new Set();
|
|
94
|
+
this.identityProbeCleanupPaths = new Set();
|
|
86
95
|
this.probedDeviceObjects = new Map();
|
|
87
96
|
this.deviceEndpoints = new Map();
|
|
88
97
|
this.name = 'WebUsbTransport';
|
|
@@ -101,7 +110,104 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
101
110
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'WebUSB is not supported by current browsers');
|
|
102
111
|
}
|
|
103
112
|
this.usb = usb;
|
|
104
|
-
|
|
113
|
+
if (!this.identityProbe)
|
|
114
|
+
registerActiveWebUsbTransport(this, usb);
|
|
115
|
+
}
|
|
116
|
+
stop() {
|
|
117
|
+
this.stopped = true;
|
|
118
|
+
this.acquiredPaths.clear();
|
|
119
|
+
if (activeWebUsbTransport === this)
|
|
120
|
+
activeWebUsbTransport = undefined;
|
|
121
|
+
if (!this.stopPromise) {
|
|
122
|
+
this.stopPromise = this.stopResources().catch(error => {
|
|
123
|
+
this.stopPromise = undefined;
|
|
124
|
+
throw error;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return this.stopPromise;
|
|
128
|
+
}
|
|
129
|
+
stopResources() {
|
|
130
|
+
var _a, _b, _c;
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
let cleanupError;
|
|
133
|
+
try {
|
|
134
|
+
yield this.disposeProtocolV2UsbLinks('WebUSB transport stopped');
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
cleanupError = error;
|
|
138
|
+
}
|
|
139
|
+
yield Promise.allSettled([...this.pendingHandleOperations]);
|
|
140
|
+
const handles = this.identityProbe
|
|
141
|
+
? [...this.identityProbeDevices].map(([path, device]) => [device, path])
|
|
142
|
+
: [...this.normalDevices];
|
|
143
|
+
for (const [device, path] of handles) {
|
|
144
|
+
if (!this.identityProbe && normalHandleOwners.get(device) !== this) {
|
|
145
|
+
this.normalDevices.delete(device);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
try {
|
|
149
|
+
if (device.opened) {
|
|
150
|
+
const iface = (_b = (_a = this.deviceEndpoints.get(path)) === null || _a === void 0 ? void 0 : _a.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
151
|
+
try {
|
|
152
|
+
yield device.releaseInterface(iface);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] stop releaseInterface failed:', error);
|
|
156
|
+
}
|
|
157
|
+
yield device.close();
|
|
158
|
+
if (device.opened)
|
|
159
|
+
throw new Error('WebUSB handle remains open after stop');
|
|
160
|
+
}
|
|
161
|
+
if (this.identityProbe) {
|
|
162
|
+
identityProbeHandles.delete(device);
|
|
163
|
+
this.identityProbeDevices.delete(path);
|
|
164
|
+
this.identityProbeCleanupPaths.delete(path);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
normalHandleOwners.delete(device);
|
|
168
|
+
this.normalDevices.delete(device);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
cleanupError !== null && cleanupError !== void 0 ? cleanupError : (cleanupError = error);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.deviceList = [];
|
|
177
|
+
this.deviceProtocol.clear();
|
|
178
|
+
this.confirmedDeviceProtocols.clear();
|
|
179
|
+
this.deviceProtocolHints.clear();
|
|
180
|
+
this.staleProtocolPaths.clear();
|
|
181
|
+
this.probedDeviceObjects.clear();
|
|
182
|
+
if (!this.normalDevices.size && !this.identityProbeDevices.size) {
|
|
183
|
+
this.deviceEndpoints.clear();
|
|
184
|
+
}
|
|
185
|
+
if (cleanupError) {
|
|
186
|
+
throw cleanupError instanceof Error
|
|
187
|
+
? cleanupError
|
|
188
|
+
: new Error(this.getErrorMessage(cleanupError));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
assertRunning() {
|
|
193
|
+
if (this.stopped) {
|
|
194
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'WebUSB transport is stopped');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
runHandleOperation(operation) {
|
|
198
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
199
|
+
this.assertRunning();
|
|
200
|
+
const pending = Promise.resolve(operation());
|
|
201
|
+
this.pendingHandleOperations.add(pending);
|
|
202
|
+
try {
|
|
203
|
+
const result = yield pending;
|
|
204
|
+
this.assertRunning();
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
this.pendingHandleOperations.delete(pending);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
105
211
|
}
|
|
106
212
|
emitDeviceDisconnect(path, device) {
|
|
107
213
|
var _a, _b;
|
|
@@ -150,15 +256,36 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
150
256
|
}
|
|
151
257
|
enumerate() {
|
|
152
258
|
return __awaiter(this, void 0, void 0, function* () {
|
|
259
|
+
for (const path of this.identityProbeCleanupPaths) {
|
|
260
|
+
try {
|
|
261
|
+
yield this.release(path);
|
|
262
|
+
}
|
|
263
|
+
catch (_a) {
|
|
264
|
+
}
|
|
265
|
+
}
|
|
153
266
|
yield this.getConnectedDevices();
|
|
154
267
|
return this.deviceList;
|
|
155
268
|
});
|
|
156
269
|
}
|
|
157
270
|
getConnectedDevices() {
|
|
271
|
+
var _a;
|
|
158
272
|
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
+
this.assertRunning();
|
|
159
274
|
if (!this.usb)
|
|
160
275
|
return [];
|
|
161
276
|
const devices = yield this.usb.getDevices();
|
|
277
|
+
this.assertRunning();
|
|
278
|
+
for (const owner of new Set(devices.map(device => normalHandleOwners.get(device)))) {
|
|
279
|
+
if (owner === null || owner === void 0 ? void 0 : owner.stopped) {
|
|
280
|
+
try {
|
|
281
|
+
yield owner.stop();
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] stopped owner cleanup failed:', error);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
this.assertRunning();
|
|
162
289
|
const onekeyDevices = devices.filter(dev => {
|
|
163
290
|
const isOneKey = hdShared.ONEKEY_WEBUSB_FILTER.some((desc) => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
|
|
164
291
|
return isOneKey && !hdShared.isKnownTrezorWebUsbDevice(dev);
|
|
@@ -185,8 +312,16 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
185
312
|
acquire(input) {
|
|
186
313
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
187
314
|
return __awaiter(this, void 0, void 0, function* () {
|
|
315
|
+
this.assertRunning();
|
|
188
316
|
if (!input.path)
|
|
189
317
|
return;
|
|
318
|
+
if (this.identityProbe) {
|
|
319
|
+
yield this.getConnectedDevices();
|
|
320
|
+
this.claimIdentityProbeHandle(input.path, yield this.findDevice(input.path));
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
this.claimNormalHandle(input.path, yield this.findDevice(input.path));
|
|
324
|
+
}
|
|
190
325
|
try {
|
|
191
326
|
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
192
327
|
yield this.closeOpenDevice(input.path);
|
|
@@ -236,31 +371,44 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
236
371
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
237
372
|
}
|
|
238
373
|
const detectedProtocol = yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
374
|
+
this.assertRunning();
|
|
239
375
|
this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
|
|
240
376
|
const probedDevice = (_g = this.deviceList.find(d => d.path === input.path)) === null || _g === void 0 ? void 0 : _g.device;
|
|
241
377
|
if (probedDevice) {
|
|
242
378
|
this.probedDeviceObjects.set(input.path, probedDevice);
|
|
243
379
|
}
|
|
244
380
|
}
|
|
381
|
+
this.assertRunning();
|
|
245
382
|
this.acquiredPaths.add(input.path);
|
|
246
383
|
return yield Promise.resolve(input.path);
|
|
247
384
|
}
|
|
248
385
|
catch (e) {
|
|
249
386
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
250
387
|
this.acquiredPaths.delete(input.path);
|
|
251
|
-
|
|
388
|
+
try {
|
|
389
|
+
yield this.closeOpenDevice(input.path);
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
this.releaseIdentityProbeHandle(input.path);
|
|
393
|
+
this.releaseNormalHandles(input.path);
|
|
394
|
+
}
|
|
252
395
|
throw e;
|
|
253
396
|
}
|
|
254
397
|
});
|
|
255
398
|
}
|
|
256
399
|
createProtocolMismatchError(expected) {
|
|
257
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
400
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
401
|
+
expectedProtocol: expected,
|
|
402
|
+
});
|
|
258
403
|
}
|
|
259
404
|
createProtocolProbeTimeoutError(expected, attempts) {
|
|
260
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
405
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
406
|
+
expectedProtocol: expected,
|
|
407
|
+
attempts,
|
|
408
|
+
});
|
|
261
409
|
}
|
|
262
410
|
createProtocolDetectionError() {
|
|
263
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
411
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed);
|
|
264
412
|
}
|
|
265
413
|
detectProtocol(path, expectedProtocol, protocolHint) {
|
|
266
414
|
var _a;
|
|
@@ -328,10 +476,15 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
328
476
|
return __awaiter(this, void 0, void 0, function* () {
|
|
329
477
|
const maxRetries = 5;
|
|
330
478
|
for (let i = 0; i < maxRetries; i++) {
|
|
479
|
+
this.assertRunning();
|
|
331
480
|
try {
|
|
332
481
|
return yield this.connectToDevice(path, first);
|
|
333
482
|
}
|
|
334
483
|
catch (e) {
|
|
484
|
+
this.assertRunning();
|
|
485
|
+
if (e instanceof hdShared.HardwareError && e.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission) {
|
|
486
|
+
throw e;
|
|
487
|
+
}
|
|
335
488
|
if (i === maxRetries - 1) {
|
|
336
489
|
throw e;
|
|
337
490
|
}
|
|
@@ -371,69 +524,174 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
371
524
|
connectToDevice(path, first) {
|
|
372
525
|
var _a;
|
|
373
526
|
return __awaiter(this, void 0, void 0, function* () {
|
|
527
|
+
this.assertRunning();
|
|
374
528
|
let device = yield this.findDevice(path);
|
|
529
|
+
if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
|
|
530
|
+
yield this.closeOpenDevice(path);
|
|
531
|
+
this.releaseIdentityProbeHandle(path);
|
|
532
|
+
this.claimIdentityProbeHandle(path, device);
|
|
533
|
+
}
|
|
534
|
+
if (!this.identityProbe)
|
|
535
|
+
this.claimNormalHandle(path, device);
|
|
536
|
+
this.assertHandleAvailable(device);
|
|
375
537
|
if (!device.opened) {
|
|
376
|
-
|
|
538
|
+
openingHandles.add(device);
|
|
539
|
+
try {
|
|
540
|
+
yield this.runHandleOperation(() => device.open());
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
544
|
+
}
|
|
545
|
+
finally {
|
|
546
|
+
openingHandles.delete(device);
|
|
547
|
+
}
|
|
377
548
|
}
|
|
378
549
|
if (first || this.deviceProtocol.get(path) !== 'V1') {
|
|
379
550
|
try {
|
|
380
|
-
yield device.reset();
|
|
551
|
+
yield this.runHandleOperation(() => device.reset());
|
|
381
552
|
}
|
|
382
553
|
catch (error) {
|
|
554
|
+
this.assertRunning();
|
|
383
555
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
|
|
384
556
|
}
|
|
385
557
|
}
|
|
386
558
|
yield this.getConnectedDevices();
|
|
387
559
|
device = yield this.findDevice(path);
|
|
560
|
+
if (this.identityProbe && this.identityProbeDevices.get(path) !== device) {
|
|
561
|
+
yield this.closeOpenDevice(path);
|
|
562
|
+
this.releaseIdentityProbeHandle(path);
|
|
563
|
+
this.claimIdentityProbeHandle(path, device);
|
|
564
|
+
}
|
|
565
|
+
if (!this.identityProbe)
|
|
566
|
+
this.claimNormalHandle(path, device);
|
|
567
|
+
this.assertHandleAvailable(device);
|
|
388
568
|
if (!device.opened) {
|
|
389
|
-
|
|
569
|
+
openingHandles.add(device);
|
|
570
|
+
try {
|
|
571
|
+
yield this.runHandleOperation(() => device.open());
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
575
|
+
}
|
|
576
|
+
finally {
|
|
577
|
+
openingHandles.delete(device);
|
|
578
|
+
}
|
|
390
579
|
}
|
|
391
580
|
if (first ||
|
|
392
581
|
!device.configuration ||
|
|
393
582
|
device.configuration.configurationValue !== this.configurationId) {
|
|
394
|
-
yield device.selectConfiguration(this.configurationId);
|
|
583
|
+
yield this.runHandleOperation(() => device.selectConfiguration(this.configurationId));
|
|
395
584
|
}
|
|
396
585
|
const endpoints = this.discoverEndpoints(device);
|
|
397
586
|
this.deviceEndpoints.set(path, endpoints);
|
|
398
|
-
|
|
587
|
+
try {
|
|
588
|
+
yield this.runHandleOperation(() => device.claimInterface(endpoints.interfaceNumber));
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
return this.throwWebUsbIoError(path, 'claimInterface', error);
|
|
592
|
+
}
|
|
399
593
|
yield this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
400
594
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
401
595
|
});
|
|
402
596
|
}
|
|
597
|
+
assertHandleAvailable(device) {
|
|
598
|
+
this.assertRunning();
|
|
599
|
+
const normalOwner = normalHandleOwners.get(device);
|
|
600
|
+
if ((normalOwner && normalOwner !== this) ||
|
|
601
|
+
(identityProbeHandles.has(device) &&
|
|
602
|
+
![...this.identityProbeDevices.values()].includes(device))) {
|
|
603
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, USB_DEVICE_IN_USE);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
claimNormalHandle(path, device) {
|
|
607
|
+
this.assertHandleAvailable(device);
|
|
608
|
+
normalHandleOwners.set(device, this);
|
|
609
|
+
this.normalDevices.set(device, path);
|
|
610
|
+
}
|
|
611
|
+
releaseNormalHandles(path) {
|
|
612
|
+
if (this.stopped)
|
|
613
|
+
return;
|
|
614
|
+
for (const [device, ownedPath] of this.normalDevices) {
|
|
615
|
+
if (ownedPath === path) {
|
|
616
|
+
normalHandleOwners.delete(device);
|
|
617
|
+
this.normalDevices.delete(device);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
isHandleOwnershipError(error) {
|
|
622
|
+
return (error instanceof hdShared.HardwareError &&
|
|
623
|
+
error.errorCode === hdShared.HardwareErrorCode.RuntimeError &&
|
|
624
|
+
this.getErrorMessage(error).includes(USB_DEVICE_IN_USE));
|
|
625
|
+
}
|
|
626
|
+
claimIdentityProbeHandle(path, device) {
|
|
627
|
+
this.assertRunning();
|
|
628
|
+
if (device.opened ||
|
|
629
|
+
openingHandles.has(device) ||
|
|
630
|
+
identityProbeHandles.has(device) ||
|
|
631
|
+
normalHandleOwners.has(device)) {
|
|
632
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, USB_DEVICE_IN_USE);
|
|
633
|
+
}
|
|
634
|
+
identityProbeHandles.add(device);
|
|
635
|
+
this.identityProbeDevices.set(path, device);
|
|
636
|
+
}
|
|
637
|
+
releaseIdentityProbeHandle(path) {
|
|
638
|
+
if (this.stopped)
|
|
639
|
+
return;
|
|
640
|
+
const device = this.identityProbeDevices.get(path);
|
|
641
|
+
if (device === null || device === void 0 ? void 0 : device.opened) {
|
|
642
|
+
this.identityProbeCleanupPaths.add(path);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (device)
|
|
646
|
+
identityProbeHandles.delete(device);
|
|
647
|
+
this.identityProbeDevices.delete(path);
|
|
648
|
+
this.identityProbeCleanupPaths.delete(path);
|
|
649
|
+
}
|
|
403
650
|
closeOpenDevice(path) {
|
|
404
651
|
var _a, _b, _c, _d;
|
|
405
652
|
return __awaiter(this, void 0, void 0, function* () {
|
|
406
|
-
|
|
653
|
+
if (this.stopped)
|
|
654
|
+
return;
|
|
655
|
+
const current = this.identityProbe
|
|
656
|
+
? this.identityProbeDevices.get(path)
|
|
657
|
+
: (_a = this.deviceList.find(device => device.path === path)) === null || _a === void 0 ? void 0 : _a.device;
|
|
407
658
|
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
408
659
|
return;
|
|
660
|
+
this.assertHandleAvailable(current);
|
|
409
661
|
const endpoints = this.deviceEndpoints.get(path);
|
|
410
662
|
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
411
663
|
try {
|
|
412
|
-
yield current.releaseInterface(ifaceNum);
|
|
664
|
+
yield this.runHandleOperation(() => current.releaseInterface(ifaceNum));
|
|
413
665
|
}
|
|
414
666
|
catch (error) {
|
|
415
667
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
416
668
|
}
|
|
669
|
+
this.assertHandleAvailable(current);
|
|
417
670
|
try {
|
|
418
|
-
yield current.close();
|
|
671
|
+
yield this.runHandleOperation(() => current.close());
|
|
419
672
|
}
|
|
420
673
|
catch (error) {
|
|
421
674
|
(_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[WebUsbTransport] close before reconnect failed:', error);
|
|
675
|
+
if (this.identityProbe)
|
|
676
|
+
throw error;
|
|
422
677
|
}
|
|
423
678
|
});
|
|
424
679
|
}
|
|
425
680
|
clearEndpointHalt(device, direction, endpointNumber) {
|
|
426
681
|
var _a;
|
|
427
682
|
return __awaiter(this, void 0, void 0, function* () {
|
|
683
|
+
this.assertHandleAvailable(device);
|
|
428
684
|
try {
|
|
429
|
-
yield device.clearHalt(direction, endpointNumber);
|
|
685
|
+
yield this.runHandleOperation(() => device.clearHalt(direction, endpointNumber));
|
|
430
686
|
}
|
|
431
687
|
catch (error) {
|
|
688
|
+
this.assertRunning();
|
|
432
689
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] clearHalt ${direction} endpoint ${endpointNumber} failed, continuing:`, error);
|
|
433
690
|
}
|
|
434
691
|
});
|
|
435
692
|
}
|
|
436
693
|
assertAcquired(path) {
|
|
694
|
+
this.assertRunning();
|
|
437
695
|
if (!this.acquiredPaths.has(path)) {
|
|
438
696
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
|
|
439
697
|
}
|
|
@@ -459,8 +717,79 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
459
717
|
}
|
|
460
718
|
return String(error);
|
|
461
719
|
}
|
|
720
|
+
getErrorName(error) {
|
|
721
|
+
if (typeof error === 'object' && error && 'name' in error) {
|
|
722
|
+
const { name } = error;
|
|
723
|
+
return typeof name === 'string' ? name : String(name !== null && name !== void 0 ? name : '');
|
|
724
|
+
}
|
|
725
|
+
return '';
|
|
726
|
+
}
|
|
727
|
+
isWebUsbIoHardwareError(error) {
|
|
728
|
+
return (error instanceof hdShared.HardwareError &&
|
|
729
|
+
(error.errorCode === hdShared.HardwareErrorCode.BridgeDeviceDisconnected ||
|
|
730
|
+
error.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission ||
|
|
731
|
+
error.errorCode === hdShared.HardwareErrorCode.WebUsbDeviceAccessError));
|
|
732
|
+
}
|
|
733
|
+
isDeviceStillEnumerated(path) {
|
|
734
|
+
var _a;
|
|
735
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
736
|
+
if (!this.usb)
|
|
737
|
+
return undefined;
|
|
738
|
+
try {
|
|
739
|
+
const devices = yield this.usb.getDevices();
|
|
740
|
+
return devices.some(device => hdShared.resolveOneKeyUsbDevicePath(device) === path);
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] failed to verify WebUSB device presence:', error);
|
|
744
|
+
return undefined;
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
throwWebUsbIoError(path, operation, error) {
|
|
749
|
+
var _a;
|
|
750
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
751
|
+
this.assertRunning();
|
|
752
|
+
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
753
|
+
throw error;
|
|
754
|
+
if (error instanceof transport.ProtocolV2LinkError && error.code !== 'io')
|
|
755
|
+
throw error;
|
|
756
|
+
const nativeError = error instanceof transport.ProtocolV2LinkError ? (_a = error.cause) !== null && _a !== void 0 ? _a : error : error;
|
|
757
|
+
if (this.isWebUsbIoHardwareError(nativeError))
|
|
758
|
+
throw nativeError;
|
|
759
|
+
const nativeErrorName = this.getErrorName(nativeError);
|
|
760
|
+
const nativeErrorMessage = this.getErrorMessage(nativeError);
|
|
761
|
+
if (this.isWebUsbPermissionDeniedMessage(nativeErrorMessage)) {
|
|
762
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BridgeNeedsPermission, nativeErrorMessage, {
|
|
763
|
+
operation,
|
|
764
|
+
nativeErrorName: nativeErrorName || undefined,
|
|
765
|
+
nativeErrorMessage,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
const disconnectedByMessage = /(?:device )?disconnected|device not found|action was interrupted/i.test(`${nativeErrorName}: ${nativeErrorMessage}`);
|
|
769
|
+
const stillEnumerated = disconnectedByMessage
|
|
770
|
+
? false
|
|
771
|
+
: yield this.isDeviceStillEnumerated(path);
|
|
772
|
+
const errorCode = disconnectedByMessage || stillEnumerated === false
|
|
773
|
+
? hdShared.HardwareErrorCode.BridgeDeviceDisconnected
|
|
774
|
+
: hdShared.HardwareErrorCode.WebUsbDeviceAccessError;
|
|
775
|
+
throw hdShared.ERRORS.TypedError(errorCode, nativeErrorMessage || undefined, {
|
|
776
|
+
operation,
|
|
777
|
+
nativeErrorName: nativeErrorName || undefined,
|
|
778
|
+
nativeErrorMessage: nativeErrorMessage || undefined,
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
isWebUsbPermissionDeniedMessage(message) {
|
|
783
|
+
return /LIBUSB_ERROR_ACCESS|\b(?:access|permission) denied\b/i.test(message);
|
|
784
|
+
}
|
|
462
785
|
isRetryablePacketIoError(error) {
|
|
786
|
+
if (this.stopped)
|
|
787
|
+
return false;
|
|
788
|
+
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
789
|
+
return false;
|
|
463
790
|
const message = this.getErrorMessage(error).toLowerCase();
|
|
791
|
+
if (this.isWebUsbPermissionDeniedMessage(message))
|
|
792
|
+
return false;
|
|
464
793
|
return (message.includes('transferout') ||
|
|
465
794
|
message.includes('transferin') ||
|
|
466
795
|
message.includes('usbdevice') ||
|
|
@@ -475,21 +804,27 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
475
804
|
this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
476
805
|
this.staleProtocolPaths.add(path);
|
|
477
806
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
807
|
+
this.assertRunning();
|
|
478
808
|
try {
|
|
479
809
|
const currentDevice = yield this.findDevice(path);
|
|
810
|
+
this.assertHandleAvailable(currentDevice);
|
|
480
811
|
if (currentDevice.opened) {
|
|
481
812
|
const endpoints = this.deviceEndpoints.get(path);
|
|
482
813
|
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
483
814
|
try {
|
|
484
|
-
yield currentDevice.releaseInterface(ifaceNum);
|
|
815
|
+
yield this.runHandleOperation(() => currentDevice.releaseInterface(ifaceNum));
|
|
485
816
|
}
|
|
486
817
|
catch (releaseError) {
|
|
487
818
|
this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
|
|
488
819
|
}
|
|
489
|
-
|
|
820
|
+
this.assertHandleAvailable(currentDevice);
|
|
821
|
+
yield this.runHandleOperation(() => currentDevice.close());
|
|
490
822
|
}
|
|
491
823
|
}
|
|
492
824
|
catch (closeError) {
|
|
825
|
+
this.assertRunning();
|
|
826
|
+
if (this.isHandleOwnershipError(closeError))
|
|
827
|
+
throw closeError;
|
|
493
828
|
this.Log.debug('[WebUsbTransport] close device before retry error:', closeError);
|
|
494
829
|
}
|
|
495
830
|
yield this.getConnectedDevices();
|
|
@@ -525,18 +860,21 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
525
860
|
lastError = error;
|
|
526
861
|
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
|
|
527
862
|
if (!shouldRetry) {
|
|
528
|
-
|
|
863
|
+
return this.throwWebUsbIoError(path, 'transferOut', error);
|
|
529
864
|
}
|
|
530
865
|
try {
|
|
531
866
|
yield this.reconnectForPacketIoRetry(path, 'out', attempt, error);
|
|
532
867
|
}
|
|
533
868
|
catch (reconnectError) {
|
|
869
|
+
this.assertRunning();
|
|
870
|
+
if (this.isHandleOwnershipError(reconnectError))
|
|
871
|
+
throw reconnectError;
|
|
534
872
|
lastError = reconnectError;
|
|
535
873
|
this.Log.debug(`[WebUsbTransport] transferout reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
536
874
|
}
|
|
537
875
|
}
|
|
538
876
|
}
|
|
539
|
-
|
|
877
|
+
return this.throwWebUsbIoError(path, 'transferOut', lastError);
|
|
540
878
|
});
|
|
541
879
|
}
|
|
542
880
|
transferOutOnce(path, packet) {
|
|
@@ -549,7 +887,12 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
549
887
|
const endpoints = this.deviceEndpoints.get(path);
|
|
550
888
|
const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
551
889
|
const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
552
|
-
|
|
890
|
+
this.assertHandleAvailable(device);
|
|
891
|
+
const result = yield device.transferOut(endpointOut, transferBuffer);
|
|
892
|
+
this.assertHandleAvailable(device);
|
|
893
|
+
if (result.status !== 'ok') {
|
|
894
|
+
return this.throwWebUsbIoError(path, 'transferOut', new Error(`transferOut status: ${String(result.status)}`));
|
|
895
|
+
}
|
|
553
896
|
});
|
|
554
897
|
}
|
|
555
898
|
transferInWithRetry(path, length, cancelToken) {
|
|
@@ -567,7 +910,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
567
910
|
}
|
|
568
911
|
const endpoints = this.deviceEndpoints.get(path);
|
|
569
912
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
913
|
+
this.assertHandleAvailable(device);
|
|
570
914
|
const result = yield device.transferIn(endpointIn, length);
|
|
915
|
+
this.assertHandleAvailable(device);
|
|
571
916
|
return this.getTransferInData(result);
|
|
572
917
|
}
|
|
573
918
|
catch (error) {
|
|
@@ -577,18 +922,21 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
577
922
|
}
|
|
578
923
|
const shouldRetry = attempt < PACKET_IO_MAX_RETRIES && this.isRetryablePacketIoError(error);
|
|
579
924
|
if (!shouldRetry) {
|
|
580
|
-
|
|
925
|
+
return this.throwWebUsbIoError(path, 'transferIn', error);
|
|
581
926
|
}
|
|
582
927
|
try {
|
|
583
928
|
yield this.reconnectForPacketIoRetry(path, 'in', attempt, error);
|
|
584
929
|
}
|
|
585
930
|
catch (reconnectError) {
|
|
931
|
+
this.assertRunning();
|
|
932
|
+
if (this.isHandleOwnershipError(reconnectError))
|
|
933
|
+
throw reconnectError;
|
|
586
934
|
lastError = reconnectError;
|
|
587
935
|
this.Log.debug(`[WebUsbTransport] transferin reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
588
936
|
}
|
|
589
937
|
}
|
|
590
938
|
}
|
|
591
|
-
|
|
939
|
+
return this.throwWebUsbIoError(path, 'transferIn', lastError);
|
|
592
940
|
});
|
|
593
941
|
}
|
|
594
942
|
transferInOnce(path, length) {
|
|
@@ -600,7 +948,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
600
948
|
}
|
|
601
949
|
const endpoints = this.deviceEndpoints.get(path);
|
|
602
950
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
951
|
+
this.assertHandleAvailable(device);
|
|
603
952
|
const result = yield device.transferIn(endpointIn, length);
|
|
953
|
+
this.assertHandleAvailable(device);
|
|
604
954
|
return this.getTransferInData(result);
|
|
605
955
|
});
|
|
606
956
|
}
|
|
@@ -660,12 +1010,14 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
660
1010
|
return false;
|
|
661
1011
|
}
|
|
662
1012
|
try {
|
|
663
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
1013
|
+
const response = yield this.callProtocolV1(path, this.identityProbe ? 'GetFeatures' : 'Initialize', {}, {
|
|
664
1014
|
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
665
1015
|
});
|
|
666
|
-
return
|
|
1016
|
+
return !this.identityProbe || response.type === 'Features';
|
|
667
1017
|
}
|
|
668
|
-
catch (
|
|
1018
|
+
catch (error) {
|
|
1019
|
+
if (this.isWebUsbIoHardwareError(error))
|
|
1020
|
+
throw error;
|
|
669
1021
|
return false;
|
|
670
1022
|
}
|
|
671
1023
|
});
|
|
@@ -680,6 +1032,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
680
1032
|
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
681
1033
|
logger: this.Log,
|
|
682
1034
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
1035
|
+
shouldRethrow: error => this.isWebUsbIoHardwareError(error),
|
|
683
1036
|
});
|
|
684
1037
|
});
|
|
685
1038
|
}
|
|
@@ -726,7 +1079,15 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
726
1079
|
}
|
|
727
1080
|
callProtocolV2(path, name, data, options) {
|
|
728
1081
|
return __awaiter(this, void 0, void 0, function* () {
|
|
729
|
-
|
|
1082
|
+
try {
|
|
1083
|
+
return yield this.callProtocolV2Usb(path, name, data, options);
|
|
1084
|
+
}
|
|
1085
|
+
catch (error) {
|
|
1086
|
+
if (!(error instanceof transport.ProtocolV2LinkError) || error.code !== 'io')
|
|
1087
|
+
throw error;
|
|
1088
|
+
const operation = error.message.includes(' USB write failed:') ? 'transferOut' : 'transferIn';
|
|
1089
|
+
return this.throwWebUsbIoError(path, operation, error);
|
|
1090
|
+
}
|
|
730
1091
|
});
|
|
731
1092
|
}
|
|
732
1093
|
receiveData(path, timeoutMs) {
|
|
@@ -769,8 +1130,14 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
769
1130
|
release(path) {
|
|
770
1131
|
return __awaiter(this, void 0, void 0, function* () {
|
|
771
1132
|
this.acquiredPaths.delete(path);
|
|
772
|
-
|
|
773
|
-
|
|
1133
|
+
try {
|
|
1134
|
+
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
1135
|
+
yield this.closeOpenDevice(path);
|
|
1136
|
+
}
|
|
1137
|
+
finally {
|
|
1138
|
+
this.releaseIdentityProbeHandle(path);
|
|
1139
|
+
this.releaseNormalHandles(path);
|
|
1140
|
+
}
|
|
774
1141
|
this.deviceEndpoints.delete(path);
|
|
775
1142
|
});
|
|
776
1143
|
}
|
|
@@ -901,11 +1268,14 @@ class ElectronBleTransport {
|
|
|
901
1268
|
if (error.code === hdShared.HardwareErrorCode.BlePermissionError) {
|
|
902
1269
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePermissionError);
|
|
903
1270
|
}
|
|
1271
|
+
if (error.code === hdShared.HardwareErrorCode.BleLocationError) {
|
|
1272
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError);
|
|
1273
|
+
}
|
|
904
1274
|
}
|
|
905
1275
|
const errorMessage = error.message || String(error);
|
|
906
1276
|
const poweredOffMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BlePoweredOff];
|
|
907
1277
|
const unsupportedMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleUnsupported];
|
|
908
|
-
const permissionMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.
|
|
1278
|
+
const permissionMessage = hdShared.HardwareErrorCodeMessage[hdShared.HardwareErrorCode.BleLocationError];
|
|
909
1279
|
if (errorMessage.includes(poweredOffMessage) || errorMessage.includes('poweredOff')) {
|
|
910
1280
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BlePoweredOff);
|
|
911
1281
|
}
|
|
@@ -913,7 +1283,7 @@ class ElectronBleTransport {
|
|
|
913
1283
|
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleUnsupported);
|
|
914
1284
|
}
|
|
915
1285
|
if (errorMessage.includes(permissionMessage) || errorMessage.includes('unauthorized')) {
|
|
916
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
1286
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleLocationError);
|
|
917
1287
|
}
|
|
918
1288
|
}
|
|
919
1289
|
return error;
|
|
@@ -1174,10 +1544,12 @@ class ElectronBleTransport {
|
|
|
1174
1544
|
});
|
|
1175
1545
|
}
|
|
1176
1546
|
createProtocolMismatchError(expected) {
|
|
1177
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
1547
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
1548
|
+
expectedProtocol: expected,
|
|
1549
|
+
});
|
|
1178
1550
|
}
|
|
1179
1551
|
createProtocolDetectionError() {
|
|
1180
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
1552
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed);
|
|
1181
1553
|
}
|
|
1182
1554
|
clearProbeProtocol(uuid, protocol) {
|
|
1183
1555
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
|
@@ -1289,9 +1661,7 @@ class ElectronBleTransport {
|
|
|
1289
1661
|
(_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
|
|
1290
1662
|
this.resetProtocolV2Frames(uuid);
|
|
1291
1663
|
},
|
|
1292
|
-
shouldRethrow: error => expectedProtocol === 'V2' ||
|
|
1293
|
-
hdShared.isBleStaleBondHardwareError(error) ||
|
|
1294
|
-
transport.isProtocolV2LinkDisabledError(error),
|
|
1664
|
+
shouldRethrow: error => expectedProtocol === 'V2' || transport.isProtocolV2LinkDisabledError(error),
|
|
1295
1665
|
});
|
|
1296
1666
|
if (!detected) {
|
|
1297
1667
|
this.clearProbeProtocol(uuid, 'V2');
|