@onekeyfe/hd-transport-web-device 1.2.0-alpha.19 → 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.
@@ -16,6 +16,29 @@ const schema = {
16
16
  };
17
17
 
18
18
  describe('WebUsbTransport Protocol V2 timeout recovery', () => {
19
+ test('resets the connection between a failed V1 probe and the V2 probe', async () => {
20
+ const webusb = new WebUsbTransport() as any;
21
+ const path = 'pro2-webusb';
22
+ const events: string[] = [];
23
+ webusb.probeProtocolV1 = jest.fn().mockImplementation(() => {
24
+ events.push('probe-v1');
25
+ return Promise.resolve(false);
26
+ });
27
+ webusb.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
28
+ events.push('reset');
29
+ return Promise.resolve();
30
+ });
31
+ webusb.probeProtocolV2 = jest.fn().mockImplementation(() => {
32
+ events.push('probe-v2');
33
+ return Promise.resolve(true);
34
+ });
35
+
36
+ await expect(webusb.detectProtocol(path)).resolves.toBe('V2');
37
+
38
+ expect(events).toEqual(['probe-v1', 'reset', 'probe-v2']);
39
+ expect(webusb.deviceProtocol.get(path)).toBe('V2');
40
+ });
41
+
19
42
  test('invalidates and resets the cached connection before another call can start', async () => {
20
43
  const webusb = new WebUsbTransport() as any;
21
44
  const path = 'pro2-webusb';
@@ -23,8 +46,8 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
23
46
  webusb.messagesV2 = transport.parseConfigure(schema);
24
47
  webusb.protocolV2Assemblers.set(path, new ProtocolV2FrameAssembler());
25
48
  webusb.transferOutOnce = jest.fn().mockResolvedValue(undefined);
26
- webusb.receiveProtocolV2Frame = jest.fn(() => new Promise(() => undefined));
27
- webusb.resetConnectionAfterProbe = jest.fn().mockImplementation(async () => {
49
+ webusb.receiveProtocolV2Frame = jest.fn(() => new Promise<void>(() => {}));
50
+ webusb.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
28
51
  webusb.protocolV2Sessions.delete(path);
29
52
  webusb.protocolV2ReadTimeouts.delete(path);
30
53
  webusb.protocolV2Assemblers.get(path)?.reset();
@@ -37,4 +60,29 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
37
60
  expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledWith(path);
38
61
  expect(webusb.protocolV2Sessions.has(path)).toBe(false);
39
62
  });
63
+
64
+ test('resets a stalled write while retaining the device sequence cursor', async () => {
65
+ const webusb = new WebUsbTransport() as any;
66
+ const path = 'pro2-webusb';
67
+ webusb.messages = transport.parseConfigure(schema);
68
+ webusb.messagesV2 = transport.parseConfigure(schema);
69
+ webusb.protocolV2WriteTimeoutMs = 10;
70
+ webusb.protocolV2Assemblers.set(path, new ProtocolV2FrameAssembler());
71
+ webusb.transferOutOnce = jest.fn(() => new Promise(() => {}));
72
+ webusb.receiveProtocolV2Frame = jest.fn();
73
+ webusb.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
74
+ webusb.protocolV2Sessions.delete(path);
75
+ webusb.protocolV2ReadTimeouts.delete(path);
76
+ webusb.protocolV2Assemblers.get(path)?.reset();
77
+ });
78
+
79
+ await expect(
80
+ webusb.callProtocolV2(path, 'Ping', { message: 'timeout' }, { timeoutMs: 10 })
81
+ ).rejects.toThrow('Protocol V2 write timeout after 10ms for Ping');
82
+
83
+ expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledWith(path);
84
+ expect(webusb.protocolV2Sequences.has(path)).toBe(true);
85
+ expect(webusb.protocolV2Sessions.has(path)).toBe(false);
86
+ expect(webusb.receiveProtocolV2Frame).not.toHaveBeenCalled();
87
+ });
40
88
  });
