@onekeyfe/hd-transport-web-device 1.2.0-alpha.7 → 1.2.0-alpha.71
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 +450 -12
- package/__tests__/webusb-protocol-v2-timeout.test.ts +385 -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 +14 -5
- package/dist/electron-ble-transport.d.ts.map +1 -1
- package/dist/index.d.ts +107 -14
- package/dist/index.js +387 -325
- 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 +279 -175
- package/src/transportLog.ts +1 -0
- package/src/webusb.ts +184 -217
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createTransportCallLog, shouldSuppressHighVolumeCallLog } from '@onekeyfe/hd-transport';
|
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
|
|
|
@@ -32,23 +41,12 @@ const REPORT_ID = PROTOCOL_V1_REPORT_ID;
|
|
|
32
41
|
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
|
-
|
|
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
|
-
|
|
44
|
+
// Legacy devices can take longer to answer Initialize after a WebUSB reset.
|
|
45
|
+
// Keep this aligned with Node USB so a slow Protocol V1 response is not
|
|
46
|
+
// misreported as a protocol mismatch during acquire.
|
|
47
|
+
const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
|
|
48
|
+
const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
|
|
49
|
+
const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
|
|
52
50
|
function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
|
|
53
51
|
return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
|
|
54
52
|
}
|
|
@@ -73,38 +71,22 @@ interface TransferCancelToken {
|
|
|
73
71
|
cancelled: boolean;
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
export default class WebUsbTransport {
|
|
74
|
+
export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
|
|
77
75
|
messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
78
76
|
|
|
79
77
|
/** Protobuf schema for Protocol V2 transports. */
|
|
80
78
|
messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
81
79
|
|
|
80
|
+
private protocolV2SchemaSource: string | undefined;
|
|
81
|
+
|
|
82
82
|
/** Per-path protocol type detected by active wire-level probe. */
|
|
83
83
|
private deviceProtocol: Map<string, ProtocolType> = new Map();
|
|
84
84
|
|
|
85
85
|
private deviceProtocolHints: Map<string, ProtocolType> = new Map();
|
|
86
86
|
|
|
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
87
|
/** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
|
|
97
88
|
private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
|
|
98
89
|
|
|
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
90
|
name = 'WebUsbTransport';
|
|
109
91
|
|
|
110
92
|
stopped = false;
|
|
@@ -127,6 +109,14 @@ export default class WebUsbTransport {
|
|
|
127
109
|
|
|
128
110
|
interfaceId = INTERFACE_ID;
|
|
129
111
|
|
|
112
|
+
constructor() {
|
|
113
|
+
super({
|
|
114
|
+
router: PROTOCOL_V2_CHANNEL_USB,
|
|
115
|
+
maxFrameBytes: PROTOCOL_V2_FRAME_MAX_BYTES,
|
|
116
|
+
logPrefix: 'ProtocolV2 WebUSB',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
130
120
|
/**
|
|
131
121
|
* Initialize WebUSB transport
|
|
132
122
|
*/
|
|
@@ -156,10 +146,20 @@ export default class WebUsbTransport {
|
|
|
156
146
|
* Cache the Protocol V2 protobuf schema.
|
|
157
147
|
*/
|
|
158
148
|
configureProtocolV2(signedData: any) {
|
|
149
|
+
const schemaSource =
|
|
150
|
+
typeof signedData === 'string'
|
|
151
|
+
? signedData
|
|
152
|
+
: JSON.stringify(signedData) ?? String(signedData);
|
|
153
|
+
if (schemaSource === this.protocolV2SchemaSource) return;
|
|
154
|
+
|
|
155
|
+
const hadProtocolV2Schema = this.protocolV2SchemaSource !== undefined;
|
|
159
156
|
this.messagesV2 = parseConfigure(signedData);
|
|
160
|
-
this.
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
this.protocolV2SchemaSource = schemaSource;
|
|
158
|
+
if (!hadProtocolV2Schema) return;
|
|
159
|
+
|
|
160
|
+
this.invalidateAllProtocolV2UsbLinks('Protocol V2 schema reconfigured').catch(error =>
|
|
161
|
+
this.Log?.debug('[WebUsbTransport] schema link cleanup failed:', error)
|
|
162
|
+
);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/**
|
|
@@ -191,26 +191,6 @@ export default class WebUsbTransport {
|
|
|
191
191
|
return this.deviceList;
|
|
192
192
|
}
|
|
193
193
|
|
|
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
194
|
/**
|
|
215
195
|
* Get list of connected devices
|
|
216
196
|
*/
|
|
@@ -218,14 +198,17 @@ export default class WebUsbTransport {
|
|
|
218
198
|
if (!this.usb) return [];
|
|
219
199
|
|
|
220
200
|
const devices = await this.usb.getDevices();
|
|
221
|
-
const onekeyDevices = devices.filter(dev =>
|
|
222
|
-
ONEKEY_WEBUSB_FILTER.some(
|
|
223
|
-
desc
|
|
224
|
-
|
|
225
|
-
|
|
201
|
+
const onekeyDevices = devices.filter(dev => {
|
|
202
|
+
const isOneKey = ONEKEY_WEBUSB_FILTER.some(
|
|
203
|
+
(desc: { vendorId: number; productId: number }) =>
|
|
204
|
+
dev.vendorId === desc.vendorId && dev.productId === desc.productId
|
|
205
|
+
);
|
|
206
|
+
const hasSerialNumber = typeof dev.serialNumber === 'string' && dev.serialNumber.length > 0;
|
|
207
|
+
return isOneKey && hasSerialNumber && !isKnownTrezorWebUsbDevice(dev);
|
|
208
|
+
});
|
|
226
209
|
|
|
227
210
|
this.deviceList = onekeyDevices.map(device => {
|
|
228
|
-
const path =
|
|
211
|
+
const path = device.serialNumber as string;
|
|
229
212
|
const protocolHint = inferProtocolHintFromDeviceName(device.productName);
|
|
230
213
|
if (protocolHint) {
|
|
231
214
|
this.deviceProtocolHints.set(path, protocolHint);
|
|
@@ -238,14 +221,6 @@ export default class WebUsbTransport {
|
|
|
238
221
|
};
|
|
239
222
|
});
|
|
240
223
|
|
|
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
224
|
return this.deviceList;
|
|
250
225
|
}
|
|
251
226
|
|
|
@@ -255,13 +230,16 @@ export default class WebUsbTransport {
|
|
|
255
230
|
async acquire(input: AcquireInput) {
|
|
256
231
|
if (!input.path) return;
|
|
257
232
|
try {
|
|
233
|
+
await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
|
|
258
234
|
await this.closeOpenDevice(input.path);
|
|
259
235
|
await this.connect(input.path ?? '', true);
|
|
260
236
|
const deviceName = this.deviceList.find(device => device.path === input.path)?.device
|
|
261
237
|
.productName;
|
|
262
238
|
const protocolHint = input.expectedProtocol
|
|
263
239
|
? undefined
|
|
264
|
-
:
|
|
240
|
+
: input.protocolHint ??
|
|
241
|
+
this.deviceProtocolHints.get(input.path) ??
|
|
242
|
+
inferProtocolHintFromDeviceName(deviceName);
|
|
265
243
|
if (protocolHint) {
|
|
266
244
|
this.deviceProtocolHints.set(input.path, protocolHint);
|
|
267
245
|
}
|
|
@@ -269,6 +247,7 @@ export default class WebUsbTransport {
|
|
|
269
247
|
return await Promise.resolve(input.path);
|
|
270
248
|
} catch (e) {
|
|
271
249
|
this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
|
|
250
|
+
await this.closeOpenDevice(input.path);
|
|
272
251
|
throw e;
|
|
273
252
|
}
|
|
274
253
|
}
|
|
@@ -285,13 +264,17 @@ export default class WebUsbTransport {
|
|
|
285
264
|
);
|
|
286
265
|
}
|
|
287
266
|
|
|
288
|
-
private
|
|
267
|
+
private createProtocolProbeTimeoutError(expected: ProtocolType, attempts: number) {
|
|
289
268
|
return ERRORS.TypedError(
|
|
290
269
|
HardwareErrorCode.RuntimeError,
|
|
291
|
-
|
|
270
|
+
`Protocol ${expected} probe timeout after ${attempts} attempts`
|
|
292
271
|
);
|
|
293
272
|
}
|
|
294
273
|
|
|
274
|
+
private createProtocolDetectionError() {
|
|
275
|
+
return ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
276
|
+
}
|
|
277
|
+
|
|
295
278
|
private async detectProtocol(
|
|
296
279
|
path: string,
|
|
297
280
|
expectedProtocol?: ProtocolType,
|
|
@@ -300,34 +283,56 @@ export default class WebUsbTransport {
|
|
|
300
283
|
if (expectedProtocol === 'V1') {
|
|
301
284
|
if (await this.probeProtocolV1(path)) {
|
|
302
285
|
this.deviceProtocol.set(path, 'V1');
|
|
303
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
|
|
304
286
|
return 'V1';
|
|
305
287
|
}
|
|
288
|
+
await this.closeConnectionAfterProbe(path);
|
|
306
289
|
throw this.createProtocolMismatchError(expectedProtocol);
|
|
307
290
|
}
|
|
308
291
|
|
|
309
292
|
if (expectedProtocol === 'V2') {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
293
|
+
for (let attempt = 1; attempt <= EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS; attempt += 1) {
|
|
294
|
+
if (await this.probeProtocolV2(path)) {
|
|
295
|
+
this.deviceProtocol.set(path, 'V2');
|
|
296
|
+
return 'V2';
|
|
297
|
+
}
|
|
298
|
+
if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
|
|
299
|
+
await this.resetConnectionAfterProbe(path);
|
|
300
|
+
this.Log?.debug(
|
|
301
|
+
`[WebUsbTransport] Protocol V2 probe timed out, retrying ${
|
|
302
|
+
attempt + 1
|
|
303
|
+
}/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`
|
|
304
|
+
);
|
|
305
|
+
} else {
|
|
306
|
+
await this.closeConnectionAfterProbe(path);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.deviceProtocol.delete(path);
|
|
310
|
+
throw this.createProtocolProbeTimeoutError(
|
|
311
|
+
expectedProtocol,
|
|
312
|
+
EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS
|
|
313
|
+
);
|
|
315
314
|
}
|
|
316
315
|
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
// 不能作为最终结论。
|
|
316
|
+
// Protocol must be actively probed after connection. Name, PID, and descriptors only
|
|
317
|
+
// influence probe order; a V2 hint probes V2 first and falls back to V1.
|
|
320
318
|
const probeOrder: ProtocolType[] =
|
|
321
319
|
protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
|
|
322
320
|
|
|
323
|
-
for (const protocol of probeOrder) {
|
|
321
|
+
for (const [index, protocol] of probeOrder.entries()) {
|
|
324
322
|
const detected =
|
|
325
323
|
protocol === 'V1' ? await this.probeProtocolV1(path) : await this.probeProtocolV2(path);
|
|
326
324
|
if (detected) {
|
|
327
325
|
this.deviceProtocol.set(path, protocol);
|
|
328
|
-
this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
|
|
329
326
|
return protocol;
|
|
330
327
|
}
|
|
328
|
+
if (index < probeOrder.length - 1) {
|
|
329
|
+
// A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
|
|
330
|
+
// reopening the device guarantees the next protocol probe cannot consume a
|
|
331
|
+
// late response from the previous protocol generation.
|
|
332
|
+
await this.resetConnectionAfterProbe(path);
|
|
333
|
+
} else {
|
|
334
|
+
await this.closeConnectionAfterProbe(path);
|
|
335
|
+
}
|
|
331
336
|
}
|
|
332
337
|
|
|
333
338
|
this.deviceProtocol.delete(path);
|
|
@@ -414,13 +419,6 @@ export default class WebUsbTransport {
|
|
|
414
419
|
*/
|
|
415
420
|
async connectToDevice(path: string, first: boolean) {
|
|
416
421
|
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
422
|
if (!device.opened) {
|
|
425
423
|
await device.open();
|
|
426
424
|
}
|
|
@@ -446,16 +444,12 @@ export default class WebUsbTransport {
|
|
|
446
444
|
// Discover endpoints from USB descriptors; descriptors are not used for protocol selection.
|
|
447
445
|
const endpoints = this.discoverEndpoints(device);
|
|
448
446
|
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
447
|
await device.claimInterface(endpoints.interfaceNumber);
|
|
453
448
|
await this.clearEndpointHalt(device, 'in', endpoints.endpointIn);
|
|
454
449
|
await this.clearEndpointHalt(device, 'out', endpoints.endpointOut);
|
|
455
450
|
}
|
|
456
451
|
|
|
457
452
|
private async closeOpenDevice(path: string) {
|
|
458
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
459
453
|
const current = this.deviceList.find(device => device.path === path)?.device;
|
|
460
454
|
if (!current?.opened) return;
|
|
461
455
|
|
|
@@ -650,27 +644,24 @@ export default class WebUsbTransport {
|
|
|
650
644
|
throw lastError;
|
|
651
645
|
}
|
|
652
646
|
|
|
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);
|
|
647
|
+
private async transferInOnce(path: string, length: number): Promise<DataView> {
|
|
648
|
+
const device = await this.findDevice(path);
|
|
649
|
+
if (!device.opened) {
|
|
650
|
+
throw new Error('USBDevice is not open for transferIn');
|
|
672
651
|
}
|
|
652
|
+
const endpoints = this.deviceEndpoints.get(path);
|
|
653
|
+
const endpointIn = endpoints?.endpointIn ?? this.endpointId;
|
|
654
|
+
const result = await device.transferIn(endpointIn, length);
|
|
655
|
+
return this.getTransferInData(result);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
private async closeConnectionAfterProbe(path: string) {
|
|
659
|
+
await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
|
|
660
|
+
await this.closeOpenDevice(path);
|
|
661
|
+
}
|
|
673
662
|
|
|
663
|
+
private async resetConnectionAfterProbe(path: string) {
|
|
664
|
+
await this.closeConnectionAfterProbe(path);
|
|
674
665
|
await this.getConnectedDevices();
|
|
675
666
|
await this.connect(path, false);
|
|
676
667
|
}
|
|
@@ -682,7 +673,6 @@ export default class WebUsbTransport {
|
|
|
682
673
|
protocol: ProtocolType,
|
|
683
674
|
onTimeout?: () => void
|
|
684
675
|
): Promise<T> {
|
|
685
|
-
void path;
|
|
686
676
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
687
677
|
let timedOut = false;
|
|
688
678
|
const waitForeverAfterTimeout = () => new Promise<never>(() => {});
|
|
@@ -702,6 +692,13 @@ export default class WebUsbTransport {
|
|
|
702
692
|
timer = setTimeout(async () => {
|
|
703
693
|
timedOut = true;
|
|
704
694
|
onTimeout?.();
|
|
695
|
+
if (protocol === 'V1') {
|
|
696
|
+
try {
|
|
697
|
+
await this.resetConnectionAfterProbe(path);
|
|
698
|
+
} catch (error) {
|
|
699
|
+
this.Log.debug('[WebUsbTransport] reset after Protocol V1 timeout failed:', error);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
705
702
|
reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
|
|
706
703
|
}, timeoutMs);
|
|
707
704
|
}),
|
|
@@ -717,10 +714,16 @@ export default class WebUsbTransport {
|
|
|
717
714
|
}
|
|
718
715
|
|
|
719
716
|
try {
|
|
720
|
-
await this.callProtocolV1(
|
|
717
|
+
await this.callProtocolV1(
|
|
718
|
+
path,
|
|
719
|
+
'Initialize',
|
|
720
|
+
{},
|
|
721
|
+
{
|
|
722
|
+
timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
|
|
723
|
+
}
|
|
724
|
+
);
|
|
721
725
|
return true;
|
|
722
|
-
} catch (
|
|
723
|
-
this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
|
|
726
|
+
} catch (_error) {
|
|
724
727
|
return false;
|
|
725
728
|
}
|
|
726
729
|
}
|
|
@@ -732,10 +735,9 @@ export default class WebUsbTransport {
|
|
|
732
735
|
|
|
733
736
|
return probeProtocolV2Helper({
|
|
734
737
|
call: (name, data, options) => this.callProtocolV2(path, name, data, options),
|
|
735
|
-
timeoutMs:
|
|
738
|
+
timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
|
|
736
739
|
logger: this.Log,
|
|
737
740
|
logPrefix: 'ProtocolV2 WebUSB',
|
|
738
|
-
onProbeFailed: () => this.resetConnectionAfterProbe(path),
|
|
739
741
|
});
|
|
740
742
|
}
|
|
741
743
|
|
|
@@ -765,12 +767,8 @@ export default class WebUsbTransport {
|
|
|
765
767
|
);
|
|
766
768
|
}
|
|
767
769
|
|
|
768
|
-
if (
|
|
769
|
-
|
|
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);
|
|
770
|
+
if (!shouldSuppressHighVolumeCallLog(name)) {
|
|
771
|
+
this.Log.debug('transport call', createTransportCallLog(name, protocol, data));
|
|
774
772
|
}
|
|
775
773
|
|
|
776
774
|
if (protocol === 'V2') {
|
|
@@ -820,83 +818,7 @@ export default class WebUsbTransport {
|
|
|
820
818
|
data: Record<string, unknown>,
|
|
821
819
|
options?: TransportCallOptions
|
|
822
820
|
) {
|
|
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;
|
|
821
|
+
return this.callProtocolV2Usb(path, name, data, options);
|
|
900
822
|
}
|
|
901
823
|
|
|
902
824
|
/**
|
|
@@ -951,18 +873,63 @@ export default class WebUsbTransport {
|
|
|
951
873
|
* Release device
|
|
952
874
|
*/
|
|
953
875
|
async release(path: string) {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
|
|
957
|
-
await device.releaseInterface(ifaceNum);
|
|
958
|
-
await device.close();
|
|
876
|
+
await this.invalidateProtocolV2UsbLink(path, 'WebUSB transport released');
|
|
877
|
+
await this.closeOpenDevice(path);
|
|
959
878
|
this.deviceProtocol.delete(path);
|
|
960
879
|
this.deviceProtocolHints.delete(path);
|
|
961
|
-
this.protocolV2Assemblers.get(path)?.reset();
|
|
962
|
-
this.protocolV2Assemblers.delete(path);
|
|
963
880
|
this.deviceEndpoints.delete(path);
|
|
964
881
|
}
|
|
965
882
|
|
|
883
|
+
protected getProtocolV2UsbSchemas(): ProtocolV2Schemas {
|
|
884
|
+
if (!this.messages || !this.messagesV2) {
|
|
885
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
protocolV1: this.messages,
|
|
889
|
+
protocolV2: this.messagesV2,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
protected getProtocolV2UsbLogger() {
|
|
894
|
+
return this.Log;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
protected async writeProtocolV2UsbPacket(
|
|
898
|
+
path: string,
|
|
899
|
+
frame: Uint8Array,
|
|
900
|
+
_context: ProtocolV2CallContext
|
|
901
|
+
): Promise<void> {
|
|
902
|
+
await this.transferOutOnce(path, frame);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
protected async readProtocolV2UsbPacket(
|
|
906
|
+
path: string,
|
|
907
|
+
_context: ProtocolV2CallContext
|
|
908
|
+
): Promise<Uint8Array> {
|
|
909
|
+
const dataView = await this.transferInOnce(path, PROTOCOL_V2_FRAME_MAX_BYTES);
|
|
910
|
+
return new Uint8Array(
|
|
911
|
+
this.toArrayBuffer(
|
|
912
|
+
dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)
|
|
913
|
+
)
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
protected async resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void> {
|
|
918
|
+
await this.closeOpenDevice(path);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
protected onProtocolV2UsbLinkInvalidated(path: string, reason: string) {
|
|
922
|
+
this.deviceProtocol.delete(path);
|
|
923
|
+
this.Log?.debug(`[WebUsbTransport] Protocol V2 link invalidated: ${path}`, reason);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error {
|
|
927
|
+
return new ProtocolV2LinkError(
|
|
928
|
+
'response-timeout',
|
|
929
|
+
`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
966
933
|
/**
|
|
967
934
|
* Expose the detected protocol type for a given device path.
|
|
968
935
|
* Used by upper layers (e.g. TransportManager) to select the correct schema.
|