@onekeyfe/hd-transport-web-device 1.2.8-alpha.52 → 1.2.8-alpha.53
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-v2-timeout.test.ts +18 -5
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +296 -27
- package/dist/webusb.d.ts +17 -1
- 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 +290 -32
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,12 @@ 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();
|
|
335
485
|
if (e instanceof hdShared.HardwareError && e.errorCode === hdShared.HardwareErrorCode.BridgeNeedsPermission) {
|
|
336
486
|
throw e;
|
|
337
487
|
}
|
|
@@ -374,42 +524,68 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
374
524
|
connectToDevice(path, first) {
|
|
375
525
|
var _a;
|
|
376
526
|
return __awaiter(this, void 0, void 0, function* () {
|
|
527
|
+
this.assertRunning();
|
|
377
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);
|
|
378
537
|
if (!device.opened) {
|
|
538
|
+
openingHandles.add(device);
|
|
379
539
|
try {
|
|
380
|
-
yield device.open();
|
|
540
|
+
yield this.runHandleOperation(() => device.open());
|
|
381
541
|
}
|
|
382
542
|
catch (error) {
|
|
383
|
-
return this.throwWebUsbIoError(path, 'open', error);
|
|
543
|
+
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
544
|
+
}
|
|
545
|
+
finally {
|
|
546
|
+
openingHandles.delete(device);
|
|
384
547
|
}
|
|
385
548
|
}
|
|
386
549
|
if (first || this.deviceProtocol.get(path) !== 'V1') {
|
|
387
550
|
try {
|
|
388
|
-
yield device.reset();
|
|
551
|
+
yield this.runHandleOperation(() => device.reset());
|
|
389
552
|
}
|
|
390
553
|
catch (error) {
|
|
554
|
+
this.assertRunning();
|
|
391
555
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[WebUsbTransport] reset before claim failed, continuing:', error);
|
|
392
556
|
}
|
|
393
557
|
}
|
|
394
558
|
yield this.getConnectedDevices();
|
|
395
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);
|
|
396
568
|
if (!device.opened) {
|
|
569
|
+
openingHandles.add(device);
|
|
397
570
|
try {
|
|
398
|
-
yield device.open();
|
|
571
|
+
yield this.runHandleOperation(() => device.open());
|
|
399
572
|
}
|
|
400
573
|
catch (error) {
|
|
401
|
-
return this.throwWebUsbIoError(path, 'open', error);
|
|
574
|
+
return yield this.throwWebUsbIoError(path, 'open', error);
|
|
575
|
+
}
|
|
576
|
+
finally {
|
|
577
|
+
openingHandles.delete(device);
|
|
402
578
|
}
|
|
403
579
|
}
|
|
404
580
|
if (first ||
|
|
405
581
|
!device.configuration ||
|
|
406
582
|
device.configuration.configurationValue !== this.configurationId) {
|
|
407
|
-
yield device.selectConfiguration(this.configurationId);
|
|
583
|
+
yield this.runHandleOperation(() => device.selectConfiguration(this.configurationId));
|
|
408
584
|
}
|
|
409
585
|
const endpoints = this.discoverEndpoints(device);
|
|
410
586
|
this.deviceEndpoints.set(path, endpoints);
|
|
411
587
|
try {
|
|
412
|
-
yield device.claimInterface(endpoints.interfaceNumber);
|
|
588
|
+
yield this.runHandleOperation(() => device.claimInterface(endpoints.interfaceNumber));
|
|
413
589
|
}
|
|
414
590
|
catch (error) {
|
|
415
591
|
return this.throwWebUsbIoError(path, 'claimInterface', error);
|
|
@@ -418,40 +594,104 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
418
594
|
yield this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
419
595
|
});
|
|
420
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
|
+
}
|
|
421
650
|
closeOpenDevice(path) {
|
|
422
651
|
var _a, _b, _c, _d;
|
|
423
652
|
return __awaiter(this, void 0, void 0, function* () {
|
|
424
|
-
|
|
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;
|
|
425
658
|
if (!(current === null || current === void 0 ? void 0 : current.opened))
|
|
426
659
|
return;
|
|
660
|
+
this.assertHandleAvailable(current);
|
|
427
661
|
const endpoints = this.deviceEndpoints.get(path);
|
|
428
662
|
const ifaceNum = (_b = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _b !== void 0 ? _b : this.interfaceId;
|
|
429
663
|
try {
|
|
430
|
-
yield current.releaseInterface(ifaceNum);
|
|
664
|
+
yield this.runHandleOperation(() => current.releaseInterface(ifaceNum));
|
|
431
665
|
}
|
|
432
666
|
catch (error) {
|
|
433
667
|
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[WebUsbTransport] releaseInterface before reconnect failed:', error);
|
|
434
668
|
}
|
|
669
|
+
this.assertHandleAvailable(current);
|
|
435
670
|
try {
|
|
436
|
-
yield current.close();
|
|
671
|
+
yield this.runHandleOperation(() => current.close());
|
|
437
672
|
}
|
|
438
673
|
catch (error) {
|
|
439
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;
|
|
440
677
|
}
|
|
441
678
|
});
|
|
442
679
|
}
|
|
443
680
|
clearEndpointHalt(device, direction, endpointNumber) {
|
|
444
681
|
var _a;
|
|
445
682
|
return __awaiter(this, void 0, void 0, function* () {
|
|
683
|
+
this.assertHandleAvailable(device);
|
|
446
684
|
try {
|
|
447
|
-
yield device.clearHalt(direction, endpointNumber);
|
|
685
|
+
yield this.runHandleOperation(() => device.clearHalt(direction, endpointNumber));
|
|
448
686
|
}
|
|
449
687
|
catch (error) {
|
|
688
|
+
this.assertRunning();
|
|
450
689
|
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] clearHalt ${direction} endpoint ${endpointNumber} failed, continuing:`, error);
|
|
451
690
|
}
|
|
452
691
|
});
|
|
453
692
|
}
|
|
454
693
|
assertAcquired(path) {
|
|
694
|
+
this.assertRunning();
|
|
455
695
|
if (!this.acquiredPaths.has(path)) {
|
|
456
696
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
|
|
457
697
|
}
|
|
@@ -508,7 +748,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
508
748
|
throwWebUsbIoError(path, operation, error) {
|
|
509
749
|
var _a;
|
|
510
750
|
return __awaiter(this, void 0, void 0, function* () {
|
|
511
|
-
|
|
751
|
+
this.assertRunning();
|
|
752
|
+
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
512
753
|
throw error;
|
|
513
754
|
if (error instanceof transport.ProtocolV2LinkError && error.code !== 'io')
|
|
514
755
|
throw error;
|
|
@@ -542,7 +783,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
542
783
|
return /LIBUSB_ERROR_ACCESS|\b(?:access|permission) denied\b/i.test(message);
|
|
543
784
|
}
|
|
544
785
|
isRetryablePacketIoError(error) {
|
|
545
|
-
if (this.
|
|
786
|
+
if (this.stopped)
|
|
787
|
+
return false;
|
|
788
|
+
if (this.isHandleOwnershipError(error) || this.isWebUsbIoHardwareError(error))
|
|
546
789
|
return false;
|
|
547
790
|
const message = this.getErrorMessage(error).toLowerCase();
|
|
548
791
|
if (this.isWebUsbPermissionDeniedMessage(message))
|
|
@@ -561,21 +804,27 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
561
804
|
this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
562
805
|
this.staleProtocolPaths.add(path);
|
|
563
806
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
807
|
+
this.assertRunning();
|
|
564
808
|
try {
|
|
565
809
|
const currentDevice = yield this.findDevice(path);
|
|
810
|
+
this.assertHandleAvailable(currentDevice);
|
|
566
811
|
if (currentDevice.opened) {
|
|
567
812
|
const endpoints = this.deviceEndpoints.get(path);
|
|
568
813
|
const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
|
|
569
814
|
try {
|
|
570
|
-
yield currentDevice.releaseInterface(ifaceNum);
|
|
815
|
+
yield this.runHandleOperation(() => currentDevice.releaseInterface(ifaceNum));
|
|
571
816
|
}
|
|
572
817
|
catch (releaseError) {
|
|
573
818
|
this.Log.debug('[WebUsbTransport] releaseInterface before retry error:', releaseError);
|
|
574
819
|
}
|
|
575
|
-
|
|
820
|
+
this.assertHandleAvailable(currentDevice);
|
|
821
|
+
yield this.runHandleOperation(() => currentDevice.close());
|
|
576
822
|
}
|
|
577
823
|
}
|
|
578
824
|
catch (closeError) {
|
|
825
|
+
this.assertRunning();
|
|
826
|
+
if (this.isHandleOwnershipError(closeError))
|
|
827
|
+
throw closeError;
|
|
579
828
|
this.Log.debug('[WebUsbTransport] close device before retry error:', closeError);
|
|
580
829
|
}
|
|
581
830
|
yield this.getConnectedDevices();
|
|
@@ -617,6 +866,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
617
866
|
yield this.reconnectForPacketIoRetry(path, 'out', attempt, error);
|
|
618
867
|
}
|
|
619
868
|
catch (reconnectError) {
|
|
869
|
+
this.assertRunning();
|
|
870
|
+
if (this.isHandleOwnershipError(reconnectError))
|
|
871
|
+
throw reconnectError;
|
|
620
872
|
lastError = reconnectError;
|
|
621
873
|
this.Log.debug(`[WebUsbTransport] transferout reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
622
874
|
}
|
|
@@ -635,7 +887,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
635
887
|
const endpoints = this.deviceEndpoints.get(path);
|
|
636
888
|
const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
637
889
|
const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
|
|
890
|
+
this.assertHandleAvailable(device);
|
|
638
891
|
const result = yield device.transferOut(endpointOut, transferBuffer);
|
|
892
|
+
this.assertHandleAvailable(device);
|
|
639
893
|
if (result.status !== 'ok') {
|
|
640
894
|
return this.throwWebUsbIoError(path, 'transferOut', new Error(`transferOut status: ${String(result.status)}`));
|
|
641
895
|
}
|
|
@@ -656,7 +910,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
656
910
|
}
|
|
657
911
|
const endpoints = this.deviceEndpoints.get(path);
|
|
658
912
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
913
|
+
this.assertHandleAvailable(device);
|
|
659
914
|
const result = yield device.transferIn(endpointIn, length);
|
|
915
|
+
this.assertHandleAvailable(device);
|
|
660
916
|
return this.getTransferInData(result);
|
|
661
917
|
}
|
|
662
918
|
catch (error) {
|
|
@@ -672,6 +928,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
672
928
|
yield this.reconnectForPacketIoRetry(path, 'in', attempt, error);
|
|
673
929
|
}
|
|
674
930
|
catch (reconnectError) {
|
|
931
|
+
this.assertRunning();
|
|
932
|
+
if (this.isHandleOwnershipError(reconnectError))
|
|
933
|
+
throw reconnectError;
|
|
675
934
|
lastError = reconnectError;
|
|
676
935
|
this.Log.debug(`[WebUsbTransport] transferin reconnect failed on retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(reconnectError)}`);
|
|
677
936
|
}
|
|
@@ -689,7 +948,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
689
948
|
}
|
|
690
949
|
const endpoints = this.deviceEndpoints.get(path);
|
|
691
950
|
const endpointIn = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointIn) !== null && _a !== void 0 ? _a : this.endpointId;
|
|
951
|
+
this.assertHandleAvailable(device);
|
|
692
952
|
const result = yield device.transferIn(endpointIn, length);
|
|
953
|
+
this.assertHandleAvailable(device);
|
|
693
954
|
return this.getTransferInData(result);
|
|
694
955
|
});
|
|
695
956
|
}
|
|
@@ -749,10 +1010,10 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
749
1010
|
return false;
|
|
750
1011
|
}
|
|
751
1012
|
try {
|
|
752
|
-
yield this.callProtocolV1(path, 'Initialize', {}, {
|
|
1013
|
+
const response = yield this.callProtocolV1(path, this.identityProbe ? 'GetFeatures' : 'Initialize', {}, {
|
|
753
1014
|
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
754
1015
|
});
|
|
755
|
-
return
|
|
1016
|
+
return !this.identityProbe || response.type === 'Features';
|
|
756
1017
|
}
|
|
757
1018
|
catch (error) {
|
|
758
1019
|
if (this.isWebUsbIoHardwareError(error))
|
|
@@ -869,8 +1130,14 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
869
1130
|
release(path) {
|
|
870
1131
|
return __awaiter(this, void 0, void 0, function* () {
|
|
871
1132
|
this.acquiredPaths.delete(path);
|
|
872
|
-
|
|
873
|
-
|
|
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
|
+
}
|
|
874
1141
|
this.deviceEndpoints.delete(path);
|
|
875
1142
|
});
|
|
876
1143
|
}
|
|
@@ -1277,10 +1544,12 @@ class ElectronBleTransport {
|
|
|
1277
1544
|
});
|
|
1278
1545
|
}
|
|
1279
1546
|
createProtocolMismatchError(expected) {
|
|
1280
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
1547
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
1548
|
+
expectedProtocol: expected,
|
|
1549
|
+
});
|
|
1281
1550
|
}
|
|
1282
1551
|
createProtocolDetectionError() {
|
|
1283
|
-
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.
|
|
1552
|
+
return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportProtocolProbeFailed);
|
|
1284
1553
|
}
|
|
1285
1554
|
clearProbeProtocol(uuid, protocol) {
|
|
1286
1555
|
if (this.deviceProtocol.get(uuid) === protocol) {
|
package/dist/webusb.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface DeviceInfo extends OneKeyDeviceInfoBase {
|
|
|
9
9
|
protocolType?: ProtocolType;
|
|
10
10
|
}
|
|
11
11
|
export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
12
|
+
private readonly identityProbe;
|
|
12
13
|
messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
13
14
|
messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
14
15
|
private protocolV2SchemaSource;
|
|
@@ -17,6 +18,11 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
17
18
|
private deviceProtocolHints;
|
|
18
19
|
private staleProtocolPaths;
|
|
19
20
|
private acquiredPaths;
|
|
21
|
+
private identityProbeDevices;
|
|
22
|
+
private normalDevices;
|
|
23
|
+
private pendingHandleOperations;
|
|
24
|
+
private stopPromise?;
|
|
25
|
+
private identityProbeCleanupPaths;
|
|
20
26
|
private probedDeviceObjects;
|
|
21
27
|
private deviceEndpoints;
|
|
22
28
|
name: string;
|
|
@@ -29,8 +35,12 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
29
35
|
configurationId: number;
|
|
30
36
|
endpointId: number;
|
|
31
37
|
interfaceId: number;
|
|
32
|
-
constructor();
|
|
38
|
+
constructor(identityProbe?: boolean);
|
|
33
39
|
init(logger: any, emitter?: EventEmitter): void;
|
|
40
|
+
stop(): Promise<void>;
|
|
41
|
+
private stopResources;
|
|
42
|
+
private assertRunning;
|
|
43
|
+
private runHandleOperation;
|
|
34
44
|
emitDeviceDisconnect(path: string, device?: USBDevice): void;
|
|
35
45
|
markProtocolStale(path: string): void;
|
|
36
46
|
configure(signedData: any): void;
|
|
@@ -47,6 +57,12 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
47
57
|
connect(path: string, first: boolean): Promise<undefined>;
|
|
48
58
|
private discoverEndpoints;
|
|
49
59
|
connectToDevice(path: string, first: boolean): Promise<undefined>;
|
|
60
|
+
private assertHandleAvailable;
|
|
61
|
+
private claimNormalHandle;
|
|
62
|
+
private releaseNormalHandles;
|
|
63
|
+
private isHandleOwnershipError;
|
|
64
|
+
private claimIdentityProbeHandle;
|
|
65
|
+
private releaseIdentityProbeHandle;
|
|
50
66
|
private closeOpenDevice;
|
|
51
67
|
private clearEndpointHalt;
|
|
52
68
|
private assertAcquired;
|
package/dist/webusb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";;AACA,OAAO,SAAS,EAAE,EAQhB,0BAA0B,EAG3B,MAAM,wBAAwB,CAAC;AAahC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
|