@onekeyfe/hd-transport-web-device 1.2.0-alpha.134 → 1.2.0-alpha.135
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/dist/index.d.ts +0 -26
- package/dist/index.js +9 -49
- package/dist/webusb.d.ts +0 -4
- package/dist/webusb.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/webusb.ts +12 -100
- package/__tests__/webusb-protocol-cache.test.ts +0 -204
package/dist/index.d.ts
CHANGED
|
@@ -20,13 +20,6 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
20
20
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
21
21
|
private deviceProtocol;
|
|
22
22
|
private deviceProtocolHints;
|
|
23
|
-
/**
|
|
24
|
-
* Paths whose cached protocol must be re-probed on the next acquire (a USB
|
|
25
|
-
* disconnect was seen, or a transfer-level reconnect happened mid-call).
|
|
26
|
-
*/
|
|
27
|
-
private staleProtocolPaths;
|
|
28
|
-
/** Paths currently acquired (between a successful acquire() and its release()). */
|
|
29
|
-
private acquiredPaths;
|
|
30
23
|
/** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
|
|
31
24
|
private deviceEndpoints;
|
|
32
25
|
name: string;
|
|
@@ -47,17 +40,6 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
47
40
|
* Initialize WebUSB transport
|
|
48
41
|
*/
|
|
49
42
|
init(logger: any): void;
|
|
50
|
-
/**
|
|
51
|
-
* Protocol type is a property of the physical device keyed by USB serial number.
|
|
52
|
-
* It can only change across a device reboot (e.g. normal ↔ bootloader mode), and
|
|
53
|
-
* a reboot always surfaces as a USB disconnect. Disconnects (and transfer-level
|
|
54
|
-
* reconnects, which cover a missed disconnect event) only MARK the cached probe
|
|
55
|
-
* result stale instead of deleting it: an in-flight session keeps using the old
|
|
56
|
-
* value so the transfer-level reconnect retries can absorb a transient
|
|
57
|
-
* re-enumeration exactly as they did before the cache existed, while the next
|
|
58
|
-
* acquire re-probes from scratch.
|
|
59
|
-
*/
|
|
60
|
-
markProtocolStale(path: string): void;
|
|
61
43
|
/**
|
|
62
44
|
* Configure Protocol V1 protobuf schema (legacy chunked 0x3F framing).
|
|
63
45
|
*/
|
|
@@ -115,14 +97,6 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
|
115
97
|
connectToDevice(path: string, first: boolean): Promise<void>;
|
|
116
98
|
private closeOpenDevice;
|
|
117
99
|
private clearEndpointHalt;
|
|
118
|
-
/**
|
|
119
|
-
* With the protocol cache surviving release(), deviceProtocol presence no
|
|
120
|
-
* longer implies an active session. Guard call()/post() explicitly so a
|
|
121
|
-
* post-release straggler fails fast instead of silently reopening the device
|
|
122
|
-
* and driving it outside any session (pre-cache behavior: the deleted
|
|
123
|
-
* protocol entry produced the same fail-fast).
|
|
124
|
-
*/
|
|
125
|
-
private assertAcquired;
|
|
126
100
|
post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
127
101
|
private getErrorMessage;
|
|
128
102
|
private isRetryablePacketIoError;
|
package/dist/index.js
CHANGED
|
@@ -57,20 +57,6 @@ const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
|
57
57
|
function inferProtocolHintFromDeviceName$1(name) {
|
|
58
58
|
return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
|
|
59
59
|
}
|
|
60
|
-
let activeWebUsbTransport;
|
|
61
|
-
let usbDisconnectListenerAttached = false;
|
|
62
|
-
function attachUsbDisconnectListener(usb) {
|
|
63
|
-
if (usbDisconnectListenerAttached)
|
|
64
|
-
return;
|
|
65
|
-
usbDisconnectListenerAttached = true;
|
|
66
|
-
usb.addEventListener('disconnect', event => {
|
|
67
|
-
var _a;
|
|
68
|
-
const serial = (_a = event.device) === null || _a === void 0 ? void 0 : _a.serialNumber;
|
|
69
|
-
if (typeof serial !== 'string' || serial.length === 0)
|
|
70
|
-
return;
|
|
71
|
-
activeWebUsbTransport === null || activeWebUsbTransport === void 0 ? void 0 : activeWebUsbTransport.markProtocolStale(serial);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
60
|
class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
75
61
|
constructor() {
|
|
76
62
|
super({
|
|
@@ -80,8 +66,6 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
80
66
|
});
|
|
81
67
|
this.deviceProtocol = new Map();
|
|
82
68
|
this.deviceProtocolHints = new Map();
|
|
83
|
-
this.staleProtocolPaths = new Set();
|
|
84
|
-
this.acquiredPaths = new Set();
|
|
85
69
|
this.deviceEndpoints = new Map();
|
|
86
70
|
this.name = 'WebUsbTransport';
|
|
87
71
|
this.stopped = false;
|
|
@@ -98,11 +82,6 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
98
82
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'WebUSB is not supported by current browsers');
|
|
99
83
|
}
|
|
100
84
|
this.usb = usb;
|
|
101
|
-
activeWebUsbTransport = this;
|
|
102
|
-
attachUsbDisconnectListener(usb);
|
|
103
|
-
}
|
|
104
|
-
markProtocolStale(path) {
|
|
105
|
-
this.staleProtocolPaths.add(path);
|
|
106
85
|
}
|
|
107
86
|
configure(signedData) {
|
|
108
87
|
const messages = parseConfigure$1(signedData);
|
|
@@ -177,30 +156,18 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
177
156
|
yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
178
157
|
yield this.closeOpenDevice(input.path);
|
|
179
158
|
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
this.deviceProtocol.delete(input.path);
|
|
187
|
-
}
|
|
188
|
-
if (!this.deviceProtocol.has(input.path)) {
|
|
189
|
-
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
190
|
-
const protocolHint = input.expectedProtocol
|
|
191
|
-
? undefined
|
|
192
|
-
: (_d = (_c = input.protocolHint) !== null && _c !== void 0 ? _c : this.deviceProtocolHints.get(input.path)) !== null && _d !== void 0 ? _d : inferProtocolHintFromDeviceName$1(deviceName);
|
|
193
|
-
if (protocolHint) {
|
|
194
|
-
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
195
|
-
}
|
|
196
|
-
yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
159
|
+
const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
|
|
160
|
+
const protocolHint = input.expectedProtocol
|
|
161
|
+
? undefined
|
|
162
|
+
: (_d = (_c = input.protocolHint) !== null && _c !== void 0 ? _c : this.deviceProtocolHints.get(input.path)) !== null && _d !== void 0 ? _d : inferProtocolHintFromDeviceName$1(deviceName);
|
|
163
|
+
if (protocolHint) {
|
|
164
|
+
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
197
165
|
}
|
|
198
|
-
this.
|
|
166
|
+
yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
199
167
|
return yield Promise.resolve(input.path);
|
|
200
168
|
}
|
|
201
169
|
catch (e) {
|
|
202
170
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
203
|
-
this.acquiredPaths.delete(input.path);
|
|
204
171
|
yield this.closeOpenDevice(input.path);
|
|
205
172
|
throw e;
|
|
206
173
|
}
|
|
@@ -384,14 +351,8 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
384
351
|
}
|
|
385
352
|
});
|
|
386
353
|
}
|
|
387
|
-
assertAcquired(path) {
|
|
388
|
-
if (!this.acquiredPaths.has(path)) {
|
|
389
|
-
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device is not acquired for ${path}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
354
|
post(session, name, data) {
|
|
393
355
|
return __awaiter(this, void 0, void 0, function* () {
|
|
394
|
-
this.assertAcquired(session);
|
|
395
356
|
if (this.deviceProtocol.get(session) === 'V2') {
|
|
396
357
|
yield this.sendProtocolV2UsbFlowControl(session, name, data);
|
|
397
358
|
return;
|
|
@@ -424,7 +385,6 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
424
385
|
var _a;
|
|
425
386
|
return __awaiter(this, void 0, void 0, function* () {
|
|
426
387
|
this.Log.debug(`[WebUsbTransport] transfer${direction} failed, retry ${attempt}/${PACKET_IO_MAX_RETRIES}: ${this.getErrorMessage(error)}`);
|
|
427
|
-
this.staleProtocolPaths.add(path);
|
|
428
388
|
yield hdShared.wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
429
389
|
try {
|
|
430
390
|
const currentDevice = yield this.findDevice(path);
|
|
@@ -639,7 +599,6 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
639
599
|
if (this.messages == null) {
|
|
640
600
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
|
|
641
601
|
}
|
|
642
|
-
this.assertAcquired(path);
|
|
643
602
|
const device = yield this.findDevice(path);
|
|
644
603
|
if (!device) {
|
|
645
604
|
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
|
|
@@ -719,9 +678,10 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
|
|
|
719
678
|
}
|
|
720
679
|
release(path) {
|
|
721
680
|
return __awaiter(this, void 0, void 0, function* () {
|
|
722
|
-
this.acquiredPaths.delete(path);
|
|
723
681
|
yield this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
724
682
|
yield this.closeOpenDevice(path);
|
|
683
|
+
this.deviceProtocol.delete(path);
|
|
684
|
+
this.deviceProtocolHints.delete(path);
|
|
725
685
|
this.deviceEndpoints.delete(path);
|
|
726
686
|
});
|
|
727
687
|
}
|
package/dist/webusb.d.ts
CHANGED
|
@@ -12,8 +12,6 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
12
12
|
private protocolV2SchemaSource;
|
|
13
13
|
private deviceProtocol;
|
|
14
14
|
private deviceProtocolHints;
|
|
15
|
-
private staleProtocolPaths;
|
|
16
|
-
private acquiredPaths;
|
|
17
15
|
private deviceEndpoints;
|
|
18
16
|
name: string;
|
|
19
17
|
stopped: boolean;
|
|
@@ -26,7 +24,6 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
26
24
|
interfaceId: number;
|
|
27
25
|
constructor();
|
|
28
26
|
init(logger: any): void;
|
|
29
|
-
markProtocolStale(path: string): void;
|
|
30
27
|
configure(signedData: any): void;
|
|
31
28
|
configureProtocolV2(signedData: any): void;
|
|
32
29
|
promptDeviceAccess(): Promise<USBDevice | null>;
|
|
@@ -43,7 +40,6 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
43
40
|
connectToDevice(path: string, first: boolean): Promise<void>;
|
|
44
41
|
private closeOpenDevice;
|
|
45
42
|
private clearEndpointHalt;
|
|
46
|
-
private assertAcquired;
|
|
47
43
|
post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
48
44
|
private getErrorMessage;
|
|
49
45
|
private isRetryablePacketIoError;
|
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,EAE3B,MAAM,wBAAwB,CAAC;AAUhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AA0BhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;
|
|
1
|
+
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-web-device",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.135",
|
|
4
4
|
"author": "OneKey",
|
|
5
5
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"lint:fix": "eslint . --fix"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
25
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
24
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.135",
|
|
25
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.135"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.
|
|
28
|
+
"@onekeyfe/hd-transport-electron": "1.2.0-alpha.135",
|
|
29
29
|
"@types/w3c-web-usb": "^1.0.6",
|
|
30
30
|
"@types/web-bluetooth": "^0.0.17"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "321e900381de2c1a9c583e90e8abfc2f8dab8266"
|
|
33
33
|
}
|
package/src/webusb.ts
CHANGED
|
@@ -69,26 +69,6 @@ interface TransferCancelToken {
|
|
|
69
69
|
cancelled: boolean;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
/**
|
|
73
|
-
* The navigator.usb disconnect listener is module-scoped and attached at most
|
|
74
|
-
* once: listeners on navigator.usb are global and never garbage collected, so a
|
|
75
|
-
* per-instance listener would retain every transport instance created across
|
|
76
|
-
* SDK re-initializations. Events are routed to the most recently initialized
|
|
77
|
-
* transport instance instead.
|
|
78
|
-
*/
|
|
79
|
-
let activeWebUsbTransport: WebUsbTransport | undefined;
|
|
80
|
-
let usbDisconnectListenerAttached = false;
|
|
81
|
-
|
|
82
|
-
function attachUsbDisconnectListener(usb: USB) {
|
|
83
|
-
if (usbDisconnectListenerAttached) return;
|
|
84
|
-
usbDisconnectListenerAttached = true;
|
|
85
|
-
usb.addEventListener('disconnect', event => {
|
|
86
|
-
const serial = event.device?.serialNumber;
|
|
87
|
-
if (typeof serial !== 'string' || serial.length === 0) return;
|
|
88
|
-
activeWebUsbTransport?.markProtocolStale(serial);
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
72
|
export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
93
73
|
messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
94
74
|
|
|
@@ -102,15 +82,6 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
102
82
|
|
|
103
83
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
104
84
|
|
|
105
|
-
/**
|
|
106
|
-
* Paths whose cached protocol must be re-probed on the next acquire (a USB
|
|
107
|
-
* disconnect was seen, or a transfer-level reconnect happened mid-call).
|
|
108
|
-
*/
|
|
109
|
-
private staleProtocolPaths: Set<string> = new Set();
|
|
110
|
-
|
|
111
|
-
/** Paths currently acquired (between a successful acquire() and its release()). */
|
|
112
|
-
private acquiredPaths: Set<string> = new Set();
|
|
113
|
-
|
|
114
85
|
/** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
|
|
115
86
|
private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
|
|
116
87
|
|
|
@@ -158,22 +129,6 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
158
129
|
);
|
|
159
130
|
}
|
|
160
131
|
this.usb = usb;
|
|
161
|
-
activeWebUsbTransport = this;
|
|
162
|
-
attachUsbDisconnectListener(usb);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Protocol type is a property of the physical device keyed by USB serial number.
|
|
167
|
-
* It can only change across a device reboot (e.g. normal ↔ bootloader mode), and
|
|
168
|
-
* a reboot always surfaces as a USB disconnect. Disconnects (and transfer-level
|
|
169
|
-
* reconnects, which cover a missed disconnect event) only MARK the cached probe
|
|
170
|
-
* result stale instead of deleting it: an in-flight session keeps using the old
|
|
171
|
-
* value so the transfer-level reconnect retries can absorb a transient
|
|
172
|
-
* re-enumeration exactly as they did before the cache existed, while the next
|
|
173
|
-
* acquire re-probes from scratch.
|
|
174
|
-
*/
|
|
175
|
-
markProtocolStale(path: string) {
|
|
176
|
-
this.staleProtocolPaths.add(path);
|
|
177
132
|
}
|
|
178
133
|
|
|
179
134
|
/**
|
|
@@ -276,36 +231,20 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
276
231
|
await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
277
232
|
await this.closeOpenDevice(input.path);
|
|
278
233
|
await this.connect(input.path ?? '', true);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
// the cache is stale — drop it and re-probe on the wire below.
|
|
289
|
-
this.deviceProtocol.delete(input.path);
|
|
290
|
-
}
|
|
291
|
-
if (!this.deviceProtocol.has(input.path)) {
|
|
292
|
-
const deviceName = this.deviceList.find(device => device.path === input.path)?.device
|
|
293
|
-
.productName;
|
|
294
|
-
const protocolHint = input.expectedProtocol
|
|
295
|
-
? undefined
|
|
296
|
-
: input.protocolHint ??
|
|
297
|
-
this.deviceProtocolHints.get(input.path) ??
|
|
298
|
-
inferProtocolHintFromDeviceName(deviceName);
|
|
299
|
-
if (protocolHint) {
|
|
300
|
-
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
301
|
-
}
|
|
302
|
-
await this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
234
|
+
const deviceName = this.deviceList.find(device => device.path === input.path)?.device
|
|
235
|
+
.productName;
|
|
236
|
+
const protocolHint = input.expectedProtocol
|
|
237
|
+
? undefined
|
|
238
|
+
: input.protocolHint ??
|
|
239
|
+
this.deviceProtocolHints.get(input.path) ??
|
|
240
|
+
inferProtocolHintFromDeviceName(deviceName);
|
|
241
|
+
if (protocolHint) {
|
|
242
|
+
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
303
243
|
}
|
|
304
|
-
this.
|
|
244
|
+
await this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
|
|
305
245
|
return await Promise.resolve(input.path);
|
|
306
246
|
} catch (e) {
|
|
307
247
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
308
|
-
this.acquiredPaths.delete(input.path);
|
|
309
248
|
await this.closeOpenDevice(input.path);
|
|
310
249
|
throw e;
|
|
311
250
|
}
|
|
@@ -541,24 +480,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
541
480
|
}
|
|
542
481
|
}
|
|
543
482
|
|
|
544
|
-
/**
|
|
545
|
-
* With the protocol cache surviving release(), deviceProtocol presence no
|
|
546
|
-
* longer implies an active session. Guard call()/post() explicitly so a
|
|
547
|
-
* post-release straggler fails fast instead of silently reopening the device
|
|
548
|
-
* and driving it outside any session (pre-cache behavior: the deleted
|
|
549
|
-
* protocol entry produced the same fail-fast).
|
|
550
|
-
*/
|
|
551
|
-
private assertAcquired(path: string) {
|
|
552
|
-
if (!this.acquiredPaths.has(path)) {
|
|
553
|
-
throw ERRORS.TypedError(
|
|
554
|
-
HardwareErrorCode.RuntimeError,
|
|
555
|
-
`Device is not acquired for ${path}`
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
483
|
async post(session: string, name: string, data: Record<string, unknown>) {
|
|
561
|
-
this.assertAcquired(session);
|
|
562
484
|
if (this.deviceProtocol.get(session) === 'V2') {
|
|
563
485
|
await this.sendProtocolV2UsbFlowControl(session, name, data);
|
|
564
486
|
return;
|
|
@@ -600,11 +522,6 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
600
522
|
error
|
|
601
523
|
)}`
|
|
602
524
|
);
|
|
603
|
-
// The device dropped off the bus mid-call and may have rebooted into a
|
|
604
|
-
// different mode. The in-flight retry keeps the known protocol, but the
|
|
605
|
-
// next acquire must re-probe — this also self-heals a stale cache when the
|
|
606
|
-
// USB disconnect event itself was missed.
|
|
607
|
-
this.staleProtocolPaths.add(path);
|
|
608
525
|
await wait(attempt * PACKET_IO_RETRY_DELAY);
|
|
609
526
|
|
|
610
527
|
try {
|
|
@@ -838,7 +755,6 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
838
755
|
if (this.messages == null) {
|
|
839
756
|
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
840
757
|
}
|
|
841
|
-
this.assertAcquired(path);
|
|
842
758
|
|
|
843
759
|
const device = await this.findDevice(path);
|
|
844
760
|
if (!device) {
|
|
@@ -955,14 +871,10 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
|
|
|
955
871
|
* Release device
|
|
956
872
|
*/
|
|
957
873
|
async release(path: string) {
|
|
958
|
-
this.acquiredPaths.delete(path);
|
|
959
874
|
await this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
960
875
|
await this.closeOpenDevice(path);
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
// V2 entries are still dropped by onProtocolV2UsbLinkInvalidated via the link
|
|
964
|
-
// invalidation above, and any entry is re-probed after a USB disconnect or a
|
|
965
|
-
// transfer-level reconnect (see markProtocolStale).
|
|
876
|
+
this.deviceProtocol.delete(path);
|
|
877
|
+
this.deviceProtocolHints.delete(path);
|
|
966
878
|
this.deviceEndpoints.delete(path);
|
|
967
879
|
}
|
|
968
880
|
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import transport from '@onekeyfe/hd-transport';
|
|
2
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
|
-
|
|
4
|
-
import WebUsbTransport from '../src/webusb';
|
|
5
|
-
|
|
6
|
-
const schema = {
|
|
7
|
-
nested: {
|
|
8
|
-
Ping: { fields: { message: { type: 'string', id: 1 } } },
|
|
9
|
-
Success: { fields: { message: { type: 'string', id: 1 } } },
|
|
10
|
-
MessageType: {
|
|
11
|
-
values: {
|
|
12
|
-
MessageType_Ping: 60206,
|
|
13
|
-
MessageType_Success: 60207,
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
function buildAcquirableTransport() {
|
|
20
|
-
const webusb = new WebUsbTransport() as any;
|
|
21
|
-
webusb.Log = { debug: jest.fn() };
|
|
22
|
-
webusb.rotateProtocolV2UsbGeneration = jest.fn().mockResolvedValue(undefined);
|
|
23
|
-
webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
|
|
24
|
-
webusb.connect = jest.fn().mockResolvedValue(undefined);
|
|
25
|
-
return webusb;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe('WebUsbTransport protocol probe cache', () => {
|
|
29
|
-
test('acquire skips the wire probe when the protocol is already cached', async () => {
|
|
30
|
-
const webusb = buildAcquirableTransport();
|
|
31
|
-
const path = 'pro-webusb';
|
|
32
|
-
webusb.deviceProtocol.set(path, 'V1');
|
|
33
|
-
webusb.detectProtocol = jest.fn();
|
|
34
|
-
|
|
35
|
-
await expect(webusb.acquire({ path, expectedProtocol: 'V1' })).resolves.toBe(path);
|
|
36
|
-
|
|
37
|
-
expect(webusb.detectProtocol).not.toHaveBeenCalled();
|
|
38
|
-
expect(webusb.deviceProtocol.get(path)).toBe('V1');
|
|
39
|
-
expect(webusb.acquiredPaths.has(path)).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('acquire re-probes when the caller expects a different protocol than cached', async () => {
|
|
43
|
-
const webusb = buildAcquirableTransport();
|
|
44
|
-
const path = 'pro-webusb';
|
|
45
|
-
webusb.deviceProtocol.set(path, 'V1');
|
|
46
|
-
webusb.detectProtocol = jest.fn().mockImplementation((p: string) => {
|
|
47
|
-
webusb.deviceProtocol.set(p, 'V2');
|
|
48
|
-
return Promise.resolve('V2');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
await expect(webusb.acquire({ path, expectedProtocol: 'V2' })).resolves.toBe(path);
|
|
52
|
-
|
|
53
|
-
expect(webusb.detectProtocol).toHaveBeenCalledWith(path, 'V2', undefined);
|
|
54
|
-
expect(webusb.deviceProtocol.get(path)).toBe('V2');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('acquire probes when nothing is cached for the path', async () => {
|
|
58
|
-
const webusb = buildAcquirableTransport();
|
|
59
|
-
const path = 'pro-webusb';
|
|
60
|
-
webusb.detectProtocol = jest.fn().mockResolvedValue('V1');
|
|
61
|
-
|
|
62
|
-
await expect(webusb.acquire({ path, expectedProtocol: 'V1' })).resolves.toBe(path);
|
|
63
|
-
|
|
64
|
-
expect(webusb.detectProtocol).toHaveBeenCalledWith(path, 'V1', undefined);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('acquire re-probes a stale-marked path even when a protocol is cached', async () => {
|
|
68
|
-
const webusb = buildAcquirableTransport();
|
|
69
|
-
const path = 'pro-webusb';
|
|
70
|
-
webusb.deviceProtocol.set(path, 'V1');
|
|
71
|
-
webusb.markProtocolStale(path);
|
|
72
|
-
webusb.detectProtocol = jest.fn().mockImplementation((p: string) => {
|
|
73
|
-
webusb.deviceProtocol.set(p, 'V1');
|
|
74
|
-
return Promise.resolve('V1');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
await expect(webusb.acquire({ path, expectedProtocol: 'V1' })).resolves.toBe(path);
|
|
78
|
-
|
|
79
|
-
expect(webusb.detectProtocol).toHaveBeenCalledTimes(1);
|
|
80
|
-
expect(webusb.staleProtocolPaths.has(path)).toBe(false);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('release keeps the cached V1 protocol so the next acquire can reuse it', async () => {
|
|
84
|
-
const webusb = new WebUsbTransport() as any;
|
|
85
|
-
const path = 'pro-webusb';
|
|
86
|
-
webusb.deviceProtocol.set(path, 'V1');
|
|
87
|
-
webusb.deviceProtocolHints.set(path, 'V2');
|
|
88
|
-
webusb.deviceEndpoints.set(path, { interfaceNumber: 0, endpointIn: 1, endpointOut: 1 });
|
|
89
|
-
webusb.acquiredPaths.add(path);
|
|
90
|
-
webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
|
|
91
|
-
|
|
92
|
-
await webusb.release(path);
|
|
93
|
-
|
|
94
|
-
expect(webusb.deviceProtocol.get(path)).toBe('V1');
|
|
95
|
-
expect(webusb.deviceProtocolHints.get(path)).toBe('V2');
|
|
96
|
-
expect(webusb.deviceEndpoints.has(path)).toBe(false);
|
|
97
|
-
expect(webusb.acquiredPaths.has(path)).toBe(false);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('release still drops a cached V2 protocol through link invalidation', async () => {
|
|
101
|
-
const webusb = new WebUsbTransport() as any;
|
|
102
|
-
const path = 'pro2-webusb';
|
|
103
|
-
webusb.Log = { debug: jest.fn() };
|
|
104
|
-
webusb.messages = transport.parseConfigure(schema);
|
|
105
|
-
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
106
|
-
webusb.writeProtocolV2UsbPacket = jest.fn().mockResolvedValue(undefined);
|
|
107
|
-
let markReadStarted: () => void = () => undefined;
|
|
108
|
-
const readStarted = new Promise<void>(resolve => {
|
|
109
|
-
markReadStarted = resolve;
|
|
110
|
-
});
|
|
111
|
-
webusb.readProtocolV2UsbPacket = jest.fn().mockImplementation(() => {
|
|
112
|
-
markReadStarted();
|
|
113
|
-
return new Promise<void>(() => {});
|
|
114
|
-
});
|
|
115
|
-
webusb.closeOpenDevice = jest.fn().mockResolvedValue(undefined);
|
|
116
|
-
webusb.deviceProtocol.set(path, 'V2');
|
|
117
|
-
await webusb.rotateProtocolV2UsbGeneration(path, 'test connection');
|
|
118
|
-
|
|
119
|
-
const call = webusb.callProtocolV2(path, 'Ping', { message: 'release' });
|
|
120
|
-
await readStarted;
|
|
121
|
-
await webusb.release(path);
|
|
122
|
-
|
|
123
|
-
await expect(call).rejects.toThrow('WebUSB transport released');
|
|
124
|
-
expect(webusb.deviceProtocol.has(path)).toBe(false);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('call and post fail fast for a path that is not acquired', async () => {
|
|
128
|
-
const webusb = new WebUsbTransport() as any;
|
|
129
|
-
const path = 'pro-webusb';
|
|
130
|
-
webusb.Log = { debug: jest.fn() };
|
|
131
|
-
webusb.messages = transport.parseConfigure(schema);
|
|
132
|
-
webusb.messagesV2 = transport.parseConfigure(schema);
|
|
133
|
-
// A surviving protocol cache entry must NOT act as a session token.
|
|
134
|
-
webusb.deviceProtocol.set(path, 'V1');
|
|
135
|
-
|
|
136
|
-
await expect(webusb.call(path, 'Ping', {})).rejects.toMatchObject({
|
|
137
|
-
errorCode: HardwareErrorCode.RuntimeError,
|
|
138
|
-
message: expect.stringContaining('not acquired'),
|
|
139
|
-
});
|
|
140
|
-
await expect(webusb.post(path, 'Ping', {})).rejects.toMatchObject({
|
|
141
|
-
errorCode: HardwareErrorCode.RuntimeError,
|
|
142
|
-
message: expect.stringContaining('not acquired'),
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('a transfer-level reconnect marks the protocol stale for the next acquire', async () => {
|
|
147
|
-
const webusb = new WebUsbTransport() as any;
|
|
148
|
-
const path = 'pro-webusb';
|
|
149
|
-
webusb.Log = { debug: jest.fn() };
|
|
150
|
-
webusb.deviceProtocol.set(path, 'V1');
|
|
151
|
-
webusb.findDevice = jest.fn().mockResolvedValue({ opened: false });
|
|
152
|
-
webusb.getConnectedDevices = jest.fn().mockResolvedValue([]);
|
|
153
|
-
webusb.connect = jest.fn().mockResolvedValue(undefined);
|
|
154
|
-
|
|
155
|
-
await webusb.reconnectForPacketIoRetry(path, 'in', 0, new Error('transferIn failed'));
|
|
156
|
-
|
|
157
|
-
// The in-flight call keeps the cached protocol; only the next acquire re-probes.
|
|
158
|
-
expect(webusb.deviceProtocol.get(path)).toBe('V1');
|
|
159
|
-
expect(webusb.staleProtocolPaths.has(path)).toBe(true);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('USB disconnect marks the serial stale and the listener attaches only once', () => {
|
|
163
|
-
const addEventListener = jest.fn();
|
|
164
|
-
const usb = { addEventListener } as unknown as USB;
|
|
165
|
-
const originalNavigator = (globalThis as any).navigator;
|
|
166
|
-
Object.defineProperty(globalThis, 'navigator', {
|
|
167
|
-
value: { usb },
|
|
168
|
-
configurable: true,
|
|
169
|
-
});
|
|
170
|
-
try {
|
|
171
|
-
const first = new WebUsbTransport() as any;
|
|
172
|
-
first.init({ debug: jest.fn() });
|
|
173
|
-
const second = new WebUsbTransport() as any;
|
|
174
|
-
second.init({ debug: jest.fn() });
|
|
175
|
-
|
|
176
|
-
// Module-level listener: one registration across instances.
|
|
177
|
-
expect(addEventListener).toHaveBeenCalledTimes(1);
|
|
178
|
-
const handler = addEventListener.mock.calls[0][1] as (event: {
|
|
179
|
-
device?: { serialNumber?: string | null };
|
|
180
|
-
}) => void;
|
|
181
|
-
|
|
182
|
-
const path = 'pro-webusb';
|
|
183
|
-
first.deviceProtocol.set(path, 'V1');
|
|
184
|
-
second.deviceProtocol.set(path, 'V1');
|
|
185
|
-
handler({ device: { serialNumber: path } });
|
|
186
|
-
|
|
187
|
-
// Routed to the most recently initialized instance; the cached value is
|
|
188
|
-
// retained (in-flight sessions keep working) but marked stale.
|
|
189
|
-
expect(second.staleProtocolPaths.has(path)).toBe(true);
|
|
190
|
-
expect(second.deviceProtocol.get(path)).toBe('V1');
|
|
191
|
-
expect(first.staleProtocolPaths.has(path)).toBe(false);
|
|
192
|
-
|
|
193
|
-
// Events without a usable serial are ignored.
|
|
194
|
-
handler({ device: { serialNumber: null } });
|
|
195
|
-
handler({});
|
|
196
|
-
expect(second.staleProtocolPaths.size).toBe(1);
|
|
197
|
-
} finally {
|
|
198
|
-
Object.defineProperty(globalThis, 'navigator', {
|
|
199
|
-
value: originalNavigator,
|
|
200
|
-
configurable: true,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
});
|