@@ -1 +1 @@
1
- {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AAoBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnG,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AAIvC,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,CAAC,EAAE,UAAU,CAAC;KACzB;CACF;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,YAAY,CAAC;CACjC,CAAC;AAqCF,MAAM,CAAC,OAAO,OAAO,oBAAoB;IACvC,OAAO,CAAC,SAAS,CAA0D;IAE3E,OAAO,CAAC,WAAW,CAA0D;IAE7E,IAAI,SAA0B;IAE9B,UAAU,UAAS;IAEnB,UAAU,EAAE,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CAAQ;IAExD,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,SAAS,CAAsE;IAEvF,OAAO,CAAC,YAAY,CAAoD;IAExE,OAAO,CAAC,aAAa,CAAwC;IAE7D,OAAO,CAAC,eAAe,CAAgD;IAEvE,OAAO,CAAC,eAAe,CAmBpB;IAEH,OAAO,CAAC,oBAAoB,CAAsC;IAElE,OAAO,CAAC,kBAAkB,CAAsC;IAEhE,OAAO,CAAC,kBAAkB,CAAkC;IAE5D,OAAO,CAAC,qBAAqB,CAAK;IAElC,OAAO,CAAC,oBAAoB;IA+B5B,OAAO,CAAC,kBAAkB;IA0B1B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAcxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAKzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAO7B,MAAM;IAIN,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqBxC,OAAO,CAAC,KAAK,EAAE,eAAe;;;;;;;;;;;IA0F9B,OAAO,CAAC,EAAE,EAAE,MAAM;IAgBxB,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;IA+C5B,OAAO,CAAC,8BAA8B;YAgBxB,iCAAiC;YAqCjC,eAAe;YAgBf,eAAe;YAuBf,iBAAiB;YAsBjB,SAAS;IASvB,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,4BAA4B;IAkBpC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,sBAAsB;YAShB,mBAAmB;IAiBjC,OAAO,CAAC,4BAA4B;IAgB9B,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA2BlB,cAAc;YAuEd,cAAc;IA6B5B,OAAO,CAAC,uBAAuB;IA0C/B,OAAO,CAAC,6BAA6B;IAsCrC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
1
+ {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AAoBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnG,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AAIvC,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,CAAC,EAAE,UAAU,CAAC;KACzB;CACF;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,gBAAgB,CAAC,EAAE,YAAY,CAAC;CACjC,CAAC;AAqCF,MAAM,CAAC,OAAO,OAAO,oBAAoB;IACvC,OAAO,CAAC,SAAS,CAA0D;IAE3E,OAAO,CAAC,WAAW,CAA0D;IAE7E,IAAI,SAA0B;IAE9B,UAAU,UAAS;IAEnB,UAAU,EAAE,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CAAQ;IAExD,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,SAAS,CAAsE;IAEvF,OAAO,CAAC,YAAY,CAAoD;IAExE,OAAO,CAAC,aAAa,CAAwC;IAE7D,OAAO,CAAC,eAAe,CAAgD;IAEvE,OAAO,CAAC,eAAe,CAmBpB;IAEH,OAAO,CAAC,oBAAoB,CAAsC;IAElE,OAAO,CAAC,kBAAkB,CAAsC;IAEhE,OAAO,CAAC,kBAAkB,CAAkC;IAE5D,OAAO,CAAC,qBAAqB,CAAK;IAElC,OAAO,CAAC,oBAAoB;IA+B5B,OAAO,CAAC,kBAAkB;IA0B1B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAcxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAKzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAO7B,MAAM;IAIN,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqBxC,OAAO,CAAC,KAAK,EAAE,eAAe;;;;;;;;;;;IA0F9B,OAAO,CAAC,EAAE,EAAE,MAAM;IAgBxB,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;IA8C5B,OAAO,CAAC,8BAA8B;YAgBxB,iCAAiC;YAqCjC,eAAe;YAgBf,eAAe;YAuBf,iBAAiB;YAsBjB,SAAS;IASvB,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,4BAA4B;IAkBpC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,sBAAsB;YAShB,mBAAmB;IAiBjC,OAAO,CAAC,4BAA4B;IAgB9B,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA2BlB,cAAc;YAuEd,cAAc;IA6B5B,OAAO,CAAC,uBAAuB;IA0C/B,OAAO,CAAC,6BAA6B;IAsCrC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,9 @@ import { Deferred } from '@onekeyfe/hd-shared';
4
4
  import { DesktopAPI } from '@onekeyfe/hd-transport-electron';
5
5
  import EventEmitter from 'events';
6
6
 
7
+ /**
8
+ * Device information with path and WebUSB device instance
9
+ */
7
10
  interface DeviceInfo extends OneKeyDeviceInfoBase {
8
11
  path: string;
9
12
  device: USBDevice;
@@ -11,13 +14,26 @@ interface DeviceInfo extends OneKeyDeviceInfoBase {
11
14
  }
12
15
  declare class WebUsbTransport {
13
16
  messages: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
17
+ /** Protobuf schema for Protocol V2 transports. */
14
18
  messagesV2: ReturnType<typeof _onekeyfe_hd_transport__default.parseConfigure> | undefined;
19
+ /** Per-path protocol type detected by active wire-level probe. */
15
20
  private deviceProtocol;
16
21
  private deviceProtocolHints;
22
+ /** Per-device Protocol V2 assembler that retains extra frames from one read. */
17
23
  private protocolV2Assemblers;
24
+ /** Per-device Protocol V2 session that keeps sequence numbers monotonic. */
18
25
  private protocolV2Sessions;
26
+ /** Sequence cursors survive ordinary reconnects and cached session rebuilds. */
27
+ private protocolV2Sequences;
28
+ /** Read timeout for the current Protocol V2 call, consumed by cached readFrame. */
19
29
  private protocolV2ReadTimeouts;
30
+ private protocolV2WriteTimeoutMs;
31
+ /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
20
32
  private deviceEndpoints;
33
+ /**
34
+ * Early Pro2 boards have no USB serial number. Assign a session-stable mock path per
35
+ * USBDevice instance so discovery retains them; reconnecting creates a new instance/path.
36
+ */
21
37
  private mockSerialPaths;
22
38
  private mockSerialCounter;
23
39
  name: string;
@@ -25,24 +41,75 @@ declare class WebUsbTransport {
25
41
  configured: boolean;
26
42
  Log?: any;
27
43
  usb?: USB;
44
+ /**
45
+ * Cached list of connected devices
46
+ * This is essential for maintaining device references between operations
47
+ */
28
48
  deviceList: Array<DeviceInfo>;
29
49
  configurationId: number;
30
50
  endpointId: number;
31
51
  interfaceId: number;
52
+ /**
53
+ * Initialize WebUSB transport
54
+ */
32
55
  init(logger: any): void;
56
+ /**
57
+ * Configure Protocol V1 protobuf schema (legacy chunked 0x3F framing).
58
+ */
33
59
  configure(signedData: any): void;
60
+ /**
61
+ * Cache the Protocol V2 protobuf schema.
62
+ */
34
63
  configureProtocolV2(signedData: any): void;
64
+ /**
65
+ * Request user to select a device
66
+ * This method must be called in response to a user action
67
+ * to comply with WebUSB security requirements
68
+ */
35
69
  promptDeviceAccess(): Promise<USBDevice | null>;
70
+ /**
71
+ * Enumerate already connected devices
72
+ * This method only returns devices that are already authorized by the browser
73
+ * It does NOT prompt the user to select a device
74
+ */
36
75
  enumerate(): Promise<DeviceInfo[]>;
76
+ /**
77
+ * Use the USB serial as the device path, falling back to a session-stable mock path.
78
+ */
37
79
  private getDevicePath;
80
+ /**
81
+ * Get list of connected devices
82
+ */
38
83
  getConnectedDevices(): Promise<DeviceInfo[]>;
84
+ /**
85
+ * Acquire device control
86
+ */
39
87
  acquire(input: AcquireInput): Promise<string | undefined>;
88
+ /**
89
+ * Determine protocol type after connect.
90
+ * Probe Protocol V1 first with Initialize. If it does not answer in time,
91
+ * fall back to a Protocol V2 Ping probe.
92
+ */
40
93
  private createProtocolMismatchError;
41
94
  private createProtocolDetectionError;
42
95
  private detectProtocol;
96
+ /**
97
+ * Find device by path
98
+ */
43
99
  findDevice(path: string): Promise<USBDevice>;
100
+ /**
101
+ * Connect to device with retry mechanism
102
+ */
44
103
  connect(path: string, first: boolean): Promise<void>;
104
+ /**
105
+ * Discover vendor-class (0xFF) interface and its IN/OUT endpoint numbers from USB descriptors.
106
+ * Falls back to legacy hardcoded values if no vendor interface is found.
107
+ */
45
108
  private discoverEndpoints;
109
+ /**
110
+ * Connect to specific device.
111
+ * Discovers interface/endpoint numbers from USB descriptors on first connection.
112
+ */
46
113
  connectToDevice(path: string, first: boolean): Promise<void>;
47
114
  private closeOpenDevice;
48
115
  private clearEndpointHalt;
@@ -59,12 +126,31 @@ declare class WebUsbTransport {
59
126
  private withProtocolReadTimeout;
60
127
  private probeProtocolV1;
61
128
  private probeProtocolV2;
129
+ /**
130
+ * Call device method — branches to Protocol V1 or Protocol V2 based on active probe.
131
+ */
62
132
  call(path: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<_onekeyfe_hd_transport.MessageFromOneKey>;
63
133
  private callProtocolV1;
134
+ /**
135
+ * Send/receive a single call over Protocol V2 (0x5A framing).
136
+ *
137
+ * Encoding: protobuf message → 2-byte LE messageTypeId + pb bytes → Protocol V2 frame
138
+ * Decoding: Protocol V2 frame → messageTypeId + pb bytes → protobuf message
139
+ */
64
140
  private callProtocolV2;
65
141
  private receiveProtocolV2Frame;
142
+ /**
143
+ * Receive data from device
144
+ */
66
145
  receiveData(path: string, timeoutMs?: number): Promise<string>;
146
+ /**
147
+ * Release device
148
+ */
67
149
  release(path: string): Promise<void>;
150
+ /**
151
+ * Expose the detected protocol type for a given device path.
152
+ * Used by upper layers (e.g. TransportManager) to select the correct schema.
153
+ */
68
154
  getProtocolType(path: string): ProtocolType | undefined;
69
155
  }
70
156
 
@@ -78,6 +164,12 @@ type BleAcquireInput = {
78
164
  forceCleanRunPromise?: boolean;
79
165
  expectedProtocol?: ProtocolType;
80
166
  };
167
+ /**
168
+ * Desktop Electron BLE transport with automatic Protocol V1/V2 detection.
169
+ *
170
+ * Protocol V1 devices continue using chunked packets. Protocol V2 is detected
171
+ * after a Protocol V1 Initialize timeout by probing Protocol V2 Ping.
172
+ */
81
173
  declare class ElectronBleTransport {
82
174
  private _messages;
83
175
  private _messagesV2;
package/dist/index.js CHANGED
@@ -69,7 +69,9 @@ class WebUsbTransport {
69
69
  this.deviceProtocolHints = new Map();
70
70
  this.protocolV2Assemblers = new Map();
71
71
  this.protocolV2Sessions = new Map();
72
+ this.protocolV2Sequences = new Map();
72
73
  this.protocolV2ReadTimeouts = new Map();
74
+ this.protocolV2WriteTimeoutMs = transport.PROTOCOL_V2_WRITE_WATCHDOG_TIMEOUT_MS;
73
75
  this.deviceEndpoints = new Map();
74
76
  this.mockSerialPaths = new WeakMap();
75
77
  this.mockSerialCounter = 0;
@@ -194,6 +196,7 @@ class WebUsbTransport {
194
196
  this.deviceProtocol.set(path, 'V1');
195
197
  return 'V1';
196
198
  }
199
+ yield this.resetConnectionAfterProbe(path);
197
200
  throw this.createProtocolMismatchError(expectedProtocol);
198
201
  }
199
202
  if (expectedProtocol === 'V2') {
@@ -207,6 +210,9 @@ class WebUsbTransport {
207
210
  this.deviceProtocol.set(path, protocol);
208
211
  return protocol;
209
212
  }
213
+ if (protocol === 'V1') {
214
+ yield this.resetConnectionAfterProbe(path);
215
+ }
210
216
  }
211
217
  this.deviceProtocol.delete(path);
212
218
  throw this.createProtocolDetectionError();
@@ -565,7 +571,6 @@ class WebUsbTransport {
565
571
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
566
572
  logger: this.Log,
567
573
  logPrefix: 'ProtocolV2 WebUSB',
568
- onProbeFailed: () => this.resetConnectionAfterProbe(path),
569
574
  });
570
575
  });
571
576
  }
@@ -624,12 +629,19 @@ class WebUsbTransport {
624
629
  }
625
630
  let session = this.protocolV2Sessions.get(path);
626
631
  if (!session) {
632
+ let sequenceCursor = this.protocolV2Sequences.get(path);
633
+ if (!sequenceCursor) {
634
+ sequenceCursor = new transport.ProtocolV2SequenceCursor();
635
+ this.protocolV2Sequences.set(path, sequenceCursor);
636
+ }
627
637
  session = new transport.ProtocolV2Session({
628
638
  schemas: {
629
639
  protocolV1: protocolV1Messages,
630
640
  protocolV2: this.messagesV2,
631
641
  },
632
642
  router: transport.PROTOCOL_V2_CHANNEL_USB,
643
+ sequenceCursor,
644
+ writeTimeoutMs: this.protocolV2WriteTimeoutMs,
633
645
  writeFrame: (frame) => this.transferOutOnce(path, frame),
634
646
  readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
635
647
  logger: this.Log,
@@ -645,7 +657,10 @@ class WebUsbTransport {
645
657
  }
646
658
  catch (error) {
647
659
  const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
648
- if (message.includes('protocol v2 read timeout') || message.includes('response timeout')) {
660
+ if (message.includes('protocol v2 read timeout') ||
661
+ message.includes('protocol v2 write timeout') ||
662
+ message.includes('protocol v2 delivery timeout') ||
663
+ message.includes('response timeout')) {
649
664
  try {
650
665
  yield this.resetConnectionAfterProbe(path);
651
666
  }
package/dist/webusb.d.ts CHANGED
@@ -13,7 +13,9 @@ export default class WebUsbTransport {
13
13
  private deviceProtocolHints;
14
14
  private protocolV2Assemblers;
15
15
  private protocolV2Sessions;
16
+ private protocolV2Sequences;
16
17
  private protocolV2ReadTimeouts;
18
+ private protocolV2WriteTimeoutMs;
17
19
  private deviceEndpoints;
18
20
  private mockSerialPaths;
19
21
  private mockSerialCounter;
@@ -1 +1 @@
1
- {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";AACA,OAAO,SAUN,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAqBhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAaD,MAAM,CAAC,OAAO,OAAO,eAAe;IAClC,QAAQ,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAGlE,UAAU,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAGpE,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAGnE,OAAO,CAAC,oBAAoB,CAAoD;IAGhF,OAAO,CAAC,kBAAkB,CAA6C;IAGvE,OAAO,CAAC,sBAAsB,CAA8C;IAG5E,OAAO,CAAC,eAAe,CAA2C;IAOlE,OAAO,CAAC,eAAe,CAA6C;IAEpE,OAAO,CAAC,iBAAiB,CAAK;IAE9B,IAAI,SAAqB;IAEzB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,GAAG,CAAC,EAAE,GAAG,CAAC;IAMV,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAM;IAEnC,eAAe,SAAoB;IAEnC,UAAU,SAAe;IAEzB,WAAW,SAAgB;IAK3B,IAAI,CAAC,MAAM,EAAE,GAAG;IAgBhB,SAAS,CAAC,UAAU,EAAE,GAAG;IASzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAW7B,kBAAkB;IAmBlB,SAAS;IASf,OAAO,CAAC,aAAa;IAmBf,mBAAmB;IAgCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA0BjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;IA0CtB,UAAU,CAAC,IAAI,EAAE,MAAM;IAwBvB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAkB1C,OAAO,CAAC,iBAAiB;IAiCnB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;YAmCpC,eAAe;YAmBf,iBAAiB;IAezB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIvE,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,wBAAwB;YAalB,yBAAyB;IAiCvC,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,aAAa;YASP,oBAAoB;YA2BpB,eAAe;YAaf,mBAAmB;YA2CnB,yBAAyB;YAyBzB,uBAAuB;YAmCvB,eAAe;YAaf,eAAe;IAiBvB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA8BlB,cAAc;YAkCd,cAAc;YAuDd,sBAAsB;IA4C9B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAmB1B,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
1
+ {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";AACA,OAAO,SAYN,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAqBhC,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAaD,MAAM,CAAC,OAAO,OAAO,eAAe;IAClC,QAAQ,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAGlE,UAAU,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAGpE,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAGnE,OAAO,CAAC,oBAAoB,CAAoD;IAGhF,OAAO,CAAC,kBAAkB,CAA6C;IAGvE,OAAO,CAAC,mBAAmB,CAAoD;IAG/E,OAAO,CAAC,sBAAsB,CAA8C;IAE5E,OAAO,CAAC,wBAAwB,CAAyC;IAGzE,OAAO,CAAC,eAAe,CAA2C;IAMlE,OAAO,CAAC,eAAe,CAA6C;IAEpE,OAAO,CAAC,iBAAiB,CAAK;IAE9B,IAAI,SAAqB;IAEzB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,GAAG,CAAC,EAAE,GAAG,CAAC;IAMV,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAM;IAEnC,eAAe,SAAoB;IAEnC,UAAU,SAAe;IAEzB,WAAW,SAAgB;IAK3B,IAAI,CAAC,MAAM,EAAE,GAAG;IAgBhB,SAAS,CAAC,UAAU,EAAE,GAAG;IASzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAW7B,kBAAkB;IAmBlB,SAAS;IAQf,OAAO,CAAC,aAAa;IAmBf,mBAAmB;IAgCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA0BjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;IAgDtB,UAAU,CAAC,IAAI,EAAE,MAAM;IAwBvB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAkB1C,OAAO,CAAC,iBAAiB;IAiCnB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;YAmCpC,eAAe;YAmBf,iBAAiB;IAezB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIvE,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,wBAAwB;YAalB,yBAAyB;IAiCvC,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,aAAa;YASP,oBAAoB;YA2BpB,eAAe;YAaf,mBAAmB;YA2CnB,yBAAyB;YAyBzB,uBAAuB;YAmCvB,eAAe;YAaf,eAAe;IAgBvB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YA8BlB,cAAc;YAkCd,cAAc;YAmEd,sBAAsB;IA4C9B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAmB1B,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-web-device",
3
- "version": "1.2.0-alpha.19",
3
+ "version": "1.2.0-alpha.20",
4
4
  "author": "OneKey",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -20,13 +20,13 @@
20
20
  "lint:fix": "eslint . --fix"
21
21
  },
22
22
  "dependencies": {
23
- "@onekeyfe/hd-shared": "1.2.0-alpha.19",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.19"
23
+ "@onekeyfe/hd-shared": "1.2.0-alpha.20",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.20"
25
25
  },
26
26
  "devDependencies": {
27
- "@onekeyfe/hd-transport-electron": "1.2.0-alpha.19",
27
+ "@onekeyfe/hd-transport-electron": "1.2.0-alpha.20",
28
28
  "@types/w3c-web-usb": "^1.0.6",
29
29
  "@types/web-bluetooth": "^0.0.17"
30
30
  },
31
- "gitHead": "4e79f6a400463f342285b9a761b6b84523f1d6b9"
31
+ "gitHead": "5fbc1ada90fd3cfda7e5ef08be368cb452018733"
32
32
  }
@@ -379,23 +379,22 @@ export default class ElectronBleTransport {
379
379
  }
380
380
 
381
381
  if (expectedProtocol === 'V2') {
382
- // 免探测路径:调用方显式承诺该设备是 V2(例如固件升级重启后的重连场景,
383
- // 上层已经探测过协议并通过 expectedProtocol 传回),这里不再重复探测。
382
+ // Skip probing when the caller explicitly confirms V2, such as reconnect after a
383
+ // firmware reboot where expectedProtocol carries the previously probed result.
384
384
  this.deviceProtocol.set(uuid, 'V2');
385
385
  this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
386
386
  return 'V2';
387
387
  }
388
388
 
389
- // 项目约束:协议判断必须在连接后主动探测,不能依赖设备名/PID/descriptor。
390
- // 设备名 hint(如 "Pro 2")只用于调整探测顺序:hint=V2 时先探 V2、失败回落 V1
391
- // 不能作为最终结论。
389
+ // Protocol must be actively probed after connection. Name, PID, and descriptors only
390
+ // influence probe order; a V2 hint probes V2 first and falls back to V1.
392
391
  const probeOrder: ProtocolType[] =
393
392
  protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
394
393
 
395
394
  for (let i = 0; i < probeOrder.length; i += 1) {
396
395
  const protocol = probeOrder[i];
397
396
  if (i > 0) {
398
- // 上一个协议探测失败后,重置订阅与缓冲,避免残留数据干扰下一个协议的探测。
397
+ // Reset subscriptions and buffers after a failed probe before trying another protocol.
399
398
  await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
400
399
  }
401
400
  const detected =
package/src/webusb.ts CHANGED
@@ -6,7 +6,9 @@ import transport, {
6
6
  PROTOCOL_V1_USB_PACKET_SIZE,
7
7
  PROTOCOL_V2_CHANNEL_USB,
8
8
  PROTOCOL_V2_FRAME_MAX_BYTES,
9
+ PROTOCOL_V2_WRITE_WATCHDOG_TIMEOUT_MS,
9
10
  ProtocolV2FrameAssembler,
11
+ ProtocolV2SequenceCursor,
10
12
  ProtocolV2Session,
11
13
  probeProtocolV2 as probeProtocolV2Helper,
12
14
  } from '@onekeyfe/hd-transport';
@@ -75,22 +77,26 @@ export default class WebUsbTransport {
75
77
 
76
78
  private deviceProtocolHints: Map<string, ProtocolType> = new Map();
77
79
 
78
- /** 按设备缓存 Protocol V2 frame assembler,保留同一次读取里多出来的后续 frame。 */
80
+ /** Per-device Protocol V2 assembler that retains extra frames from one read. */
79
81
  private protocolV2Assemblers: Map<string, ProtocolV2FrameAssembler> = new Map();
80
82
 
81
- /** 按设备缓存 Protocol V2 session,保持 seq 与设备会话一致递增。 */
83
+ /** Per-device Protocol V2 session that keeps sequence numbers monotonic. */
82
84
  private protocolV2Sessions: Map<string, ProtocolV2Session> = new Map();
83
85
 
84
- /** 当前 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. */
85
90
  private protocolV2ReadTimeouts: Map<string, number | undefined> = new Map();
86
91
 
92
+ private protocolV2WriteTimeoutMs = PROTOCOL_V2_WRITE_WATCHDOG_TIMEOUT_MS;
93
+
87
94
  /** Per-path USB endpoint / interface numbers (discovered from USB descriptors) */
88
95
  private deviceEndpoints: Map<string, DeviceEndpoints> = new Map();
89
96
 
90
97
  /**
91
- * 早期 Pro2 工程板 USB descriptor 没有烧录 serial number
92
- * 为这类设备生成会话内稳定的 mock path(同一 USBDevice 实例复用同一 path),
93
- * 避免设备因为空 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.
94
100
  */
95
101
  private mockSerialPaths: WeakMap<USBDevice, string> = new WeakMap();
96
102
 
@@ -182,8 +188,7 @@ export default class WebUsbTransport {
182
188
  }
183
189
 
184
190
  /**
185
- * 设备 path:正常设备直接用 USB serial number;
186
- * 空 serial(早期工程板)回退到会话内稳定的 mock path。
191
+ * Use the USB serial as the device path, falling back to a session-stable mock path.
187
192
  */
188
193
  private getDevicePath(device: USBDevice): string {
189
194
  if (typeof device.serialNumber === 'string' && device.serialNumber.length > 0) {
@@ -286,19 +291,19 @@ export default class WebUsbTransport {
286
291
  this.deviceProtocol.set(path, 'V1');
287
292
  return 'V1';
288
293
  }
294
+ await this.resetConnectionAfterProbe(path);
289
295
  throw this.createProtocolMismatchError(expectedProtocol);
290
296
  }
291
297
 
292
298
  if (expectedProtocol === 'V2') {
293
- // 免探测路径:调用方显式承诺该设备是 V2(例如固件升级重启后的重连场景,
294
- // 上层已经探测过协议并通过 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.
295
301
  this.deviceProtocol.set(path, 'V2');
296
302
  return 'V2';
297
303
  }
298
304
 
299
- // 项目约束:协议判断必须在连接后主动探测,不能依赖设备名/PID/descriptor。
300
- // 设备名 hint(如 "Pro 2")只用于调整探测顺序:hint=V2 时先探 V2、失败回落 V1
301
- // 不能作为最终结论。
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.
302
307
  const probeOrder: ProtocolType[] =
303
308
  protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
304
309
 
@@ -309,6 +314,12 @@ export default class WebUsbTransport {
309
314
  this.deviceProtocol.set(path, protocol);
310
315
  return protocol;
311
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
+ }
312
323
  }
313
324
 
314
325
  this.deviceProtocol.delete(path);
@@ -707,7 +718,6 @@ export default class WebUsbTransport {
707
718
  timeoutMs: PROTOCOL_PROBE_TIMEOUT,
708
719
  logger: this.Log,
709
720
  logPrefix: 'ProtocolV2 WebUSB',
710
- onProbeFailed: () => this.resetConnectionAfterProbe(path),
711
721
  });
712
722
  }
713
723
 
@@ -801,12 +811,19 @@ export default class WebUsbTransport {
801
811
 
802
812
  let session = this.protocolV2Sessions.get(path);
803
813
  if (!session) {
814
+ let sequenceCursor = this.protocolV2Sequences.get(path);
815
+ if (!sequenceCursor) {
816
+ sequenceCursor = new ProtocolV2SequenceCursor();
817
+ this.protocolV2Sequences.set(path, sequenceCursor);
818
+ }
804
819
  session = new ProtocolV2Session({
805
820
  schemas: {
806
821
  protocolV1: protocolV1Messages,
807
822
  protocolV2: this.messagesV2,
808
823
  },
809
824
  router: PROTOCOL_V2_CHANNEL_USB,
825
+ sequenceCursor,
826
+ writeTimeoutMs: this.protocolV2WriteTimeoutMs,
810
827
  writeFrame: (frame: Uint8Array) => this.transferOutOnce(path, frame),
811
828
  readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
812
829
  logger: this.Log,
@@ -824,7 +841,12 @@ export default class WebUsbTransport {
824
841
  } catch (error) {
825
842
  const message =
826
843
  error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
827
- if (message.includes('protocol v2 read timeout') || message.includes('response timeout')) {
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
+ ) {
828
850
  try {
829
851
  await this.resetConnectionAfterProbe(path);
830
852
  } catch (resetError) {