@onekeyfe/hd-transport-web-device 1.2.0-alpha.33 → 1.2.0-alpha.34

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.
@@ -1,5 +1,5 @@
1
1
  import transport, { PROTOCOL_V2_CHANNEL_BLE_UART, bytesToHex } from '@onekeyfe/hd-transport';
2
- import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
3
3
  import EventEmitter from 'events';
4
4
 
5
5
  import ElectronBleTransport from '../src/electron-ble-transport';
@@ -11,6 +11,9 @@ const protocolV1Schema = {
11
11
  Initialize: {
12
12
  fields: {},
13
13
  },
14
+ GetFeatures: {
15
+ fields: {},
16
+ },
14
17
  Success: {
15
18
  fields: {
16
19
  message: {
@@ -23,6 +26,7 @@ const protocolV1Schema = {
23
26
  values: {
24
27
  MessageType_Initialize: 1,
25
28
  MessageType_Success: 2,
29
+ MessageType_GetFeatures: 55,
26
30
  },
27
31
  },
28
32
  },
@@ -238,7 +242,7 @@ describe('ElectronBleTransport protocol detection', () => {
238
242
  }
239
243
  });
240
244
 
241
- test('detects Protocol V1 when device responds to Initialize', async () => {
245
+ test('reconnects Protocol V1 with a non-destructive GetFeatures probe', async () => {
242
246
  const device = { id: 'classic-id', name: 'OneKey Classic' };
243
247
  const nobleBle = createNobleBle(device);
244
248
  let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
@@ -252,7 +256,7 @@ describe('ElectronBleTransport protocol detection', () => {
252
256
  return jest.fn();
253
257
  });
254
258
  nobleBle.write.mockImplementation(() => {
255
- // Respond to first write (V1 Initialize probe) with V1 Success
259
+ // The first write is the V1 GetFeatures probe; answer with a V1 Success response.
256
260
  setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
257
261
  return Promise.resolve();
258
262
  });
@@ -266,12 +270,100 @@ describe('ElectronBleTransport protocol detection', () => {
266
270
  })
267
271
  );
268
272
  expect(transport.getProtocolType(device.id)).toBe('V1');
273
+ await expect(transport.acquire({ uuid: device.id, expectedProtocol: 'V1' })).resolves.toEqual(
274
+ expect.objectContaining({
275
+ uuid: device.id,
276
+ })
277
+ );
278
+ expect(nobleBle.write).toHaveBeenCalledTimes(2);
279
+ expect(nobleBle.write.mock.calls.every(([, hex]) => /^3f23230037/.test(hex))).toBe(true);
269
280
  expect(protocolV2Writer).not.toHaveBeenCalled();
270
281
  } finally {
271
282
  await transport.release(device.id);
272
283
  }
273
284
  });
274
285
 
286
+ test('invalidates and disconnects a Protocol V1 link after a response timeout', async () => {
287
+ const device = { id: 'classic-timeout-id', name: 'OneKey Classic' };
288
+ const nobleBle = createNobleBle(device);
289
+ let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
290
+ const v1ResponseHex = '3f23230002000000040a026f6b';
291
+ let writeCount = 0;
292
+
293
+ nobleBle.onNotification.mockImplementation(handler => {
294
+ notificationHandler = handler;
295
+ return jest.fn();
296
+ });
297
+ nobleBle.write.mockImplementation(() => {
298
+ writeCount += 1;
299
+ if (writeCount === 1) {
300
+ setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
301
+ }
302
+ return Promise.resolve();
303
+ });
304
+ const bleTransport = configureTransport(nobleBle);
305
+
306
+ await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
307
+ await expect(
308
+ bleTransport.call(device.id, 'Initialize', {}, { timeoutMs: 5 })
309
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleTimeoutError });
310
+
311
+ expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
312
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
313
+ expect(bleTransport.getProtocolType(device.id)).toBeUndefined();
314
+ });
315
+
316
+ test('keeps another device V2 reader when force-cleaning a V1 call', async () => {
317
+ const device = { id: 'classic-force-clean-id', name: 'OneKey Classic' };
318
+ const nobleBle = createNobleBle(device);
319
+ let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
320
+ const v1ResponseHex = '3f23230002000000040a026f6b';
321
+ nobleBle.onNotification.mockImplementation(handler => {
322
+ notificationHandler = handler;
323
+ return jest.fn();
324
+ });
325
+ nobleBle.write.mockImplementation(() => {
326
+ setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
327
+ return Promise.resolve();
328
+ });
329
+ const bleTransport = configureTransport(nobleBle) as any;
330
+ const activeV1Call = createDeferred<string>();
331
+ const otherDeviceReader = createDeferred<Uint8Array>();
332
+ activeV1Call.promise.catch(() => undefined);
333
+ otherDeviceReader.promise.catch(() => undefined);
334
+ bleTransport.runPromise = activeV1Call;
335
+ bleTransport.v2FramePromises.set('device-b', otherDeviceReader);
336
+
337
+ await bleTransport.acquire({
338
+ uuid: device.id,
339
+ expectedProtocol: 'V1',
340
+ forceCleanRunPromise: true,
341
+ });
342
+
343
+ expect(bleTransport.v2FramePromises.get('device-b')).toBe(otherDeviceReader);
344
+ await bleTransport.release(device.id);
345
+ });
346
+
347
+ test('rejects a pending V2 reader when its device frame state resets', async () => {
348
+ const nobleBle = createNobleBle();
349
+ const bleTransport = configureTransport(nobleBle) as any;
350
+ const reader = createDeferred<Uint8Array>();
351
+ bleTransport.v2FramePromises.set('device-a', reader);
352
+ const result = Promise.race([
353
+ reader.promise.then(
354
+ () => 'resolved',
355
+ () => 'rejected'
356
+ ),
357
+ new Promise(resolve => {
358
+ setTimeout(() => resolve('pending'), 20);
359
+ }),
360
+ ]);
361
+
362
+ bleTransport.resetProtocolV2Frames('device-a');
363
+
364
+ await expect(result).resolves.toBe('rejected');
365
+ });
366
+
275
367
  test('throws when both protocol probes fail', async () => {
276
368
  const device = { id: 'dead-device-id', name: 'Unknown Device' };
277
369
  const nobleBle = createNobleBle(device);
@@ -96,6 +96,24 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
96
96
  expect(webusb.deviceProtocol.get(path)).toBe('V2');
97
97
  });
98
98
 
99
+ test('allows legacy WebUSB Initialize up to the Node USB probe timeout', async () => {
100
+ const webusb = new WebUsbTransport() as any;
101
+ const path = 'pro-webusb';
102
+ webusb.messages = {};
103
+ webusb.callProtocolV1 = jest.fn().mockResolvedValue({});
104
+
105
+ await expect(webusb.probeProtocolV1(path)).resolves.toBe(true);
106
+
107
+ expect(webusb.callProtocolV1).toHaveBeenCalledWith(
108
+ path,
109
+ 'Initialize',
110
+ {},
111
+ {
112
+ timeoutMs: 5000,
113
+ }
114
+ );
115
+ });
116
+
99
117
  test('retries an expected Protocol V2 probe once after resetting the connection', async () => {
100
118
  const webusb = new WebUsbTransport() as any;
101
119
  const path = 'pro2-webusb';
@@ -117,6 +135,7 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
117
135
  webusb.probeProtocolV1 = jest.fn();
118
136
  webusb.probeProtocolV2 = jest.fn().mockResolvedValue(false);
119
137
  webusb.resetConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
138
+ webusb.closeConnectionAfterProbe = jest.fn().mockResolvedValue(undefined);
120
139
 
121
140
  await expect(webusb.detectProtocol(path, 'V2')).rejects.toThrow(
122
141
  'Protocol V2 probe timeout after 2 attempts'
@@ -124,10 +143,40 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
124
143
 
125
144
  expect(webusb.probeProtocolV2).toHaveBeenCalledTimes(2);
126
145
  expect(webusb.probeProtocolV1).not.toHaveBeenCalled();
127
- expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledTimes(2);
146
+ expect(webusb.resetConnectionAfterProbe).toHaveBeenCalledTimes(1);
147
+ expect(webusb.closeConnectionAfterProbe).toHaveBeenCalledTimes(1);
128
148
  expect(webusb.deviceProtocol.has(path)).toBe(false);
129
149
  });
130
150
 
151
+ test('closes the reopened device when acquire exhausts the expected Protocol V2 probe', async () => {
152
+ const webusb = new WebUsbTransport() as any;
153
+ const path = 'pro2-webusb';
154
+ const device = {
155
+ opened: false,
156
+ releaseInterface: jest.fn().mockResolvedValue(undefined),
157
+ close: jest.fn().mockImplementation(() => {
158
+ device.opened = false;
159
+ return Promise.resolve();
160
+ }),
161
+ };
162
+ webusb.deviceList = [{ path, device }];
163
+ webusb.Log = { debug: jest.fn() };
164
+ webusb.rotateProtocolV2UsbGeneration = jest.fn().mockResolvedValue(undefined);
165
+ webusb.connect = jest.fn().mockImplementation(() => {
166
+ device.opened = true;
167
+ return Promise.resolve();
168
+ });
169
+ webusb.detectProtocol = jest.fn().mockRejectedValue(new Error('terminal probe failure'));
170
+
171
+ await expect(webusb.acquire({ path, expectedProtocol: 'V2' })).rejects.toThrow(
172
+ 'terminal probe failure'
173
+ );
174
+
175
+ expect(device.releaseInterface).toHaveBeenCalledTimes(1);
176
+ expect(device.close).toHaveBeenCalledTimes(1);
177
+ expect(device.opened).toBe(false);
178
+ });
179
+
131
180
  test('invalidates and resets the cached connection before another call can start', async () => {
132
181
  const webusb = new WebUsbTransport() as any;
133
182
  const path = 'pro2-webusb';
@@ -12,6 +12,7 @@ export type BleAcquireInput = {
12
12
  uuid: string;
13
13
  forceCleanRunPromise?: boolean;
14
14
  expectedProtocol?: ProtocolType;
15
+ protocolHint?: ProtocolType;
15
16
  };
16
17
  export default class ElectronBleTransport {
17
18
  private _messages;
@@ -20,6 +21,7 @@ export default class ElectronBleTransport {
20
21
  name: string;
21
22
  configured: boolean;
22
23
  runPromise: Deferred<Uint8Array | string> | null;
24
+ private runPromiseDeviceId;
23
25
  Log?: any;
24
26
  emitter?: EventEmitter;
25
27
  private connectedDevices;
@@ -68,7 +70,6 @@ export default class ElectronBleTransport {
68
70
  private handleProtocolV2Notification;
69
71
  private getProtocolV2FrameQueue;
70
72
  private resolveProtocolV2Frame;
71
- private rejectAllProtocolV2Frames;
72
73
  private resetProtocolV2Frames;
73
74
  private rejectProtocolV2Frames;
74
75
  private readProtocolV2Frame;
@@ -1 +1 @@
1
- {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AAsBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EAEZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,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;AAoCF,MAAM,CAAC,OAAO,OAAO,oBAAoB;IACvC,OAAO,CAAC,SAAS,CAA0D;IAE3E,OAAO,CAAC,WAAW,CAA0D;IAE7E,OAAO,CAAC,6BAA6B,CAAqB;IAE1D,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;IAgB7B,MAAM;IAIN,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqBxC,OAAO,CAAC,KAAK,EAAE,eAAe;;;;;;;;;;;IAoF9B,OAAO,CAAC,EAAE,EAAE,MAAM;YAUV,aAAa;IAe3B,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,SAAS;IASvB,OAAO,CAAC,oBAAoB;IAmB5B,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;IA0B5B,OAAO,CAAC,uBAAuB;IAyC/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":";AAsBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EAEZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,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;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC;AAoCF,MAAM,CAAC,OAAO,OAAO,oBAAoB;IACvC,OAAO,CAAC,SAAS,CAA0D;IAE3E,OAAO,CAAC,WAAW,CAA0D;IAE7E,OAAO,CAAC,6BAA6B,CAAqB;IAE1D,IAAI,SAA0B;IAE9B,UAAU,UAAS;IAEnB,UAAU,EAAE,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,IAAI,CAAQ;IAExD,OAAO,CAAC,kBAAkB,CAAuB;IAEjD,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;IA+B1B,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,YAAY;IAcxC,SAAS,CAAC,UAAU,EAAE,GAAG;IAKzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAgB7B,MAAM;IAIN,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqBxC,OAAO,CAAC,KAAK,EAAE,eAAe;;;;;;;;;;;IAsF9B,OAAO,CAAC,EAAE,EAAE,MAAM;YAUV,aAAa;IAe3B,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;IA+C5B,OAAO,CAAC,8BAA8B;YAgBxB,iCAAiC;YA8CjC,eAAe;YAkBf,eAAe;YAuBf,SAAS;IASvB,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,kBAAkB;IAwB1B,OAAO,CAAC,4BAA4B;IAkBpC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,sBAAsB;YAShB,mBAAmB;IAiBjC,OAAO,CAAC,4BAA4B;IAqB9B,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;YAqFd,cAAc;IA0B5B,OAAO,CAAC,uBAAuB;IAyC/B,OAAO,CAAC,6BAA6B;IAsCrC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
package/dist/index.d.ts CHANGED
@@ -107,6 +107,7 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
107
107
  private transferOutOnce;
108
108
  private transferInWithRetry;
109
109
  private transferInOnce;
110
+ private closeConnectionAfterProbe;
110
111
  private resetConnectionAfterProbe;
111
112
  private withProtocolReadTimeout;
112
113
  private probeProtocolV1;
@@ -154,12 +155,13 @@ type BleAcquireInput = {
154
155
  uuid: string;
155
156
  forceCleanRunPromise?: boolean;
156
157
  expectedProtocol?: ProtocolType;
158
+ protocolHint?: ProtocolType;
157
159
  };
158
160
  /**
159
161
  * Desktop Electron BLE transport with automatic Protocol V1/V2 detection.
160
162
  *
161
163
  * Protocol V1 devices continue using chunked packets. Protocol V2 is detected
162
- * after a Protocol V1 Initialize timeout by probing Protocol V2 Ping.
164
+ * after a Protocol V1 GetFeatures timeout by probing Protocol V2 Ping.
163
165
  */
164
166
  declare class ElectronBleTransport {
165
167
  private _messages;
@@ -168,6 +170,7 @@ declare class ElectronBleTransport {
168
170
  name: string;
169
171
  configured: boolean;
170
172
  runPromise: Deferred<Uint8Array | string> | null;
173
+ private runPromiseDeviceId;
171
174
  Log?: any;
172
175
  emitter?: EventEmitter;
173
176
  private connectedDevices;
@@ -216,7 +219,6 @@ declare class ElectronBleTransport {
216
219
  private handleProtocolV2Notification;
217
220
  private getProtocolV2FrameQueue;
218
221
  private resolveProtocolV2Frame;
219
- private rejectAllProtocolV2Frames;
220
222
  private resetProtocolV2Frames;
221
223
  private rejectProtocolV2Frames;
222
224
  private readProtocolV2Frame;
package/dist/index.js CHANGED
@@ -59,7 +59,8 @@ const REPORT_ID = transport.PROTOCOL_V1_REPORT_ID;
59
59
  const HEADER_LENGTH = transport.PROTOCOL_V1_MESSAGE_HEADER_SIZE;
60
60
  const PACKET_IO_MAX_RETRIES = 3;
61
61
  const PACKET_IO_RETRY_DELAY = 300;
62
- const PROTOCOL_PROBE_TIMEOUT = 1000;
62
+ const PROTOCOL_V1_PROBE_TIMEOUT = 5000;
63
+ const PROTOCOL_V2_PROBE_TIMEOUT = 1000;
63
64
  const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
64
65
  function inferProtocolHintFromDeviceName$1(name) {
65
66
  return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
@@ -155,7 +156,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
155
156
  });
156
157
  }
157
158
  acquire(input) {
158
- var _a, _b, _c;
159
+ var _a, _b, _c, _d;
159
160
  return __awaiter(this, void 0, void 0, function* () {
160
161
  if (!input.path)
161
162
  return;
@@ -166,7 +167,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
166
167
  const deviceName = (_b = this.deviceList.find(device => device.path === input.path)) === null || _b === void 0 ? void 0 : _b.device.productName;
167
168
  const protocolHint = input.expectedProtocol
168
169
  ? undefined
169
- : (_c = this.deviceProtocolHints.get(input.path)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName$1(deviceName);
170
+ : (_d = (_c = input.protocolHint) !== null && _c !== void 0 ? _c : this.deviceProtocolHints.get(input.path)) !== null && _d !== void 0 ? _d : inferProtocolHintFromDeviceName$1(deviceName);
170
171
  if (protocolHint) {
171
172
  this.deviceProtocolHints.set(input.path, protocolHint);
172
173
  }
@@ -175,6 +176,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
175
176
  }
176
177
  catch (e) {
177
178
  this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
179
+ yield this.closeOpenDevice(input.path);
178
180
  throw e;
179
181
  }
180
182
  });
@@ -196,7 +198,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
196
198
  this.deviceProtocol.set(path, 'V1');
197
199
  return 'V1';
198
200
  }
199
- yield this.resetConnectionAfterProbe(path);
201
+ yield this.closeConnectionAfterProbe(path);
200
202
  throw this.createProtocolMismatchError(expectedProtocol);
201
203
  }
202
204
  if (expectedProtocol === 'V2') {
@@ -205,22 +207,30 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
205
207
  this.deviceProtocol.set(path, 'V2');
206
208
  return 'V2';
207
209
  }
208
- yield this.resetConnectionAfterProbe(path);
209
210
  if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
211
+ yield this.resetConnectionAfterProbe(path);
210
212
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[WebUsbTransport] Protocol V2 probe timed out, retrying ${attempt + 1}/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`);
211
213
  }
214
+ else {
215
+ yield this.closeConnectionAfterProbe(path);
216
+ }
212
217
  }
213
218
  this.deviceProtocol.delete(path);
214
219
  throw this.createProtocolProbeTimeoutError(expectedProtocol, EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS);
215
220
  }
216
221
  const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
217
- for (const protocol of probeOrder) {
222
+ for (const [index, protocol] of probeOrder.entries()) {
218
223
  const detected = protocol === 'V1' ? yield this.probeProtocolV1(path) : yield this.probeProtocolV2(path);
219
224
  if (detected) {
220
225
  this.deviceProtocol.set(path, protocol);
221
226
  return protocol;
222
227
  }
223
- yield this.resetConnectionAfterProbe(path);
228
+ if (index < probeOrder.length - 1) {
229
+ yield this.resetConnectionAfterProbe(path);
230
+ }
231
+ else {
232
+ yield this.closeConnectionAfterProbe(path);
233
+ }
224
234
  }
225
235
  this.deviceProtocol.delete(path);
226
236
  throw this.createProtocolDetectionError();
@@ -509,27 +519,15 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
509
519
  return this.getTransferInData(result);
510
520
  });
511
521
  }
512
- resetConnectionAfterProbe(path) {
513
- var _a;
522
+ closeConnectionAfterProbe(path) {
514
523
  return __awaiter(this, void 0, void 0, function* () {
515
524
  yield this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
516
- try {
517
- const device = yield this.findDevice(path);
518
- if (device.opened) {
519
- const endpoints = this.deviceEndpoints.get(path);
520
- const ifaceNum = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.interfaceNumber) !== null && _a !== void 0 ? _a : this.interfaceId;
521
- try {
522
- yield device.releaseInterface(ifaceNum);
523
- }
524
- catch (error) {
525
- this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
526
- }
527
- yield device.close();
528
- }
529
- }
530
- catch (error) {
531
- this.Log.debug('[WebUsbTransport] close after protocol probe error:', error);
532
- }
525
+ yield this.closeOpenDevice(path);
526
+ });
527
+ }
528
+ resetConnectionAfterProbe(path) {
529
+ return __awaiter(this, void 0, void 0, function* () {
530
+ yield this.closeConnectionAfterProbe(path);
533
531
  yield this.getConnectedDevices();
534
532
  yield this.connect(path, false);
535
533
  });
@@ -577,7 +575,9 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
577
575
  return false;
578
576
  }
579
577
  try {
580
- yield this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
578
+ yield this.callProtocolV1(path, 'Initialize', {}, {
579
+ timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
580
+ });
581
581
  return true;
582
582
  }
583
583
  catch (_error) {
@@ -592,7 +592,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
592
592
  }
593
593
  return transport.probeProtocolV2({
594
594
  call: (name, data, options) => this.callProtocolV2(path, name, data, options),
595
- timeoutMs: PROTOCOL_PROBE_TIMEOUT,
595
+ timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
596
596
  logger: this.Log,
597
597
  logPrefix: 'ProtocolV2 WebUSB',
598
598
  });
@@ -747,6 +747,7 @@ class ElectronBleTransport {
747
747
  this.name = 'ElectronBleTransport';
748
748
  this.configured = false;
749
749
  this.runPromise = null;
750
+ this.runPromiseDeviceId = null;
750
751
  this.connectedDevices = new Set();
751
752
  this.deviceProtocol = new Map();
752
753
  this.deviceProtocolHints = new Map();
@@ -819,6 +820,11 @@ class ElectronBleTransport {
819
820
  this.v2Assemblers.delete(deviceId);
820
821
  this.resetProtocolV2Frames(deviceId);
821
822
  this.notificationTokens.delete(deviceId);
823
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
824
+ this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceDisconnected));
825
+ this.runPromise = null;
826
+ this.runPromiseDeviceId = null;
827
+ }
822
828
  const notifyCleanup = this.notificationCleanups.get(deviceId);
823
829
  if (notifyCleanup) {
824
830
  notifyCleanup();
@@ -887,7 +893,7 @@ class ElectronBleTransport {
887
893
  });
888
894
  }
889
895
  acquire(input) {
890
- var _a, _b, _c, _d, _e;
896
+ var _a, _b, _c, _d, _e, _f;
891
897
  return __awaiter(this, void 0, void 0, function* () {
892
898
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
893
899
  if (!uuid) {
@@ -899,8 +905,8 @@ class ElectronBleTransport {
899
905
  if (forceCleanRunPromise && this.runPromise) {
900
906
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
901
907
  this.runPromise.reject(error);
902
- this.rejectAllProtocolV2Frames(error);
903
908
  this.runPromise = null;
909
+ this.runPromiseDeviceId = null;
904
910
  }
905
911
  try {
906
912
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
@@ -912,7 +918,7 @@ class ElectronBleTransport {
912
918
  }
913
919
  const protocolHint = expectedProtocol
914
920
  ? undefined
915
- : (_b = this.deviceProtocolHints.get(uuid)) !== null && _b !== void 0 ? _b : inferProtocolHintFromDeviceName(device.name);
921
+ : (_c = (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(uuid)) !== null && _c !== void 0 ? _c : inferProtocolHintFromDeviceName(device.name);
916
922
  if (protocolHint) {
917
923
  this.deviceProtocolHints.set(uuid, protocolHint);
918
924
  }
@@ -928,6 +934,7 @@ class ElectronBleTransport {
928
934
  yield window.desktopApi.nobleBle.subscribe(uuid);
929
935
  const cleanup = this.createNotificationSubscription(uuid);
930
936
  this.notificationCleanups.set(uuid, cleanup);
937
+ const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
931
938
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
932
939
  var _a;
933
940
  if (disconnectedDevice.id === uuid) {
@@ -940,19 +947,18 @@ class ElectronBleTransport {
940
947
  }
941
948
  });
942
949
  this.disconnectCleanups.set(uuid, disconnectCleanup);
943
- const protocolType = yield this.detectProtocol(uuid, expectedProtocol, protocolHint);
944
950
  return Object.assign(Object.assign({}, toBleDescriptor({ id: device.id, name: device.name }, protocolType)), { uuid });
945
951
  }
946
952
  catch (error) {
947
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] acquire failed:', error);
953
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] acquire failed:', error);
948
954
  try {
949
- if (((_d = window.desktopApi) === null || _d === void 0 ? void 0 : _d.nobleBle) && this.connectedDevices.has(uuid)) {
955
+ if (((_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) && this.connectedDevices.has(uuid)) {
950
956
  yield window.desktopApi.nobleBle.unsubscribe(uuid);
951
957
  yield window.desktopApi.nobleBle.disconnect(uuid);
952
958
  }
953
959
  }
954
960
  catch (cleanupError) {
955
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
961
+ (_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug('[Electron BLE] acquire cleanup failed:', cleanupError);
956
962
  }
957
963
  this.cleanupDeviceState(uuid);
958
964
  throw error;
@@ -994,7 +1000,7 @@ class ElectronBleTransport {
994
1000
  return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol mismatch: expected ${expected}, but device did not respond to expected protocol`);
995
1001
  }
996
1002
  createProtocolDetectionError() {
997
- return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping');
1003
+ return hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping');
998
1004
  }
999
1005
  clearProbeProtocol(uuid, protocol) {
1000
1006
  if (this.deviceProtocol.get(uuid) === protocol) {
@@ -1052,16 +1058,17 @@ class ElectronBleTransport {
1052
1058
  });
1053
1059
  }
1054
1060
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1055
- var _a, _b, _c, _d, _e, _f, _g;
1061
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1056
1062
  return __awaiter(this, void 0, void 0, function* () {
1057
1063
  yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1058
1064
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1059
1065
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1060
1066
  this.resetProtocolV2Frames(uuid);
1061
- if (this.runPromise) {
1067
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
1062
1068
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1063
1069
  this.runPromise.reject(error);
1064
1070
  this.runPromise = null;
1071
+ this.runPromiseDeviceId = null;
1065
1072
  }
1066
1073
  const notifyCleanup = this.notificationCleanups.get(uuid);
1067
1074
  if (notifyCleanup) {
@@ -1076,10 +1083,19 @@ class ElectronBleTransport {
1076
1083
  (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1077
1084
  }
1078
1085
  try {
1079
- yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.subscribe(uuid));
1086
+ yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.disconnect(uuid));
1080
1087
  }
1081
1088
  catch (error) {
1082
- (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
1089
+ (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
1090
+ }
1091
+ this.connectedDevices.delete(uuid);
1092
+ try {
1093
+ yield ((_j = (_h = window.desktopApi) === null || _h === void 0 ? void 0 : _h.nobleBle) === null || _j === void 0 ? void 0 : _j.connect(uuid));
1094
+ this.connectedDevices.add(uuid);
1095
+ yield ((_l = (_k = window.desktopApi) === null || _k === void 0 ? void 0 : _k.nobleBle) === null || _l === void 0 ? void 0 : _l.subscribe(uuid));
1096
+ }
1097
+ catch (error) {
1098
+ (_m = this.Log) === null || _m === void 0 ? void 0 : _m.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
1083
1099
  throw error;
1084
1100
  }
1085
1101
  const cleanup = this.createNotificationSubscription(uuid);
@@ -1094,12 +1110,12 @@ class ElectronBleTransport {
1094
1110
  }
1095
1111
  try {
1096
1112
  this.deviceProtocol.set(uuid, 'V1');
1097
- yield this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1113
+ yield this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
1098
1114
  return true;
1099
1115
  }
1100
1116
  catch (error) {
1101
1117
  this.clearProbeProtocol(uuid, 'V1');
1102
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
1118
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
1103
1119
  return false;
1104
1120
  }
1105
1121
  });
@@ -1160,7 +1176,7 @@ class ElectronBleTransport {
1160
1176
  if (this.deviceProtocol.get(deviceId) === 'V2') {
1161
1177
  this.rejectProtocolV2Frames(deviceId, error);
1162
1178
  }
1163
- else if (this.runPromise) {
1179
+ else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1164
1180
  this.runPromise.reject(error);
1165
1181
  }
1166
1182
  return;
@@ -1212,16 +1228,8 @@ class ElectronBleTransport {
1212
1228
  }
1213
1229
  this.getProtocolV2FrameQueue(uuid).push(frame);
1214
1230
  }
1215
- rejectAllProtocolV2Frames(error) {
1216
- this.v2FrameQueues.clear();
1217
- for (const framePromise of this.v2FramePromises.values()) {
1218
- framePromise.reject(error);
1219
- }
1220
- this.v2FramePromises.clear();
1221
- }
1222
1231
  resetProtocolV2Frames(uuid) {
1223
- this.v2FrameQueues.delete(uuid);
1224
- this.v2FramePromises.delete(uuid);
1232
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
1225
1233
  }
1226
1234
  rejectProtocolV2Frames(uuid, error) {
1227
1235
  this.v2FrameQueues.delete(uuid);
@@ -1254,12 +1262,15 @@ class ElectronBleTransport {
1254
1262
  const result = this.processProtocolV1Notification(deviceId, hexData);
1255
1263
  if (result.error) {
1256
1264
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
1257
- if (this.runPromise) {
1265
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1258
1266
  this.runPromise.reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError));
1259
1267
  }
1260
1268
  return;
1261
1269
  }
1262
- if (result.isComplete && result.completePacket && this.runPromise) {
1270
+ if (result.isComplete &&
1271
+ result.completePacket &&
1272
+ this.runPromise &&
1273
+ this.runPromiseDeviceId === deviceId) {
1263
1274
  this.runPromise.resolve(result.completePacket);
1264
1275
  }
1265
1276
  }
@@ -1298,6 +1309,7 @@ class ElectronBleTransport {
1298
1309
  const runPromise = hdShared.createDeferred();
1299
1310
  runPromise.promise.catch(() => undefined);
1300
1311
  this.runPromise = runPromise;
1312
+ this.runPromiseDeviceId = uuid;
1301
1313
  const messages = this._messages;
1302
1314
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
1303
1315
  let timeout;
@@ -1336,6 +1348,17 @@ class ElectronBleTransport {
1336
1348
  }
1337
1349
  catch (e) {
1338
1350
  (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V1 call error:', e);
1351
+ const isProbeTimeout = name === 'GetFeatures' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS;
1352
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError) {
1353
+ this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1354
+ const notifyCleanup = this.notificationCleanups.get(uuid);
1355
+ notifyCleanup === null || notifyCleanup === void 0 ? void 0 : notifyCleanup();
1356
+ this.notificationCleanups.delete(uuid);
1357
+ this.notificationTokens.delete(uuid);
1358
+ if (!isProbeTimeout) {
1359
+ yield this.releaseNative(uuid);
1360
+ }
1361
+ }
1339
1362
  throw e;
1340
1363
  }
1341
1364
  finally {
@@ -1343,6 +1366,7 @@ class ElectronBleTransport {
1343
1366
  clearTimeout(timeout);
1344
1367
  if (this.runPromise === runPromise) {
1345
1368
  this.runPromise = null;
1369
+ this.runPromiseDeviceId = null;
1346
1370
  }
1347
1371
  }
1348
1372
  });
package/dist/webusb.d.ts CHANGED
@@ -50,6 +50,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
50
50
  private transferOutOnce;
51
51
  private transferInWithRetry;
52
52
  private transferInOnce;
53
+ private closeConnectionAfterProbe;
53
54
  private resetConnectionAfterProbe;
54
55
  private withProtocolReadTimeout;
55
56
  private probeProtocolV1;
@@ -1 +1 @@
1
- {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";AACA,OAAO,SAAS,EAAE,EAQhB,0BAA0B,EAE3B,MAAM,wBAAwB,CAAC;AAYhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AA0BhC,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,eAAgB,SAAQ,0BAA0B,CAAC,MAAM,CAAC;IAC7E,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;IAEpE,OAAO,CAAC,sBAAsB,CAAqB;IAGnD,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAGnE,OAAO,CAAC,eAAe,CAA2C;IAElE,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;;IAa3B,IAAI,CAAC,MAAM,EAAE,GAAG;IAgBhB,SAAS,CAAC,UAAU,EAAE,GAAG;IASzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAsB7B,kBAAkB;IAmBlB,SAAS;IAQT,mBAAmB;IAiCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA8BjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,+BAA+B;IAOvC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;IAmEtB,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;YAgCpC,eAAe;YAkBf,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,cAAc;YAWd,yBAAyB;YAKzB,yBAAyB;YAMzB,uBAAuB;YA0CvB,eAAe;YAoBf,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;IAYtB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAQ1B,SAAS,CAAC,uBAAuB,IAAI,iBAAiB;IAUtD,SAAS,CAAC,sBAAsB;cAIhB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC;cAIA,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,UAAU,CAAC;cASN,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1F,SAAS,CAAC,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKrE,SAAS,CAAC,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK;IAWjF,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.33",
3
+ "version": "1.2.0-alpha.34",
4
4
  "author": "OneKey",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -21,13 +21,13 @@
21
21
  "lint:fix": "eslint . --fix"
22
22
  },
23
23
  "dependencies": {
24
- "@onekeyfe/hd-shared": "1.2.0-alpha.33",
25
- "@onekeyfe/hd-transport": "1.2.0-alpha.33"
24
+ "@onekeyfe/hd-shared": "1.2.0-alpha.34",
25
+ "@onekeyfe/hd-transport": "1.2.0-alpha.34"
26
26
  },
27
27
  "devDependencies": {
28
- "@onekeyfe/hd-transport-electron": "1.2.0-alpha.33",
28
+ "@onekeyfe/hd-transport-electron": "1.2.0-alpha.34",
29
29
  "@types/w3c-web-usb": "^1.0.6",
30
30
  "@types/web-bluetooth": "^0.0.17"
31
31
  },
32
- "gitHead": "965e837c93fc67225ccfac28d22c3403ff96ad25"
32
+ "gitHead": "07f10b4c9fab177da7c36d976ec18b075492b57f"
33
33
  }
@@ -42,6 +42,7 @@ export type BleAcquireInput = {
42
42
  uuid: string;
43
43
  forceCleanRunPromise?: boolean;
44
44
  expectedProtocol?: ProtocolType;
45
+ protocolHint?: ProtocolType;
45
46
  };
46
47
 
47
48
  interface PacketProcessResult {
@@ -76,7 +77,7 @@ const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
76
77
  * Desktop Electron BLE transport with automatic Protocol V1/V2 detection.
77
78
  *
78
79
  * Protocol V1 devices continue using chunked packets. Protocol V2 is detected
79
- * after a Protocol V1 Initialize timeout by probing Protocol V2 Ping.
80
+ * after a Protocol V1 GetFeatures timeout by probing Protocol V2 Ping.
80
81
  */
81
82
  export default class ElectronBleTransport {
82
83
  private _messages: ReturnType<typeof transport.parseConfigure> | undefined;
@@ -91,6 +92,8 @@ export default class ElectronBleTransport {
91
92
 
92
93
  runPromise: Deferred<Uint8Array | string> | null = null;
93
94
 
95
+ private runPromiseDeviceId: string | null = null;
96
+
94
97
  Log?: any;
95
98
 
96
99
  emitter?: EventEmitter;
@@ -181,6 +184,11 @@ export default class ElectronBleTransport {
181
184
  this.v2Assemblers.delete(deviceId);
182
185
  this.resetProtocolV2Frames(deviceId);
183
186
  this.notificationTokens.delete(deviceId);
187
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
188
+ this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleDeviceDisconnected));
189
+ this.runPromise = null;
190
+ this.runPromiseDeviceId = null;
191
+ }
184
192
 
185
193
  const notifyCleanup = this.notificationCleanups.get(deviceId);
186
194
  if (notifyCleanup) {
@@ -269,8 +277,8 @@ export default class ElectronBleTransport {
269
277
  if (forceCleanRunPromise && this.runPromise) {
270
278
  const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
271
279
  this.runPromise.reject(error);
272
- this.rejectAllProtocolV2Frames(error);
273
280
  this.runPromise = null;
281
+ this.runPromiseDeviceId = null;
274
282
  }
275
283
 
276
284
  try {
@@ -284,7 +292,9 @@ export default class ElectronBleTransport {
284
292
  }
285
293
  const protocolHint = expectedProtocol
286
294
  ? undefined
287
- : this.deviceProtocolHints.get(uuid) ?? inferProtocolHintFromDeviceName(device.name);
295
+ : input.protocolHint ??
296
+ this.deviceProtocolHints.get(uuid) ??
297
+ inferProtocolHintFromDeviceName(device.name);
288
298
  if (protocolHint) {
289
299
  this.deviceProtocolHints.set(uuid, protocolHint);
290
300
  }
@@ -304,6 +314,8 @@ export default class ElectronBleTransport {
304
314
  const cleanup = this.createNotificationSubscription(uuid);
305
315
  this.notificationCleanups.set(uuid, cleanup);
306
316
 
317
+ const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
318
+
307
319
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected(
308
320
  (disconnectedDevice: any) => {
309
321
  if (disconnectedDevice.id === uuid) {
@@ -318,8 +330,6 @@ export default class ElectronBleTransport {
318
330
  );
319
331
  this.disconnectCleanups.set(uuid, disconnectCleanup);
320
332
 
321
- const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
322
-
323
333
  return {
324
334
  ...toBleDescriptor({ id: device.id, name: device.name }, protocolType),
325
335
  uuid,
@@ -374,7 +384,7 @@ export default class ElectronBleTransport {
374
384
  private createProtocolDetectionError() {
375
385
  return ERRORS.TypedError(
376
386
  HardwareErrorCode.BleTimeoutError,
377
- 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
387
+ 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping'
378
388
  );
379
389
  }
380
390
 
@@ -455,10 +465,11 @@ export default class ElectronBleTransport {
455
465
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
456
466
  this.v2Assemblers.get(uuid)?.reset();
457
467
  this.resetProtocolV2Frames(uuid);
458
- if (this.runPromise) {
468
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
459
469
  const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
460
470
  this.runPromise.reject(error);
461
471
  this.runPromise = null;
472
+ this.runPromiseDeviceId = null;
462
473
  }
463
474
 
464
475
  const notifyCleanup = this.notificationCleanups.get(uuid);
@@ -474,9 +485,17 @@ export default class ElectronBleTransport {
474
485
  this.Log?.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
475
486
  }
476
487
  try {
488
+ await window.desktopApi?.nobleBle?.disconnect(uuid);
489
+ } catch (error) {
490
+ this.Log?.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
491
+ }
492
+ this.connectedDevices.delete(uuid);
493
+ try {
494
+ await window.desktopApi?.nobleBle?.connect(uuid);
495
+ this.connectedDevices.add(uuid);
477
496
  await window.desktopApi?.nobleBle?.subscribe(uuid);
478
497
  } catch (error) {
479
- this.Log?.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
498
+ this.Log?.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
480
499
  throw error;
481
500
  }
482
501
 
@@ -491,11 +510,13 @@ export default class ElectronBleTransport {
491
510
 
492
511
  try {
493
512
  this.deviceProtocol.set(uuid, 'V1');
494
- await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
513
+ // GetFeatures identifies Protocol V1 without resetting an existing wallet
514
+ // session before Core has a chance to restore a hidden wallet.
515
+ await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
495
516
  return true;
496
517
  } catch (error) {
497
518
  this.clearProbeProtocol(uuid, 'V1');
498
- this.Log?.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
519
+ this.Log?.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
499
520
  return false;
500
521
  }
501
522
  }
@@ -557,7 +578,7 @@ export default class ElectronBleTransport {
557
578
  const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
558
579
  if (this.deviceProtocol.get(deviceId) === 'V2') {
559
580
  this.rejectProtocolV2Frames(deviceId, error);
560
- } else if (this.runPromise) {
581
+ } else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
561
582
  this.runPromise.reject(error);
562
583
  }
563
584
  return;
@@ -612,17 +633,8 @@ export default class ElectronBleTransport {
612
633
  this.getProtocolV2FrameQueue(uuid).push(frame);
613
634
  }
614
635
 
615
- private rejectAllProtocolV2Frames(error: Error) {
616
- this.v2FrameQueues.clear();
617
- for (const framePromise of this.v2FramePromises.values()) {
618
- framePromise.reject(error);
619
- }
620
- this.v2FramePromises.clear();
621
- }
622
-
623
636
  private resetProtocolV2Frames(uuid: string) {
624
- this.v2FrameQueues.delete(uuid);
625
- this.v2FramePromises.delete(uuid);
637
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
626
638
  }
627
639
 
628
640
  private rejectProtocolV2Frames(uuid: string, error: Error) {
@@ -656,13 +668,18 @@ export default class ElectronBleTransport {
656
668
 
657
669
  if (result.error) {
658
670
  this.Log?.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
659
- if (this.runPromise) {
671
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
660
672
  this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
661
673
  }
662
674
  return;
663
675
  }
664
676
 
665
- if (result.isComplete && result.completePacket && this.runPromise) {
677
+ if (
678
+ result.isComplete &&
679
+ result.completePacket &&
680
+ this.runPromise &&
681
+ this.runPromiseDeviceId === deviceId
682
+ ) {
666
683
  this.runPromise.resolve(result.completePacket);
667
684
  }
668
685
  }
@@ -716,6 +733,7 @@ export default class ElectronBleTransport {
716
733
  const runPromise = createDeferred<Uint8Array | string>();
717
734
  runPromise.promise.catch(() => undefined);
718
735
  this.runPromise = runPromise;
736
+ this.runPromiseDeviceId = uuid;
719
737
  const messages = this._messages;
720
738
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
721
739
  let timeout: ReturnType<typeof setTimeout> | undefined;
@@ -760,11 +778,24 @@ export default class ElectronBleTransport {
760
778
  return check.call(jsonData);
761
779
  } catch (e) {
762
780
  this.Log?.error('[Electron BLE] Protocol V1 call error:', e);
781
+ const isProbeTimeout =
782
+ name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
783
+ if ((e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError) {
784
+ this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
785
+ const notifyCleanup = this.notificationCleanups.get(uuid);
786
+ notifyCleanup?.();
787
+ this.notificationCleanups.delete(uuid);
788
+ this.notificationTokens.delete(uuid);
789
+ if (!isProbeTimeout) {
790
+ await this.releaseNative(uuid);
791
+ }
792
+ }
763
793
  throw e;
764
794
  } finally {
765
795
  if (timeout) clearTimeout(timeout);
766
796
  if (this.runPromise === runPromise) {
767
797
  this.runPromise = null;
798
+ this.runPromiseDeviceId = null;
768
799
  }
769
800
  }
770
801
  }
package/src/webusb.ts CHANGED
@@ -41,7 +41,11 @@ const REPORT_ID = PROTOCOL_V1_REPORT_ID;
41
41
  const HEADER_LENGTH = PROTOCOL_V1_MESSAGE_HEADER_SIZE;
42
42
  const PACKET_IO_MAX_RETRIES = 3;
43
43
  const PACKET_IO_RETRY_DELAY = 300;
44
- const PROTOCOL_PROBE_TIMEOUT = 1000;
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;
45
49
  const EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS = 2;
46
50
  function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
47
51
  return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
@@ -233,7 +237,9 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
233
237
  .productName;
234
238
  const protocolHint = input.expectedProtocol
235
239
  ? undefined
236
- : this.deviceProtocolHints.get(input.path) ?? inferProtocolHintFromDeviceName(deviceName);
240
+ : input.protocolHint ??
241
+ this.deviceProtocolHints.get(input.path) ??
242
+ inferProtocolHintFromDeviceName(deviceName);
237
243
  if (protocolHint) {
238
244
  this.deviceProtocolHints.set(input.path, protocolHint);
239
245
  }
@@ -241,6 +247,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
241
247
  return await Promise.resolve(input.path);
242
248
  } catch (e) {
243
249
  this.Log.debug('acquire error: ', e instanceof Error ? `${e.name}: ${e.message}` : String(e));
250
+ await this.closeOpenDevice(input.path);
244
251
  throw e;
245
252
  }
246
253
  }
@@ -281,7 +288,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
281
288
  this.deviceProtocol.set(path, 'V1');
282
289
  return 'V1';
283
290
  }
284
- await this.resetConnectionAfterProbe(path);
291
+ await this.closeConnectionAfterProbe(path);
285
292
  throw this.createProtocolMismatchError(expectedProtocol);
286
293
  }
287
294
 
@@ -291,13 +298,15 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
291
298
  this.deviceProtocol.set(path, 'V2');
292
299
  return 'V2';
293
300
  }
294
- await this.resetConnectionAfterProbe(path);
295
301
  if (attempt < EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS) {
302
+ await this.resetConnectionAfterProbe(path);
296
303
  this.Log?.debug(
297
304
  `[WebUsbTransport] Protocol V2 probe timed out, retrying ${
298
305
  attempt + 1
299
306
  }/${EXPECTED_PROTOCOL_V2_PROBE_ATTEMPTS}`
300
307
  );
308
+ } else {
309
+ await this.closeConnectionAfterProbe(path);
301
310
  }
302
311
  }
303
312
  this.deviceProtocol.delete(path);
@@ -312,17 +321,21 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
312
321
  const probeOrder: ProtocolType[] =
313
322
  protocolHint === 'V2' || this.deviceProtocol.get(path) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
314
323
 
315
- for (const protocol of probeOrder) {
324
+ for (const [index, protocol] of probeOrder.entries()) {
316
325
  const detected =
317
326
  protocol === 'V1' ? await this.probeProtocolV1(path) : await this.probeProtocolV2(path);
318
327
  if (detected) {
319
328
  this.deviceProtocol.set(path, protocol);
320
329
  return protocol;
321
330
  }
322
- // A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
323
- // reopening the device guarantees the next protocol probe cannot consume a
324
- // late response from the previous protocol generation.
325
- await this.resetConnectionAfterProbe(path);
331
+ if (index < probeOrder.length - 1) {
332
+ // A timed-out WebUSB transferIn cannot be cancelled in place. Closing and
333
+ // reopening the device guarantees the next protocol probe cannot consume a
334
+ // late response from the previous protocol generation.
335
+ await this.resetConnectionAfterProbe(path);
336
+ } else {
337
+ await this.closeConnectionAfterProbe(path);
338
+ }
326
339
  }
327
340
 
328
341
  this.deviceProtocol.delete(path);
@@ -645,25 +658,13 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
645
658
  return this.getTransferInData(result);
646
659
  }
647
660
 
648
- private async resetConnectionAfterProbe(path: string) {
661
+ private async closeConnectionAfterProbe(path: string) {
649
662
  await this.rotateProtocolV2UsbGeneration(path, 'WebUSB protocol probe reset');
663
+ await this.closeOpenDevice(path);
664
+ }
650
665
 
651
- try {
652
- const device = await this.findDevice(path);
653
- if (device.opened) {
654
- const endpoints = this.deviceEndpoints.get(path);
655
- const ifaceNum = endpoints?.interfaceNumber ?? this.interfaceId;
656
- try {
657
- await device.releaseInterface(ifaceNum);
658
- } catch (error) {
659
- this.Log.debug('[WebUsbTransport] releaseInterface after protocol probe error:', error);
660
- }
661
- await device.close();
662
- }
663
- } catch (error) {
664
- this.Log.debug('[WebUsbTransport] close after protocol probe error:', error);
665
- }
666
-
666
+ private async resetConnectionAfterProbe(path: string) {
667
+ await this.closeConnectionAfterProbe(path);
667
668
  await this.getConnectedDevices();
668
669
  await this.connect(path, false);
669
670
  }
@@ -716,7 +717,14 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
716
717
  }
717
718
 
718
719
  try {
719
- await this.callProtocolV1(path, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT });
720
+ await this.callProtocolV1(
721
+ path,
722
+ 'Initialize',
723
+ {},
724
+ {
725
+ timeoutMs: PROTOCOL_V1_PROBE_TIMEOUT,
726
+ }
727
+ );
720
728
  return true;
721
729
  } catch (_error) {
722
730
  return false;
@@ -730,7 +738,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
730
738
 
731
739
  return probeProtocolV2Helper({
732
740
  call: (name, data, options) => this.callProtocolV2(path, name, data, options),
733
- timeoutMs: PROTOCOL_PROBE_TIMEOUT,
741
+ timeoutMs: PROTOCOL_V2_PROBE_TIMEOUT,
734
742
  logger: this.Log,
735
743
  logPrefix: 'ProtocolV2 WebUSB',
736
744
  });