@onekeyfe/hd-transport-web-device 1.2.0-alpha.3 → 1.2.0-alpha.31
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 +267 -18
- package/__tests__/webusb-protocol-v2-timeout.test.ts +288 -0
- package/dist/electron-ble-transport.d.ts +6 -5
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +106 -11
- package/dist/index.js +267 -294
- package/dist/transportLog.d.ts +7 -0
- package/dist/transportLog.d.ts.map +1 -0
- package/dist/webusb.d.ts +15 -7
- package/dist/webusb.d.ts.map +1 -1
- package/jest.config.js +5 -0
- package/package.json +6 -5
- package/src/electron-ble-transport.ts +153 -175
- package/src/transportLog.ts +11 -0
- package/src/webusb.ts +171 -179
package/src/webusb.ts
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
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
|
|
|
22
|
+
import { createTransportCallLog, shouldSuppressHighVolumeCallLog } from './transportLog';
|
|
23
|
+
|
|
17
24
|
import type {
|
|
18
25
|
AcquireInput,
|
|
19
26
|
OneKeyDeviceInfoBase,
|
|
20
27
|
ProtocolType,
|
|
28
|
+
ProtocolV2CallContext,
|
|
29
|
+
ProtocolV2Schemas,
|
|
21
30
|
TransportCallOptions,
|
|
22
31
|
} from '@onekeyfe/hd-transport';
|
|
23
32
|
|
|
@@ -33,22 +42,7 @@ const HEADER_LENGTH = PROTOCOL_V1_MESSAGE_HEADER_SIZE;
|
|
|
33
42
|
const PACKET_IO_MAX_RETRIES = 3;
|
|
34
43
|
const PACKET_IO_RETRY_DELAY = 300;
|
|
35
44
|
const PROTOCOL_PROBE_TIMEOUT = 1000;
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
function shouldSuppressWebUsbCallLog(name: string) {
|
|
39
|
-
const normalized = name.replace(/[_\s-]/g, '');
|
|
40
|
-
return WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
|
|
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
|
-
|
|
45
|
+
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
52
46
|
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
53
47
|
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
54
48
|
}
|
|
@@ -73,33 +67,25 @@ interface TransferCancelToken {
|
|
|
73
67
|
cancelled: boolean;
|
|
74
68
|
}
|
|
75
69
|
|
|
76
|
-
export default class WebUsbTransport {
|
|
70
|
+
export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
77
71
|
messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
78
72
|
|
|
79
73
|
/** Protobuf schema for Protocol V2 transports. */
|
|
80
74
|
messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
81
75
|
|
|
76
|
+
private protocolV2SchemaSource: string | undefined;
|
|
77
|
+
|
|
82
78
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
83
79
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
84
80
|
|
|
85
81
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
86
82
|
|
|
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
83
|
/** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
|
|
97
84
|
private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
|
|
98
85
|
|
|
99
86
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* 避免设备因为空 serial 被发现流程整体丢弃。重新插拔后实例变化,path 会重新生成。
|
|
87
|
+
* Early Pro2 boards have no USB serial number. Assign a session-stable mock path per
|
|
88
|
+
* USBDevice instance so discovery retains them; reconnecting creates a new instance/path.
|
|
103
89
|
*/
|
|
104
90
|
private mockSerialPaths: WeakMap<USBDevice, string> = new WeakMap();
|
|
105
91
|
|
|
@@ -127,6 +113,14 @@ export default class WebUsbTransport {
|
|
|
127
113
|
|
|
128
114
|
interfaceId = INTERFACE_ID;
|
|
129
115
|
|
|
116
|
+
constructor() {
|
|
117
|
+
super({
|
|
118
|
+
router: PROTOCOL_V2_CHANNEL_USB,
|
|
119
|
+
maxFrameBytes: PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
120
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
130
124
|
/**
|
|
131
125
|
* Initialize WebUSB transport
|
|
132
126
|
*/
|
|
@@ -156,10 +150,20 @@ export default class WebUsbTransport {
|
|
|
156
150
|
* Cache the Protocol V2 protobuf schema.
|
|
157
151
|
*/
|
|
158
152
|
configureProtocolV2(signedData: any) {
|
|
153
|
+
const schemaSource =
|
|
154
|
+
typeof signedData === 'string'
|
|
155
|
+
? signedData
|
|
156
|
+
: JSON.stringify(signedData) ?? String(signedData);
|
|
157
|
+
if (schemaSource === this.protocolV2SchemaSource) return;
|
|
158
|
+
|
|
159
|
+
const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
|
|
159
160
|
this.messagesV2 = parseConfigure(signedData);
|
|
160
|
-
this.
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
this.protocolV2SchemaSource = schemaSource;
|
|
162
|
+
if (!hadProtocolV2Schema) return;
|
|
163
|
+
|
|
164
|
+
this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error =>
|
|
165
|
+
this.Log?.debug('[WebUsbTransport] schema link cleanup failed:', error)
|
|
166
|
+
);
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
/**
|
|
@@ -192,8 +196,7 @@ export default class WebUsbTransport {
|
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
/**
|
|
195
|
-
*
|
|
196
|
-
* 空 serial(早期工程板)回退到会话内稳定的 mock path。
|
|
199
|
+
* Use the USB serial as the device path, falling back to a session-stable mock path.
|
|
197
200
|
*/
|
|
198
201
|
private getDevicePath(device: USBDevice): string {
|
|
199
202
|
if (typeof device.serialNumber === 'string' && device.serialNumber.length > 0) {
|
|
@@ -218,11 +221,13 @@ export default class WebUsbTransport {
|
|
|
218
221
|
if (!this.usb) return [];
|
|
219
222
|
|
|
220
223
|
const devices = await this.usb.getDevices();
|
|
221
|
-
const onekeyDevices = devices.filter(dev =>
|
|
222
|
-
ONEKEY_WEBUSB_FILTER.some(
|
|
223
|
-
desc
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
const onekeyDevices = devices.filter(dev => {
|
|
225
|
+
const isOneKey = ONEKEY_WEBUSB_FILTER.some(
|
|
226
|
+
(desc: { vendorId: number; productId: number }) =>
|
|
227
|
+
dev.vendorId === desc.vendorId && dev.productId === desc.productId
|
|
228
|
+
);
|
|
229
|
+
return isOneKey && !isKnownTrezorWebUsbDevice(dev);
|
|
230
|
+
});
|
|
226
231
|
|
|
227
232
|
this.deviceList = onekeyDevices.map(device => {
|
|
228
233
|
const path = this.getDevicePath(device);
|
|
@@ -238,14 +243,6 @@ export default class WebUsbTransport {
|
|
|
238
243
|
};
|
|
239
244
|
});
|
|
240
245
|
|
|
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
246
|
return this.deviceList;
|
|
250
247
|
}
|
|
251
248
|
|
|
@@ -255,6 +252,7 @@ export default class WebUsbTransport {
|
|
|
255
252
|
async acquire(input: AcquireInput) {
|
|
256
253
|
if (!input.path) return;
|
|
257
254
|
try {
|
|
255
|
+
await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
258
256
|
await this.closeOpenDevice(input.path);
|
|
259
257
|
await this.connect(input.path ?? '', true);
|
|
260
258
|
const deviceName = this.deviceList.find(device => device.path === input.path)?.device
|
|
@@ -285,6 +283,13 @@ export default class WebUsbTransport {
|
|
|
285
283
|
);
|
|
286
284
|
}
|
|
287
285
|
|
|
286
|
+
private createProtocolProbeTimeoutError(expected: ProtocolType, attempts: number) {
|
|
287
|
+
return ERRORS.TypedError(
|
|
288
|
+
HardwareErrorCode.RuntimeError,
|
|
289
|
+
`Protocol ${expected} probe timeout after ${attempts} attempts`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
288
293
|
private createProtocolDetectionError() {
|
|
289
294
|
return ERRORS.TypedError(
|
|
290
295
|
HardwareErrorCode.RuntimeError,
|
|
@@ -300,23 +305,36 @@ export default class WebUsbTransport {
|
|
|
300
305
|
if (expectedProtocol === 'V1') {
|
|
301
306
|
if (await this.probeProtocolV1(path)) {
|
|
302
307
|
this.deviceProtocol.set(path, 'V1');
|
|
303
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
|
|
304
308
|
return 'V1';
|
|
305
309
|
}
|
|
310
|
+
await this.resetConnectionAfterProbe(path);
|
|
306
311
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
307
312
|
}
|
|
308
313
|
|
|
309
314
|
if (expectedProtocol === 'V2') {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
+
for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
|
|
316
|
+
if (await this.probeProtocolV2(path)) {
|
|
317
|
+
this.deviceProtocol.set(path, 'V2');
|
|
318
|
+
return 'V2';
|
|
319
|
+
}
|
|
320
|
+
await this.resetConnectionAfterProbe(path);
|
|
321
|
+
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
322
|
+
this.Log?.debug(
|
|
323
|
+
`[WebUsbTransport] Protocol V2 probe timed out, retrying ${
|
|
324
|
+
attempt + 1
|
|
325
|
+
}/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
this.deviceProtocol.delete(path);
|
|
330
|
+
throw this.createProtocolProbeTimeoutError(
|
|
331
|
+
expectedProtocol,
|
|
332
|
+
EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS
|
|
333
|
+
);
|
|
315
334
|
}
|
|
316
335
|
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
// 不能作为最终结论。
|
|
336
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
337
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
320
338
|
const probeOrder: ProtocolType[] =
|
|
321
339
|
protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
322
340
|
|
|
@@ -325,9 +343,12 @@ export default class WebUsbTransport {
|
|
|
325
343
|
protocol === 'V1' ? await this.probeProtocolV1(path) : await this.probeProtocolV2(path);
|
|
326
344
|
if (detected) {
|
|
327
345
|
this.deviceProtocol.set(path, protocol);
|
|
328
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
|
|
329
346
|
return protocol;
|
|
330
347
|
}
|
|
348
|
+
// A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
|
|
349
|
+
// reopening the device guarantees the next protocol probe cannot consume a
|
|
350
|
+
// late response from the previous protocol generation.
|
|
351
|
+
await this.resetConnectionAfterProbe(path);
|
|
331
352
|
}
|
|
332
353
|
|
|
333
354
|
this.deviceProtocol.delete(path);
|
|
@@ -414,13 +435,6 @@ export default class WebUsbTransport {
|
|
|
414
435
|
*/
|
|
415
436
|
async connectToDevice(path: string, first: boolean) {
|
|
416
437
|
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
438
|
if (!device.opened) {
|
|
425
439
|
await device.open();
|
|
426
440
|
}
|
|
@@ -446,16 +460,12 @@ export default class WebUsbTransport {
|
|
|
446
460
|
// Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
|
|
447
461
|
const endpoints = this.discoverEndpoints(device);
|
|
448
462
|
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
463
|
await device.claimInterface(endpoints.interfaceNumber);
|
|
453
464
|
await this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
454
465
|
await this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
455
466
|
}
|
|
456
467
|
|
|
457
468
|
private async closeOpenDevice(path: string) {
|
|
458
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
459
469
|
const current = this.deviceList.find(device => device.path === path)?.device;
|
|
460
470
|
if (!current?.opened) return;
|
|
461
471
|
|
|
@@ -571,16 +581,7 @@ export default class WebUsbTransport {
|
|
|
571
581
|
let lastError: unknown;
|
|
572
582
|
for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
|
|
573
583
|
try {
|
|
574
|
-
|
|
575
|
-
if (!device.opened) {
|
|
576
|
-
await this.connect(path, false);
|
|
577
|
-
}
|
|
578
|
-
const endpoints = this.deviceEndpoints.get(path);
|
|
579
|
-
const endpointOut = endpoints?.endpointOut ?? this.endpointId;
|
|
580
|
-
const transferBuffer = this.toArrayBuffer(
|
|
581
|
-
packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
|
|
582
|
-
);
|
|
583
|
-
await device.transferOut(endpointOut, transferBuffer);
|
|
584
|
+
await this.transferOutOnce(path, packet);
|
|
584
585
|
return;
|
|
585
586
|
} catch (error) {
|
|
586
587
|
lastError = error;
|
|
@@ -603,6 +604,19 @@ export default class WebUsbTransport {
|
|
|
603
604
|
throw lastError;
|
|
604
605
|
}
|
|
605
606
|
|
|
607
|
+
private async transferOutOnce(path: string, packet: Uint8Array) {
|
|
608
|
+
const device = await this.findDevice(path);
|
|
609
|
+
if (!device.opened) {
|
|
610
|
+
await this.connect(path, false);
|
|
611
|
+
}
|
|
612
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
613
|
+
const endpointOut = endpoints?.endpointOut ?? this.endpointId;
|
|
614
|
+
const transferBuffer = this.toArrayBuffer(
|
|
615
|
+
packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
|
|
616
|
+
);
|
|
617
|
+
await device.transferOut(endpointOut, transferBuffer);
|
|
618
|
+
}
|
|
619
|
+
|
|
606
620
|
private async transferInWithRetry(
|
|
607
621
|
path: string,
|
|
608
622
|
length: number,
|
|
@@ -646,10 +660,19 @@ export default class WebUsbTransport {
|
|
|
646
660
|
throw lastError;
|
|
647
661
|
}
|
|
648
662
|
|
|
663
|
+
private async transferInOnce(path: string, length: number): Promise<DataView> {
|
|
664
|
+
const device = await this.findDevice(path);
|
|
665
|
+
if (!device.opened) {
|
|
666
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
667
|
+
}
|
|
668
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
669
|
+
const endpointIn = endpoints?.endpointIn ?? this.endpointId;
|
|
670
|
+
const result = await device.transferIn(endpointIn, length);
|
|
671
|
+
return this.getTransferInData(result);
|
|
672
|
+
}
|
|
673
|
+
|
|
649
674
|
private async resetConnectionAfterProbe(path: string) {
|
|
650
|
-
this.
|
|
651
|
-
this.protocolV2Sessions.delete(path);
|
|
652
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
675
|
+
await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
653
676
|
|
|
654
677
|
try {
|
|
655
678
|
const device = await this.findDevice(path);
|
|
@@ -678,7 +701,6 @@ export default class WebUsbTransport {
|
|
|
678
701
|
protocol: ProtocolType,
|
|
679
702
|
onTimeout?: () => void
|
|
680
703
|
): Promise<T> {
|
|
681
|
-
void path;
|
|
682
704
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
683
705
|
let timedOut = false;
|
|
684
706
|
const waitForeverAfterTimeout = () => new Promise<never>(() => {});
|
|
@@ -698,6 +720,13 @@ export default class WebUsbTransport {
|
|
|
698
720
|
timer = setTimeout(async () => {
|
|
699
721
|
timedOut = true;
|
|
700
722
|
onTimeout?.();
|
|
723
|
+
if (protocol === 'V1') {
|
|
724
|
+
try {
|
|
725
|
+
await this.resetConnectionAfterProbe(path);
|
|
726
|
+
} catch (error) {
|
|
727
|
+
this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
701
730
|
reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
|
|
702
731
|
}, timeoutMs);
|
|
703
732
|
}),
|
|
@@ -715,8 +744,7 @@ export default class WebUsbTransport {
|
|
|
715
744
|
try {
|
|
716
745
|
await this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
|
|
717
746
|
return true;
|
|
718
|
-
} catch (
|
|
719
|
-
this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
|
|
747
|
+
} catch (_error) {
|
|
720
748
|
return false;
|
|
721
749
|
}
|
|
722
750
|
}
|
|
@@ -731,7 +759,6 @@ export default class WebUsbTransport {
|
|
|
731
759
|
timeoutMs: PROTOCOL_PROBE_TIMEOUT,
|
|
732
760
|
logger: this.Log,
|
|
733
761
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
734
|
-
onProbeFailed: () => this.resetConnectionAfterProbe(path),
|
|
735
762
|
});
|
|
736
763
|
}
|
|
737
764
|
|
|
@@ -761,12 +788,8 @@ export default class WebUsbTransport {
|
|
|
761
788
|
);
|
|
762
789
|
}
|
|
763
790
|
|
|
764
|
-
if (
|
|
765
|
-
|
|
766
|
-
} else if (shouldBlockWebUsbCallDataLog(name)) {
|
|
767
|
-
this.Log.debug('call-', ' name: ', name, ' protocol: ', protocol);
|
|
768
|
-
} else {
|
|
769
|
-
this.Log.debug('call-', ' name: ', name, ' data: ', data, ' protocol: ', protocol);
|
|
791
|
+
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
792
|
+
this.Log.debug('transport call', createTransportCallLog(name, protocol));
|
|
770
793
|
}
|
|
771
794
|
|
|
772
795
|
if (protocol === 'V2') {
|
|
@@ -816,83 +839,7 @@ export default class WebUsbTransport {
|
|
|
816
839
|
data: Record<string, unknown>,
|
|
817
840
|
options?: TransportCallOptions
|
|
818
841
|
) {
|
|
819
|
-
|
|
820
|
-
if (!this.messagesV2) {
|
|
821
|
-
throw ERRORS.TypedError(
|
|
822
|
-
HardwareErrorCode.TransportNotConfigured,
|
|
823
|
-
'Protocol V2 schema not configured'
|
|
824
|
-
);
|
|
825
|
-
}
|
|
826
|
-
if (!protocolV1Messages) {
|
|
827
|
-
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
let session = this.protocolV2Sessions.get(path);
|
|
831
|
-
if (!session) {
|
|
832
|
-
session = new ProtocolV2Session({
|
|
833
|
-
schemas: {
|
|
834
|
-
protocolV1: protocolV1Messages,
|
|
835
|
-
protocolV2: this.messagesV2,
|
|
836
|
-
},
|
|
837
|
-
router: PROTOCOL_V2_CHANNEL_USB,
|
|
838
|
-
writeFrame: (frame: Uint8Array) => this.transferOutWithRetry(path, frame),
|
|
839
|
-
readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
|
|
840
|
-
logger: this.Log,
|
|
841
|
-
logPrefix: 'ProtocolV2 WebUSB',
|
|
842
|
-
createTimeoutError: (messageName: string, timeoutMs: number) =>
|
|
843
|
-
new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${messageName}`),
|
|
844
|
-
});
|
|
845
|
-
this.protocolV2Sessions.set(path, session);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
this.protocolV2ReadTimeouts.set(path, options?.timeoutMs);
|
|
849
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
850
|
-
try {
|
|
851
|
-
return await session.call(name, data, options);
|
|
852
|
-
} finally {
|
|
853
|
-
this.protocolV2ReadTimeouts.delete(path);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
private async receiveProtocolV2Frame(path: string, timeoutMs?: number): Promise<Uint8Array> {
|
|
858
|
-
let assembler = this.protocolV2Assemblers.get(path);
|
|
859
|
-
if (!assembler) {
|
|
860
|
-
assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
861
|
-
this.protocolV2Assemblers.set(path, assembler);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
let frame: Uint8Array | undefined = assembler.push(new Uint8Array(0));
|
|
865
|
-
const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
|
|
866
|
-
|
|
867
|
-
while (!frame) {
|
|
868
|
-
const cancelToken = { cancelled: false };
|
|
869
|
-
const transferIn = this.transferInWithRetry(path, PROTOCOL_V2_FRAME_MAX_BYTES, cancelToken);
|
|
870
|
-
const dataView = deadline
|
|
871
|
-
? await this.withProtocolReadTimeout(
|
|
872
|
-
path,
|
|
873
|
-
transferIn,
|
|
874
|
-
Math.max(deadline - Date.now(), 1),
|
|
875
|
-
'V2',
|
|
876
|
-
() => {
|
|
877
|
-
cancelToken.cancelled = true;
|
|
878
|
-
}
|
|
879
|
-
)
|
|
880
|
-
: await transferIn;
|
|
881
|
-
const bytes = new Uint8Array(
|
|
882
|
-
this.toArrayBuffer(
|
|
883
|
-
dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
|
|
884
|
-
)
|
|
885
|
-
);
|
|
886
|
-
try {
|
|
887
|
-
frame = assembler.push(bytes);
|
|
888
|
-
} catch (error) {
|
|
889
|
-
throw ERRORS.TypedError(
|
|
890
|
-
HardwareErrorCode.NetworkError,
|
|
891
|
-
error instanceof Error ? error.message : String(error)
|
|
892
|
-
);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
return frame;
|
|
842
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
896
843
|
}
|
|
897
844
|
|
|
898
845
|
/**
|
|
@@ -947,18 +894,63 @@ export default class WebUsbTransport {
|
|
|
947
894
|
* Release device
|
|
948
895
|
*/
|
|
949
896
|
async release(path: string) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
|
|
953
|
-
await device.releaseInterface(ifaceNum);
|
|
954
|
-
await device.close();
|
|
897
|
+
await this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
898
|
+
await this.closeOpenDevice(path);
|
|
955
899
|
this.deviceProtocol.delete(path);
|
|
956
900
|
this.deviceProtocolHints.delete(path);
|
|
957
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
958
|
-
this.protocolV2Assemblers.delete(path);
|
|
959
901
|
this.deviceEndpoints.delete(path);
|
|
960
902
|
}
|
|
961
903
|
|
|
904
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas {
|
|
905
|
+
if (!this.messages || !this.messagesV2) {
|
|
906
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
909
|
+
protocolV1: this.messages,
|
|
910
|
+
protocolV2: this.messagesV2,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
protected getProtocolV2UsbLogger() {
|
|
915
|
+
return this.Log;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
protected async writeProtocolV2UsbPacket(
|
|
919
|
+
path: string,
|
|
920
|
+
frame: Uint8Array,
|
|
921
|
+
_context: ProtocolV2CallContext
|
|
922
|
+
): Promise<void> {
|
|
923
|
+
await this.transferOutOnce(path, frame);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
protected async readProtocolV2UsbPacket(
|
|
927
|
+
path: string,
|
|
928
|
+
_context: ProtocolV2CallContext
|
|
929
|
+
): Promise<Uint8Array> {
|
|
930
|
+
const dataView = await this.transferInOnce(path, PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
931
|
+
return new Uint8Array(
|
|
932
|
+
this.toArrayBuffer(
|
|
933
|
+
dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
|
|
934
|
+
)
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
protected async resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void> {
|
|
939
|
+
await this.closeOpenDevice(path);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string) {
|
|
943
|
+
this.deviceProtocol.delete(path);
|
|
944
|
+
this.Log?.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error {
|
|
948
|
+
return new ProtocolV2LinkError(
|
|
949
|
+
'response-timeout',
|
|
950
|
+
`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
|
|
962
954
|
/**
|
|
963
955
|
* Expose the detected protocol type for a given device path.
|
|
964
956
|
* Used by upper layers (e.g. TransportManager) to select the correct schema.
|