@onekeyfe/hd-transport-web-device 1.2.0-alpha.2 → 1.2.0-alpha.20

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/src/webusb.ts CHANGED
@@ -1,19 +1,28 @@
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,
9
+ PROTOCOL_V2_WRITE_WATCHDOG_TIMEOUT_MS,
10
10
  ProtocolV2FrameAssembler,
11
+ ProtocolV2SequenceCursor,
11
12
  ProtocolV2Session,
12
13
  probeProtocolV2 as probeProtocolV2Helper,
13
14
  } from '@onekeyfe/hd-transport';
14
- import { ERRORS, HardwareErrorCode, ONEKEY_WEBUSB_FILTER, wait } from '@onekeyfe/hd-shared';
15
+ import {
16
+ ERRORS,
17
+ HardwareErrorCode,
18
+ ONEKEY_WEBUSB_FILTER,
19
+ isKnownTrezorWebUsbDevice,
20
+ wait,
21
+ } from '@onekeyfe/hd-shared';
15
22
  import ByteBuffer from 'bytebuffer';
16
23
 
24
+ import { createTransportCallLog, shouldSuppressHighVolumeCallLog } from './transportLog';
25
+
17
26
  import type {
18
27
  AcquireInput,
19
28
  OneKeyDeviceInfoBase,
@@ -33,22 +42,6 @@ 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 WEBUSB_FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
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
-
52
45
  function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
53
46
  return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
54
47
  }
