@onekeyfe/hd-transport-web-device 1.2.0-alpha.11 → 1.2.0-alpha.111
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 +373 -22
- package/__tests__/webusb-protocol-v2-timeout.test.ts +386 -0
- package/dist/ble-packet-capacity.d.ts +2 -0
- package/dist/ble-packet-capacity.d.ts.map +1 -0
- package/dist/electron-ble-transport.d.ts +15 -3
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +109 -12
- package/dist/index.js +336 -250
- package/dist/transportLog.d.ts +2 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/dist/webusb.d.ts +15 -10
- package/dist/webusb.d.ts.map +1 -1
- package/jest.config.js +5 -0
- package/package.json +6 -5
- package/src/ble-packet-capacity.ts +13 -0
- package/src/electron-ble-transport.ts +225 -96
- package/src/transportLog.ts +1 -0
- package/src/webusb.ts +181 -220
package/src/webusb.ts
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
/* eslint-disable no-undef */
|
|
2
2
|
import transport, {
|
|
3
|
-
LogBlockCommand,
|
|
4
3
|
PROTOCOL_V1_CHUNK_PAYLOAD_SIZE,
|
|
5
4
|
PROTOCOL_V1_MESSAGE_HEADER_SIZE,
|
|
6
5
|
PROTOCOL_V1_REPORT_ID,
|
|
7
6
|
PROTOCOL_V1_USB_PACKET_SIZE,
|
|
8
7
|
PROTOCOL_V2_CHANNEL_USB,
|
|
9
8
|
PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
ProtocolV2LinkError,
|
|
10
|
+
ProtocolV2UsbTransportBase,
|
|
12
11
|
probeProtocolV2 as probeProtocolV2Helper,
|
|
13
12
|
} from '@onekeyfe/hd-transport';
|
|
14
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ERRORS,
|
|
15
|
+
HardwareErrorCode,
|
|
16
|
+
ONEKEY_WEBUSB_FILTER,
|
|
17
|
+
isKnownTrezorWebUsbDevice,
|
|
18
|
+
wait,
|
|
19
|
+
} from '@onekeyfe/hd-shared';
|
|
15
20
|
import ByteBuffer from 'bytebuffer';
|
|
16
21
|
|
|
17
22
|
import type {
|
|
18
23
|
AcquireInput,
|
|
19
24
|
OneKeyDeviceInfoBase,
|
|
20
25
|
ProtocolType,
|
|
26
|
+
ProtocolV2CallContext,
|
|
27
|
+
ProtocolV2Schemas,
|
|
21
28
|
TransportCallOptions,
|
|
22
29
|
} from '@onekeyfe/hd-transport';
|
|
23
30
|
|
|
@@ -32,23 +39,12 @@ const REPORT_ID = PROTOCOL_V1_REPORT_ID;
|
|
|
32
39
|
const HEADER_LENGTH = PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
33
40
|
const PACKET_IO_MAX_RETRIES = 3;
|
|
34
41
|
const PACKET_IO_RETRY_DELAY = 300;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function isLogBlockCommand(name: string) {
|
|
44
|
-
return (LogBlockCommand as Set<string> | undefined)?.has?.(name) ?? false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function shouldBlockWebUsbCallDataLog(name: string) {
|
|
48
|
-
const normalized = name.replace(/[_\s-]/g, '');
|
|
49
|
-
return isLogBlockCommand(name) || WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
|
|
50
|
-
}
|
|
51
|
-
|
|
42
|
+
// Legacy devices can take longer to answer Initialize after a WebUSB reset.
|
|
43
|
+
// Keep this aligned with Node USB so a slow Protocol V1 response is not
|
|
44
|
+
// misreported as a protocol mismatch during acquire.
|
|
45
|
+
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
46
|
+
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
47
|
+
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
52
48
|
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
53
49
|
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
54
50
|
}
|
|
@@ -73,38 +69,22 @@ interface TransferCancelToken {
|
|
|
73
69
|
cancelled: boolean;
|
|
74
70
|
}
|
|
75
71
|
|
|
76
|
-
export default class WebUsbTransport {
|
|
72
|
+
export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
77
73
|
messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
78
74
|
|
|
79
75
|
/** Protobuf schema for Protocol V2 transports. */
|
|
80
76
|
messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
81
77
|
|
|
78
|
+
private protocolV2SchemaSource: string | undefined;
|
|
79
|
+
|
|
82
80
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
83
81
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
84
82
|
|
|
85
83
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
86
84
|
|
|
87
|
-
/** 按设备缓存 Protocol V2 frame assembler,保留同一次读取里多出来的后续 frame。 */
|
|
88
|
-
private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
|
|
89
|
-
|
|
90
|
-
/** 按设备缓存 Protocol V2 session,保持 seq 与设备会话一致递增。 */
|
|
91
|
-
private protocolV2Sessions: Map<string, ProtocolV2Session> = new Map();
|
|
92
|
-
|
|
93
|
-
/** 当前 Protocol V2 调用的读取超时,由缓存 session 的 readFrame 闭包读取。 */
|
|
94
|
-
private protocolV2ReadTimeouts: Map<string, number | undefined> = new Map();
|
|
95
|
-
|
|
96
85
|
/** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
|
|
97
86
|
private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
|
|
98
87
|
|
|
99
|
-
/**
|
|
100
|
-
* 早期 Pro2 工程板 USB descriptor 没有烧录 serial number。
|
|
101
|
-
* 为这类设备生成会话内稳定的 mock path(同一 USBDevice 实例复用同一 path),
|
|
102
|
-
* 避免设备因为空 serial 被发现流程整体丢弃。重新插拔后实例变化,path 会重新生成。
|
|
103
|
-
*/
|
|
104
|
-
private mockSerialPaths: WeakMap<USBDevice, string> = new WeakMap();
|
|
105
|
-
|
|
106
|
-
private mockSerialCounter = 0;
|
|
107
|
-
|
|
108
88
|
name = 'WebUsbTransport';
|
|
109
89
|
|
|
110
90
|
stopped = false;
|
|
@@ -127,6 +107,14 @@ export default class WebUsbTransport {
|
|
|
127
107
|
|
|
128
108
|
interfaceId = INTERFACE_ID;
|
|
129
109
|
|
|
110
|
+
constructor() {
|
|
111
|
+
super({
|
|
112
|
+
router: PROTOCOL_V2_CHANNEL_USB,
|
|
113
|
+
maxFrameBytes: PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
114
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
130
118
|
/**
|
|
131
119
|
* Initialize WebUSB transport
|
|
132
120
|
*/
|
|
@@ -156,10 +144,20 @@ export default class WebUsbTransport {
|
|
|
156
144
|
* Cache the Protocol V2 protobuf schema.
|
|
157
145
|
*/
|
|
158
146
|
configureProtocolV2(signedData: any) {
|
|
147
|
+
const schemaSource =
|
|
148
|
+
typeof signedData === 'string'
|
|
149
|
+
? signedData
|
|
150
|
+
: JSON.stringify(signedData) ?? String(signedData);
|
|
151
|
+
if (schemaSource === this.protocolV2SchemaSource) return;
|
|
152
|
+
|
|
153
|
+
const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
|
|
159
154
|
this.messagesV2 = parseConfigure(signedData);
|
|
160
|
-
this.
|
|
161
|
-
|
|
162
|
-
|
|
155
|
+
this.protocolV2SchemaSource = schemaSource;
|
|
156
|
+
if (!hadProtocolV2Schema) return;
|
|
157
|
+
|
|
158
|
+
this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error =>
|
|
159
|
+
this.Log?.debug('[WebUsbTransport] schema link cleanup failed:', error)
|
|
160
|
+
);
|
|
163
161
|
}
|
|
164
162
|
|
|
165
163
|
/**
|
|
@@ -191,26 +189,6 @@ export default class WebUsbTransport {
|
|
|
191
189
|
return this.deviceList;
|
|
192
190
|
}
|
|
193
191
|
|
|
194
|
-
/**
|
|
195
|
-
* 设备 path:正常设备直接用 USB serial number;
|
|
196
|
-
* 空 serial(早期工程板)回退到会话内稳定的 mock path。
|
|
197
|
-
*/
|
|
198
|
-
private getDevicePath(device: USBDevice): string {
|
|
199
|
-
if (typeof device.serialNumber === 'string' && device.serialNumber.length > 0) {
|
|
200
|
-
return device.serialNumber;
|
|
201
|
-
}
|
|
202
|
-
let path = this.mockSerialPaths.get(device);
|
|
203
|
-
if (!path) {
|
|
204
|
-
this.mockSerialCounter += 1;
|
|
205
|
-
path = `mock-serial:${device.vendorId.toString(16)}:${device.productId.toString(16)}:${
|
|
206
|
-
this.mockSerialCounter
|
|
207
|
-
}`;
|
|
208
|
-
this.mockSerialPaths.set(device, path);
|
|
209
|
-
this.Log?.debug(`[WebUSB] device has no serial number, using mock path: ${path}`);
|
|
210
|
-
}
|
|
211
|
-
return path;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
192
|
/**
|
|
215
193
|
* Get list of connected devices
|
|
216
194
|
*/
|
|
@@ -218,14 +196,17 @@ export default class WebUsbTransport {
|
|
|
218
196
|
if (!this.usb) return [];
|
|
219
197
|
|
|
220
198
|
const devices = await this.usb.getDevices();
|
|
221
|
-
const onekeyDevices = devices.filter(dev =>
|
|
222
|
-
ONEKEY_WEBUSB_FILTER.some(
|
|
223
|
-
desc
|
|
224
|
-
|
|
225
|
-
|
|
199
|
+
const onekeyDevices = devices.filter(dev => {
|
|
200
|
+
const isOneKey = ONEKEY_WEBUSB_FILTER.some(
|
|
201
|
+
(desc: { vendorId: number; productId: number }) =>
|
|
202
|
+
dev.vendorId === desc.vendorId && dev.productId === desc.productId
|
|
203
|
+
);
|
|
204
|
+
const hasSerialNumber = typeof dev.serialNumber === 'string' && dev.serialNumber.length > 0;
|
|
205
|
+
return isOneKey && hasSerialNumber && !isKnownTrezorWebUsbDevice(dev);
|
|
206
|
+
});
|
|
226
207
|
|
|
227
208
|
this.deviceList = onekeyDevices.map(device => {
|
|
228
|
-
const path =
|
|
209
|
+
const path = device.serialNumber as string;
|
|
229
210
|
const protocolHint = inferProtocolHintFromDeviceName(device.productName);
|
|
230
211
|
if (protocolHint) {
|
|
231
212
|
this.deviceProtocolHints.set(path, protocolHint);
|
|
@@ -238,14 +219,6 @@ export default class WebUsbTransport {
|
|
|
238
219
|
};
|
|
239
220
|
});
|
|
240
221
|
|
|
241
|
-
// Debug: log all discovered devices. Protocol is detected after acquire via wire probe.
|
|
242
|
-
for (const dev of onekeyDevices) {
|
|
243
|
-
this.Log.debug(
|
|
244
|
-
`[WebUSB] Device: name="${dev.productName}" serial="${dev.serialNumber}" ` +
|
|
245
|
-
`VID=0x${dev.vendorId.toString(16)} PID=0x${dev.productId.toString(16)}`
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
222
|
return this.deviceList;
|
|
250
223
|
}
|
|
251
224
|
|
|
@@ -255,13 +228,16 @@ export default class WebUsbTransport {
|
|
|
255
228
|
async acquire(input: AcquireInput) {
|
|
256
229
|
if (!input.path) return;
|
|
257
230
|
try {
|
|
231
|
+
await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
258
232
|
await this.closeOpenDevice(input.path);
|
|
259
233
|
await this.connect(input.path ?? '', true);
|
|
260
234
|
const deviceName = this.deviceList.find(device => device.path === input.path)?.device
|
|
261
235
|
.productName;
|
|
262
236
|
const protocolHint = input.expectedProtocol
|
|
263
237
|
? undefined
|
|
264
|
-
:
|
|
238
|
+
: input.protocolHint ??
|
|
239
|
+
this.deviceProtocolHints.get(input.path) ??
|
|
240
|
+
inferProtocolHintFromDeviceName(deviceName);
|
|
265
241
|
if (protocolHint) {
|
|
266
242
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
267
243
|
}
|
|
@@ -269,6 +245,7 @@ export default class WebUsbTransport {
|
|
|
269
245
|
return await Promise.resolve(input.path);
|
|
270
246
|
} catch (e) {
|
|
271
247
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
248
|
+
await this.closeOpenDevice(input.path);
|
|
272
249
|
throw e;
|
|
273
250
|
}
|
|
274
251
|
}
|
|
@@ -285,13 +262,17 @@ export default class WebUsbTransport {
|
|
|
285
262
|
);
|
|
286
263
|
}
|
|
287
264
|
|
|
288
|
-
private
|
|
265
|
+
private createProtocolProbeTimeoutError(expected: ProtocolType, attempts: number) {
|
|
289
266
|
return ERRORS.TypedError(
|
|
290
|
-
HardwareErrorCode.
|
|
291
|
-
|
|
267
|
+
HardwareErrorCode.DeviceInitializeFailed,
|
|
268
|
+
`Protocol ${expected} probe timeout after ${attempts} attempts`
|
|
292
269
|
);
|
|
293
270
|
}
|
|
294
271
|
|
|
272
|
+
private createProtocolDetectionError() {
|
|
273
|
+
return ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
274
|
+
}
|
|
275
|
+
|
|
295
276
|
private async detectProtocol(
|
|
296
277
|
path: string,
|
|
297
278
|
expectedProtocol?: ProtocolType,
|
|
@@ -300,34 +281,56 @@ export default class WebUsbTransport {
|
|
|
300
281
|
if (expectedProtocol === 'V1') {
|
|
301
282
|
if (await this.probeProtocolV1(path)) {
|
|
302
283
|
this.deviceProtocol.set(path, 'V1');
|
|
303
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
|
|
304
284
|
return 'V1';
|
|
305
285
|
}
|
|
286
|
+
await this.closeConnectionAfterProbe(path);
|
|
306
287
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
307
288
|
}
|
|
308
289
|
|
|
309
290
|
if (expectedProtocol === 'V2') {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
291
|
+
for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
|
|
292
|
+
if (await this.probeProtocolV2(path)) {
|
|
293
|
+
this.deviceProtocol.set(path, 'V2');
|
|
294
|
+
return 'V2';
|
|
295
|
+
}
|
|
296
|
+
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
297
|
+
await this.resetConnectionAfterProbe(path);
|
|
298
|
+
this.Log?.debug(
|
|
299
|
+
`[WebUsbTransport] Protocol V2 probe timed out, retrying ${
|
|
300
|
+
attempt + 1
|
|
301
|
+
}/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`
|
|
302
|
+
);
|
|
303
|
+
} else {
|
|
304
|
+
await this.closeConnectionAfterProbe(path);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
this.deviceProtocol.delete(path);
|
|
308
|
+
throw this.createProtocolProbeTimeoutError(
|
|
309
|
+
expectedProtocol,
|
|
310
|
+
EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS
|
|
311
|
+
);
|
|
315
312
|
}
|
|
316
313
|
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
// 不能作为最终结论。
|
|
314
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
315
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
320
316
|
const probeOrder: ProtocolType[] =
|
|
321
317
|
protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
322
318
|
|
|
323
|
-
for (const protocol of probeOrder) {
|
|
319
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
324
320
|
const detected =
|
|
325
321
|
protocol === 'V1' ? await this.probeProtocolV1(path) : await this.probeProtocolV2(path);
|
|
326
322
|
if (detected) {
|
|
327
323
|
this.deviceProtocol.set(path, protocol);
|
|
328
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
|
|
329
324
|
return protocol;
|
|
330
325
|
}
|
|
326
|
+
if (index < probeOrder.length - 1) {
|
|
327
|
+
// A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
|
|
328
|
+
// reopening the device guarantees the next protocol probe cannot consume a
|
|
329
|
+
// late response from the previous protocol generation.
|
|
330
|
+
await this.resetConnectionAfterProbe(path);
|
|
331
|
+
} else {
|
|
332
|
+
await this.closeConnectionAfterProbe(path);
|
|
333
|
+
}
|
|
331
334
|
}
|
|
332
335
|
|
|
333
336
|
this.deviceProtocol.delete(path);
|
|
@@ -414,13 +417,6 @@ export default class WebUsbTransport {
|
|
|
414
417
|
*/
|
|
415
418
|
async connectToDevice(path: string, first: boolean) {
|
|
416
419
|
let device: USBDevice = await this.findDevice(path);
|
|
417
|
-
this.Log.debug(
|
|
418
|
-
'[WebUsbTransport] connecting to device:',
|
|
419
|
-
device.productName,
|
|
420
|
-
'PID:',
|
|
421
|
-
device.productId
|
|
422
|
-
);
|
|
423
|
-
|
|
424
420
|
if (!device.opened) {
|
|
425
421
|
await device.open();
|
|
426
422
|
}
|
|
@@ -446,16 +442,12 @@ export default class WebUsbTransport {
|
|
|
446
442
|
// Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
|
|
447
443
|
const endpoints = this.discoverEndpoints(device);
|
|
448
444
|
this.deviceEndpoints.set(path, endpoints);
|
|
449
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
450
|
-
this.protocolV2Assemblers.set(path, new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES));
|
|
451
|
-
|
|
452
445
|
await device.claimInterface(endpoints.interfaceNumber);
|
|
453
446
|
await this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
454
447
|
await this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
455
448
|
}
|
|
456
449
|
|
|
457
450
|
private async closeOpenDevice(path: string) {
|
|
458
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
459
451
|
const current = this.deviceList.find(device => device.path === path)?.device;
|
|
460
452
|
if (!current?.opened) return;
|
|
461
453
|
|
|
@@ -650,27 +642,24 @@ export default class WebUsbTransport {
|
|
|
650
642
|
throw lastError;
|
|
651
643
|
}
|
|
652
644
|
|
|
653
|
-
private async
|
|
654
|
-
this.
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
try {
|
|
659
|
-
const device = await this.findDevice(path);
|
|
660
|
-
if (device.opened) {
|
|
661
|
-
const endpoints = this.deviceEndpoints.get(path);
|
|
662
|
-
const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
|
|
663
|
-
try {
|
|
664
|
-
await device.releaseInterface(ifaceNum);
|
|
665
|
-
} catch (error) {
|
|
666
|
-
this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
|
|
667
|
-
}
|
|
668
|
-
await device.close();
|
|
669
|
-
}
|
|
670
|
-
} catch (error) {
|
|
671
|
-
this.Log.debug('[WebUsbTransport] close after protocol probe error:', error);
|
|
645
|
+
private async transferInOnce(path: string, length: number): Promise<DataView> {
|
|
646
|
+
const device = await this.findDevice(path);
|
|
647
|
+
if (!device.opened) {
|
|
648
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
672
649
|
}
|
|
650
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
651
|
+
const endpointIn = endpoints?.endpointIn ?? this.endpointId;
|
|
652
|
+
const result = await device.transferIn(endpointIn, length);
|
|
653
|
+
return this.getTransferInData(result);
|
|
654
|
+
}
|
|
673
655
|
|
|
656
|
+
private async closeConnectionAfterProbe(path: string) {
|
|
657
|
+
await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
658
|
+
await this.closeOpenDevice(path);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private async resetConnectionAfterProbe(path: string) {
|
|
662
|
+
await this.closeConnectionAfterProbe(path);
|
|
674
663
|
await this.getConnectedDevices();
|
|
675
664
|
await this.connect(path, false);
|
|
676
665
|
}
|
|
@@ -682,7 +671,6 @@ export default class WebUsbTransport {
|
|
|
682
671
|
protocol: ProtocolType,
|
|
683
672
|
onTimeout?: () => void
|
|
684
673
|
): Promise<T> {
|
|
685
|
-
void path;
|
|
686
674
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
687
675
|
let timedOut = false;
|
|
688
676
|
const waitForeverAfterTimeout = () => new Promise<never>(() => {});
|
|
@@ -702,6 +690,13 @@ export default class WebUsbTransport {
|
|
|
702
690
|
timer = setTimeout(async () => {
|
|
703
691
|
timedOut = true;
|
|
704
692
|
onTimeout?.();
|
|
693
|
+
if (protocol === 'V1') {
|
|
694
|
+
try {
|
|
695
|
+
await this.resetConnectionAfterProbe(path);
|
|
696
|
+
} catch (error) {
|
|
697
|
+
this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
705
700
|
reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
|
|
706
701
|
}, timeoutMs);
|
|
707
702
|
}),
|
|
@@ -717,10 +712,16 @@ export default class WebUsbTransport {
|
|
|
717
712
|
}
|
|
718
713
|
|
|
719
714
|
try {
|
|
720
|
-
await this.callProtocolV1(
|
|
715
|
+
await this.callProtocolV1(
|
|
716
|
+
path,
|
|
717
|
+
'Initialize',
|
|
718
|
+
{},
|
|
719
|
+
{
|
|
720
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
721
|
+
}
|
|
722
|
+
);
|
|
721
723
|
return true;
|
|
722
|
-
} catch (
|
|
723
|
-
this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
|
|
724
|
+
} catch (_error) {
|
|
724
725
|
return false;
|
|
725
726
|
}
|
|
726
727
|
}
|
|
@@ -732,10 +733,9 @@ export default class WebUsbTransport {
|
|
|
732
733
|
|
|
733
734
|
return probeProtocolV2Helper({
|
|
734
735
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
735
|
-
timeoutMs:
|
|
736
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
736
737
|
logger: this.Log,
|
|
737
738
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
738
|
-
onProbeFailed: () => this.resetConnectionAfterProbe(path),
|
|
739
739
|
});
|
|
740
740
|
}
|
|
741
741
|
|
|
@@ -765,14 +765,6 @@ export default class WebUsbTransport {
|
|
|
765
765
|
);
|
|
766
766
|
}
|
|
767
767
|
|
|
768
|
-
if (shouldSuppressWebUsbCallLog(name)) {
|
|
769
|
-
// 高频文件写入不要逐包发 debug 事件,否则浏览器侧会被日志处理拖慢。
|
|
770
|
-
} else if (shouldBlockWebUsbCallDataLog(name)) {
|
|
771
|
-
this.Log.debug('call-', ' name: ', name, ' protocol: ', protocol);
|
|
772
|
-
} else {
|
|
773
|
-
this.Log.debug('call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
768
|
if (protocol === 'V2') {
|
|
777
769
|
return this.callProtocolV2(path, name, data, options);
|
|
778
770
|
}
|
|
@@ -820,83 +812,7 @@ export default class WebUsbTransport {
|
|
|
820
812
|
data: Record<string, unknown>,
|
|
821
813
|
options?: TransportCallOptions
|
|
822
814
|
) {
|
|
823
|
-
|
|
824
|
-
if (!this.messagesV2) {
|
|
825
|
-
throw ERRORS.TypedError(
|
|
826
|
-
HardwareErrorCode.TransportNotConfigured,
|
|
827
|
-
'Protocol V2 schema not configured'
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
if (!protocolV1Messages) {
|
|
831
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
let session = this.protocolV2Sessions.get(path);
|
|
835
|
-
if (!session) {
|
|
836
|
-
session = new ProtocolV2Session({
|
|
837
|
-
schemas: {
|
|
838
|
-
protocolV1: protocolV1Messages,
|
|
839
|
-
protocolV2: this.messagesV2,
|
|
840
|
-
},
|
|
841
|
-
router: PROTOCOL_V2_CHANNEL_USB,
|
|
842
|
-
writeFrame: (frame: Uint8Array) => this.transferOutOnce(path, frame),
|
|
843
|
-
readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
|
|
844
|
-
logger: this.Log,
|
|
845
|
-
logPrefix: 'ProtocolV2 WebUSB',
|
|
846
|
-
createTimeoutError: (messageName: string, timeoutMs: number) =>
|
|
847
|
-
new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
|
|
848
|
-
});
|
|
849
|
-
this.protocolV2Sessions.set(path, session);
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
this.protocolV2ReadTimeouts.set(path, options?.timeoutMs);
|
|
853
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
854
|
-
try {
|
|
855
|
-
return await session.call(name, data, options);
|
|
856
|
-
} finally {
|
|
857
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
private async receiveProtocolV2Frame(path: string, timeoutMs?: number): Promise<Uint8Array> {
|
|
862
|
-
let assembler = this.protocolV2Assemblers.get(path);
|
|
863
|
-
if (!assembler) {
|
|
864
|
-
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
865
|
-
this.protocolV2Assemblers.set(path, assembler);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
let frame: Uint8Array | undefined = assembler.push(new Uint8Array(0));
|
|
869
|
-
const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
|
|
870
|
-
|
|
871
|
-
while (!frame) {
|
|
872
|
-
const cancelToken = { cancelled: false };
|
|
873
|
-
const transferIn = this.transferInWithRetry(path, PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
|
|
874
|
-
const dataView = deadline
|
|
875
|
-
? await this.withProtocolReadTimeout(
|
|
876
|
-
path,
|
|
877
|
-
transferIn,
|
|
878
|
-
Math.max(deadline - Date.now(), 1),
|
|
879
|
-
'V2',
|
|
880
|
-
() => {
|
|
881
|
-
cancelToken.cancelled = true;
|
|
882
|
-
}
|
|
883
|
-
)
|
|
884
|
-
: await transferIn;
|
|
885
|
-
const bytes = new Uint8Array(
|
|
886
|
-
this.toArrayBuffer(
|
|
887
|
-
dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
|
|
888
|
-
)
|
|
889
|
-
);
|
|
890
|
-
try {
|
|
891
|
-
frame = assembler.push(bytes);
|
|
892
|
-
} catch (error) {
|
|
893
|
-
throw ERRORS.TypedError(
|
|
894
|
-
HardwareErrorCode.NetworkError,
|
|
895
|
-
error instanceof Error ? error.message : String(error)
|
|
896
|
-
);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
return frame;
|
|
815
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
900
816
|
}
|
|
901
817
|
|
|
902
818
|
/**
|
|
@@ -951,18 +867,63 @@ export default class WebUsbTransport {
|
|
|
951
867
|
* Release device
|
|
952
868
|
*/
|
|
953
869
|
async release(path: string) {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
|
|
957
|
-
await device.releaseInterface(ifaceNum);
|
|
958
|
-
await device.close();
|
|
870
|
+
await this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
871
|
+
await this.closeOpenDevice(path);
|
|
959
872
|
this.deviceProtocol.delete(path);
|
|
960
873
|
this.deviceProtocolHints.delete(path);
|
|
961
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
962
|
-
this.protocolV2Assemblers.delete(path);
|
|
963
874
|
this.deviceEndpoints.delete(path);
|
|
964
875
|
}
|
|
965
876
|
|
|
877
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas {
|
|
878
|
+
if (!this.messages || !this.messagesV2) {
|
|
879
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
880
|
+
}
|
|
881
|
+
return {
|
|
882
|
+
protocolV1: this.messages,
|
|
883
|
+
protocolV2: this.messagesV2,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
protected getProtocolV2UsbLogger() {
|
|
888
|
+
return this.Log;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
protected async writeProtocolV2UsbPacket(
|
|
892
|
+
path: string,
|
|
893
|
+
frame: Uint8Array,
|
|
894
|
+
_context: ProtocolV2CallContext
|
|
895
|
+
): Promise<void> {
|
|
896
|
+
await this.transferOutOnce(path, frame);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
protected async readProtocolV2UsbPacket(
|
|
900
|
+
path: string,
|
|
901
|
+
_context: ProtocolV2CallContext
|
|
902
|
+
): Promise<Uint8Array> {
|
|
903
|
+
const dataView = await this.transferInOnce(path, PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
904
|
+
return new Uint8Array(
|
|
905
|
+
this.toArrayBuffer(
|
|
906
|
+
dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
|
|
907
|
+
)
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
protected async resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void> {
|
|
912
|
+
await this.closeOpenDevice(path);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string) {
|
|
916
|
+
this.deviceProtocol.delete(path);
|
|
917
|
+
this.Log?.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error {
|
|
921
|
+
return new ProtocolV2LinkError(
|
|
922
|
+
'response-timeout',
|
|
923
|
+
`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
966
927
|
/**
|
|
967
928
|
* Expose the detected protocol type for a given device path.
|
|
968
929
|
* Used by upper layers (e.g. TransportManager) to select the correct schema.
|