@@ -84,22 +77,26 @@ export default class WebUsbTransport {
84
77
 
85
78
  private deviceProtocolHints: Map<string, ProtocolType> = new Map();
86
79
 
87
- /** 按设备缓存 Protocol V2 frame assembler,保留同一次读取里多出来的后续 frame。 */
80
+ /** Per-device Protocol V2 assembler that retains extra frames from one read. */
88
81
  private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
89
82
 
90
- /** 按设备缓存 Protocol V2 session,保持 seq 与设备会话一致递增。 */
83
+ /** Per-device Protocol V2 session that keeps sequence numbers monotonic. */
91
84
  private protocolV2Sessions: Map<string, ProtocolV2Session> = new Map();
92
85
 
93
- /** 当前 Protocol V2 调用的读取超时,由缓存 session 的 readFrame 闭包读取。 */
86
+ /** Sequence cursors survive ordinary reconnects and cached session rebuilds. */
87
+ private protocolV2Sequences: Map<string, ProtocolV2SequenceCursor> = new Map();
88
+
89
+ /** Read timeout for the current Protocol V2 call, consumed by cached readFrame. */
94
90
  private protocolV2ReadTimeouts: Map<string, number | undefined> = new Map();
95
91
 
92
+ private protocolV2WriteTimeoutMs = PROTOCOL_V2_WRITE_WATCHDOG_TIMEOUT_MS;
93
+
96
94
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
97
95
  private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
98
96
 
99
97
  /**
100
- * 早期 Pro2 工程板 USB descriptor 没有烧录 serial number。
101
- * 为这类设备生成会话内稳定的 mock path(同一 USBDevice 实例复用同一 path),
102
- * 避免设备因为空 serial 被发现流程整体丢弃。重新插拔后实例变化,path 会重新生成。
98
+ * Early Pro2 boards have no USB serial number. Assign a session-stable mock path per
99
+ * USBDevice instance so discovery retains them; reconnecting creates a new instance/path.
103
100
  */
104
101
  private mockSerialPaths: WeakMap<USBDevice, string> = new WeakMap();
105
102
 
@@ -159,7 +156,6 @@ export default class WebUsbTransport {
159
156
  this.messagesV2 = parseConfigure(signedData);
160
157
  this.protocolV2Sessions.clear();
161
158
  this.protocolV2ReadTimeouts.clear();
162
- this.Log?.debug('[WebUsbTransport] Protocol V2 schema configured');
163
159
  }
164
160
 
165
161
  /**
@@ -192,8 +188,7 @@ export default class WebUsbTransport {
192
188
  }
193
189
 
194
190
  /**
195
- * 设备 path:正常设备直接用 USB serial number;
196
- * 空 serial(早期工程板)回退到会话内稳定的 mock path。
191
+ * Use the USB serial as the device path, falling back to a session-stable mock path.
197
192
  */
198
193
  private getDevicePath(device: USBDevice): string {
199
194
  if (typeof device.serialNumber === 'string' && device.serialNumber.length > 0) {
@@ -218,11 +213,13 @@ export default class WebUsbTransport {
218
213
  if (!this.usb) return [];
219
214
 
220
215
  const devices = await this.usb.getDevices();
221
- const onekeyDevices = devices.filter(dev =>
222
- ONEKEY_WEBUSB_FILTER.some(
223
- desc => dev.vendorId === desc.vendorId && dev.productId === desc.productId
224
- )
225
- );
216
+ const onekeyDevices = devices.filter(dev => {
217
+ const isOneKey = ONEKEY_WEBUSB_FILTER.some(
218
+ (desc: { vendorId: number; productId: number }) =>
219
+ dev.vendorId === desc.vendorId && dev.productId === desc.productId
220
+ );
221
+ return isOneKey && !isKnownTrezorWebUsbDevice(dev);
222
+ });
226
223
 
227
224
  this.deviceList = onekeyDevices.map(device => {
228
225
  const path = this.getDevicePath(device);
@@ -238,14 +235,6 @@ export default class WebUsbTransport {
238
235
  };
239
236
  });
240
237
 
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
238
  return this.deviceList;
250
239
  }
251
240
 
@@ -300,23 +289,21 @@ export default class WebUsbTransport {
300
289
  if (expectedProtocol === 'V1') {
301
290
  if (await this.probeProtocolV1(path)) {
302
291
  this.deviceProtocol.set(path, 'V1');
303
- this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V1 (expected)`);
304
292
  return 'V1';
305
293
  }
294
+ await this.resetConnectionAfterProbe(path);
306
295
  throw this.createProtocolMismatchError(expectedProtocol);
307
296
  }
308
297
 
309
298
  if (expectedProtocol === 'V2') {
310
- // 免探测路径:调用方显式承诺该设备是 V2(例如固件升级重启后的重连场景,
311
- // 上层已经探测过协议并通过 expectedProtocol 传回),这里不再重复探测。
299
+ // Skip probing when the caller explicitly confirms V2, such as reconnect after a
300
+ // firmware reboot where expectedProtocol carries the previously probed result.
312
301
  this.deviceProtocol.set(path, 'V2');
313
- this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> V2 (expected)`);
314
302
  return 'V2';
315
303
  }
316
304
 
317
- // 项目约束:协议判断必须在连接后主动探测,不能依赖设备名/PID/descriptor。
318
- // 设备名 hint(如 "Pro 2")只用于调整探测顺序:hint=V2 时先探 V2、失败回落 V1,
319
- // 不能作为最终结论。
305
+ // Protocol must be actively probed after connection. Name, PID, and descriptors only
306
+ // influence probe order; a V2 hint probes V2 first and falls back to V1.
320
307
  const probeOrder: ProtocolType[] =
321
308
  protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
322
309
 
@@ -325,9 +312,14 @@ export default class WebUsbTransport {
325
312
  protocol === 'V1' ? await this.probeProtocolV1(path) : await this.probeProtocolV2(path);
326
313
  if (detected) {
327
314
  this.deviceProtocol.set(path, protocol);
328
- this.Log.debug(`[WebUsbTransport] detectProtocol: path=${path} -> ${protocol}`);
329
315
  return protocol;
330
316
  }
317
+ if (protocol === 'V1') {
318
+ // A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
319
+ // reopening the device guarantees the next protocol probe cannot consume a
320
+ // late V1 response. V2 timeout recovery is owned by callProtocolV2.
321
+ await this.resetConnectionAfterProbe(path);
322
+ }
331
323
  }
332
324
 
333
325
  this.deviceProtocol.delete(path);
@@ -414,13 +406,6 @@ export default class WebUsbTransport {
414
406
  */
415
407
  async connectToDevice(path: string, first: boolean) {
416
408
  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
409
  if (!device.opened) {
425
410
  await device.open();
426
411
  }
@@ -571,16 +556,7 @@ export default class WebUsbTransport {
571
556
  let lastError: unknown;
572
557
  for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
573
558
  try {
574
- const device = await this.findDevice(path);
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);
559
+ await this.transferOutOnce(path, packet);
584
560
  return;
585
561
  } catch (error) {
586
562
  lastError = error;
@@ -603,6 +579,19 @@ export default class WebUsbTransport {
603
579
  throw lastError;
604
580
  }
605
581
 
582
+ private async transferOutOnce(path: string, packet: Uint8Array) {
583
+ const device = await this.findDevice(path);
584
+ if (!device.opened) {
585
+ await this.connect(path, false);
586
+ }
587
+ const endpoints = this.deviceEndpoints.get(path);
588
+ const endpointOut = endpoints?.endpointOut ?? this.endpointId;
589
+ const transferBuffer = this.toArrayBuffer(
590
+ packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
591
+ );
592
+ await device.transferOut(endpointOut, transferBuffer);
593
+ }
594
+
606
595
  private async transferInWithRetry(
607
596
  path: string,
608
597
  length: number,
@@ -672,13 +661,12 @@ export default class WebUsbTransport {
672
661
  }
673
662
 
674
663
  private async withProtocolReadTimeout<T>(
675
- path: string,
664
+ _path: string,
676
665
  promise: Promise<T>,
677
666
  timeoutMs: number,
678
667
  protocol: ProtocolType,
679
668
  onTimeout?: () => void
680
669
  ): Promise<T> {
681
- void path;
682
670
  let timer: ReturnType<typeof setTimeout> | undefined;
683
671
  let timedOut = false;
684
672
  const waitForeverAfterTimeout = () => new Promise<never>(() => {});
@@ -695,7 +683,7 @@ export default class WebUsbTransport {
695
683
  return await Promise.race([
696
684
  guardedPromise,
697
685
  new Promise<never>((_, reject) => {
698
- timer = setTimeout(async () => {
686
+ timer = setTimeout(() => {
699
687
  timedOut = true;
700
688
  onTimeout?.();
701
689
  reject(new Error(`Protocol ${protocol} read timeout after ${timeoutMs}ms`));
@@ -715,8 +703,7 @@ export default class WebUsbTransport {
715
703
  try {
716
704
  await this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
717
705
  return true;
718
- } catch (error) {
719
- this.Log.debug('[WebUsbTransport] Protocol V1 Initialize probe failed:', error);
706
+ } catch (_error) {
720
707
  return false;
721
708
  }
722
709
  }
@@ -731,7 +718,6 @@ export default class WebUsbTransport {
731
718
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
732
719
  logger: this.Log,
733
720
  logPrefix: 'ProtocolV2 WebUSB',
734
- onProbeFailed: () => this.resetConnectionAfterProbe(path),
735
721
  });
736
722
  }
737
723
 
@@ -761,12 +747,8 @@ export default class WebUsbTransport {
761
747
  );
762
748
  }
763
749
 
764
- if (shouldSuppressWebUsbCallLog(name)) {
765
- // 高频文件写入不要逐包发 debug 事件,否则浏览器侧会被日志处理拖慢。
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);
750
+ if (!shouldSuppressHighVolumeCallLog(name)) {
751
+ this.Log.debug('transport call', createTransportCallLog(name, protocol));
770
752
  }
771
753
 
772
754
  if (protocol === 'V2') {
@@ -829,13 +811,20 @@ export default class WebUsbTransport {
829
811
 
830
812
  let session = this.protocolV2Sessions.get(path);
831
813
  if (!session) {
814
+ let sequenceCursor = this.protocolV2Sequences.get(path);
815
+ if (!sequenceCursor) {
816
+ sequenceCursor = new ProtocolV2SequenceCursor();
817
+ this.protocolV2Sequences.set(path, sequenceCursor);
818
+ }
832
819
  session = new ProtocolV2Session({
833
820
  schemas: {
834
821
  protocolV1: protocolV1Messages,
835
822
  protocolV2: this.messagesV2,
836
823
  },
837
824
  router: PROTOCOL_V2_CHANNEL_USB,
838
- writeFrame: (frame: Uint8Array) => this.transferOutWithRetry(path, frame),
825
+ sequenceCursor,
826
+ writeTimeoutMs: this.protocolV2WriteTimeoutMs,
827
+ writeFrame: (frame: Uint8Array) => this.transferOutOnce(path, frame),
839
828
  readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
840
829
  logger: this.Log,
841
830
  logPrefix: 'ProtocolV2 WebUSB',
@@ -849,6 +838,22 @@ export default class WebUsbTransport {
849
838
  this.protocolV2Assemblers.get(path)?.reset();
850
839
  try {
851
840
  return await session.call(name, data, options);
841
+ } catch (error) {
842
+ const message =
843
+ error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
844
+ if (
845
+ message.includes('protocol v2 read timeout') ||
846
+ message.includes('protocol v2 write timeout') ||
847
+ message.includes('protocol v2 delivery timeout') ||
848
+ message.includes('response timeout')
849
+ ) {
850
+ try {
851
+ await this.resetConnectionAfterProbe(path);
852
+ } catch (resetError) {
853
+ this.Log.debug('[WebUsbTransport] Protocol V2 timeout reset failed:', resetError);
854
+ }
855
+ }
856
+ throw error;
852
857
  } finally {
853
858
  this.protocolV2ReadTimeouts.delete(path);
854
859
  }
@@ -956,6 +961,8 @@ export default class WebUsbTransport {
956
961
  this.deviceProtocolHints.delete(path);
957
962
  this.protocolV2Assemblers.get(path)?.reset();
958
963
  this.protocolV2Assemblers.delete(path);
964
+ this.protocolV2Sessions.delete(path);
965
+ this.protocolV2ReadTimeouts.delete(path);
959
966
  this.deviceEndpoints.delete(path);
960
967
  }
961
968