@onekeyfe/hd-transport-web-device 1.2.2-alpha.100 → 1.2.2-alpha.2

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.
@@ -73,8 +73,26 @@ const protocolV2Schema = {
73
73
  },
74
74
  },
75
75
  },
76
+ Failure: {
77
+ fields: {
78
+ code: {
79
+ type: 'FailureType',
80
+ id: 1,
81
+ },
82
+ message: {
83
+ type: 'string',
84
+ id: 2,
85
+ },
86
+ },
87
+ },
88
+ FailureType: {
89
+ values: {
90
+ Failure_ProcessError: 5,
91
+ },
92
+ },
76
93
  MessageType: {
77
94
  values: {
95
+ MessageType_Failure: 3,
78
96
  MessageType_ProtocolInfoRequest: 60200,
79
97
  MessageType_ProtocolInfo: 60201,
80
98
  MessageType_Ping: 60206,
@@ -327,7 +345,7 @@ describe('ElectronBleTransport protocol detection', () => {
327
345
  }
328
346
  });
329
347
 
330
- test('reconnects Protocol V1 with a non-destructive GetFeatures probe', async () => {
348
+ test('reconnects a declared Protocol V1 device without probing again', async () => {
331
349
  const device = { id: 'classic-id', name: 'OneKey Classic' };
332
350
  const nobleBle = createNobleBle(device);
333
351
  let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
@@ -360,7 +378,10 @@ describe('ElectronBleTransport protocol detection', () => {
360
378
  uuid: device.id,
361
379
  })
362
380
  );
363
- expect(nobleBle.write).toHaveBeenCalledTimes(2);
381
+ // The first acquire probes because the protocol is unknown; the second
382
+ // declares V1 and must send nothing at all, leaving the first frame on
383
+ // the link to Core — which is what carries the wallet session.
384
+ expect(nobleBle.write).toHaveBeenCalledTimes(1);
364
385
  expect(nobleBle.write.mock.calls.every(([, hex]) => /^3f23230037/.test(hex))).toBe(true);
365
386
  expect(protocolV2Writer).not.toHaveBeenCalled();
366
387
  } finally {
@@ -373,19 +394,13 @@ describe('ElectronBleTransport protocol detection', () => {
373
394
  const nobleBle = createNobleBle(device);
374
395
  let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
375
396
  const v1ResponseHex = '3f23230002000000040a026f6b';
376
- let writeCount = 0;
377
-
378
397
  nobleBle.onNotification.mockImplementation(handler => {
379
398
  notificationHandler = handler;
380
399
  return jest.fn();
381
400
  });
382
- nobleBle.write.mockImplementation(() => {
383
- writeCount += 1;
384
- if (writeCount === 1) {
385
- setTimeout(() => notificationHandler?.(device.id, v1ResponseHex), 0);
386
- }
387
- return Promise.resolve();
388
- });
401
+ // A declared V1 acquire writes nothing, so every write here belongs to the
402
+ // call under test and none of them is answered.
403
+ nobleBle.write.mockImplementation(() => Promise.resolve());
389
404
  const bleTransport = configureTransport(nobleBle);
390
405
 
391
406
  await bleTransport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
@@ -464,6 +479,41 @@ describe('ElectronBleTransport protocol detection', () => {
464
479
  expect(transport.getProtocolType(device.id)).toBeUndefined();
465
480
  });
466
481
 
482
+ test('surfaces Protocol V2 link disabled while the initial V1 probe is active', async () => {
483
+ const device = { id: 'usb-priority-pro2-id', name: 'OneKey Pro 2' };
484
+ const nobleBle = createNobleBle(device);
485
+ let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
486
+
487
+ nobleBle.onNotification.mockImplementation(handler => {
488
+ notificationHandler = handler;
489
+ return jest.fn();
490
+ });
491
+ nobleBle.write.mockImplementation(() => {
492
+ const response = ProtocolV2.encodeFrame(
493
+ schemas,
494
+ 'Failure',
495
+ { code: 5, message: 'link disabled' },
496
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
497
+ );
498
+ const splitAt = 4;
499
+ setTimeout(() => {
500
+ notificationHandler?.(device.id, bytesToHex(response.subarray(0, splitAt)));
501
+ notificationHandler?.(device.id, bytesToHex(response.subarray(splitAt)));
502
+ }, 0);
503
+ return Promise.resolve();
504
+ });
505
+ const transport = configureTransport(nobleBle);
506
+
507
+ await expect(transport.acquire({ uuid: device.id })).rejects.toMatchObject({
508
+ name: 'ProtocolV2LinkDisabledError',
509
+ failureCode: 'Failure_ProcessError',
510
+ firmwareMessage: 'link disabled',
511
+ });
512
+ expect(nobleBle.write).toHaveBeenCalledTimes(1);
513
+ expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
514
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
515
+ });
516
+
467
517
  test('keeps a first expected Protocol V2 probe miss retryable', async () => {
468
518
  const device = { id: 'first-v2-id', name: 'OneKey Pro 2' };
469
519
  const nobleBle = createNobleBle(device);
@@ -500,6 +550,40 @@ describe('ElectronBleTransport protocol detection', () => {
500
550
  expect(transport.getProtocolType(device.id)).toBeUndefined();
501
551
  });
502
552
 
553
+ test('fails Protocol V2 acquire immediately when subscribe reports insufficient encryption', async () => {
554
+ const device = { id: 'stale-bond-pro2-id', name: 'OneKey Pro 2' };
555
+ const nobleBle = createNobleBle(device);
556
+ nobleBle.subscribe.mockRejectedValue(new Error('Encryption is insufficient'));
557
+ const transport = configureTransport(nobleBle);
558
+ const probe = jest.spyOn(transport as any, 'probeProtocolV2');
559
+
560
+ await expect(
561
+ transport.acquire({ uuid: device.id, expectedProtocol: 'V2' })
562
+ ).rejects.toMatchObject({
563
+ errorCode: HardwareErrorCode.BleDeviceBondError,
564
+ });
565
+
566
+ expect(probe).not.toHaveBeenCalled();
567
+ expect(nobleBle.unsubscribe).toHaveBeenCalledWith(device.id);
568
+ expect(nobleBle.disconnect).toHaveBeenCalledWith(device.id);
569
+ });
570
+
571
+ test('keeps stale-bond subscribe mapping out of Protocol V1 acquire', async () => {
572
+ const device = { id: 'classic-v1-id', name: 'OneKey Classic' };
573
+ const nobleBle = createNobleBle(device);
574
+ nobleBle.subscribe.mockRejectedValue(new Error('Encryption is insufficient'));
575
+ const transport = configureTransport(nobleBle);
576
+
577
+ try {
578
+ await transport.acquire({ uuid: device.id, expectedProtocol: 'V1' });
579
+ throw new Error('Expected Protocol V1 acquire to fail');
580
+ } catch (error) {
581
+ expect(error).toBeInstanceOf(Error);
582
+ expect((error as Error).message).toBe('Encryption is insufficient');
583
+ expect((error as { errorCode?: unknown }).errorCode).toBeUndefined();
584
+ }
585
+ });
586
+
503
587
  test('reports a stale bond when a previously confirmed Protocol V2 device stops responding', async () => {
504
588
  const device = { id: 'reset-pro2-id', name: 'OneKey Pro 2' };
505
589
  const nobleBle = createNobleBle(device);
@@ -69,6 +69,38 @@ describe('WebUsbTransport protocol probe cache', () => {
69
69
  expect(webusb.detectProtocol).toHaveBeenCalledWith(path, 'V1', undefined);
70
70
  });
71
71
 
72
+ test('acquire reuses a previously confirmed protocol during explicit no-probe recovery', async () => {
73
+ const webusb = buildAcquirableTransport();
74
+ const path = 'pro-webusb';
75
+ webusb.deviceProtocol.set(path, 'V1');
76
+ webusb.confirmedDeviceProtocols.set(path, 'V2');
77
+ webusb.markProtocolStale(path);
78
+ webusb.detectProtocol = jest.fn();
79
+
80
+ await expect(
81
+ webusb.acquire({ path, expectedProtocol: 'V2', skipProtocolProbe: true })
82
+ ).resolves.toBe(path);
83
+
84
+ expect(webusb.detectProtocol).not.toHaveBeenCalled();
85
+ expect(webusb.deviceProtocol.get(path)).toBe('V2');
86
+ expect(webusb.staleProtocolPaths.has(path)).toBe(false);
87
+ expect(webusb.acquiredPaths.has(path)).toBe(true);
88
+ });
89
+
90
+ test('acquire rejects no-probe recovery without a previously confirmed protocol', async () => {
91
+ const webusb = buildAcquirableTransport();
92
+ const path = 'pro-webusb';
93
+ webusb.detectProtocol = jest.fn();
94
+
95
+ await expect(
96
+ webusb.acquire({ path, expectedProtocol: 'V2', skipProtocolProbe: true })
97
+ ).rejects.toThrow('previously confirmed protocol');
98
+
99
+ expect(webusb.detectProtocol).not.toHaveBeenCalled();
100
+ expect(webusb.deviceProtocol.has(path)).toBe(false);
101
+ expect(webusb.acquiredPaths.has(path)).toBe(false);
102
+ });
103
+
72
104
  test('forced protocol detection bypasses a valid cache and probes on the wire', async () => {
73
105
  const webusb = buildAcquirableTransport();
74
106
  const path = 'pro-webusb';
@@ -387,6 +387,13 @@ describe('WebUsbTransport Protocol V2 timeout recovery', () => {
387
387
  webusb.callProtocolV2(path, 'Ping', { message: 'short' }, { timeoutMs: 25 }),
388
388
  ]);
389
389
 
390
- expect(readTimeouts).toEqual([1_000, 25]);
390
+ // Each call keeps its own budget instead of inheriting the other's. The
391
+ // queued one is a deadline, so it arrives with whatever the first call left
392
+ // of its 25ms — asserting the exact remainder makes this fail on a loaded
393
+ // machine, which is timing, not behaviour.
394
+ expect(readTimeouts).toHaveLength(2);
395
+ expect(readTimeouts[0]).toBe(1_000);
396
+ expect(readTimeouts[1]).toBeGreaterThan(0);
397
+ expect(readTimeouts[1]).toBeLessThanOrEqual(25);
391
398
  });
392
399
  });
@@ -41,6 +41,7 @@ export default class ElectronBleTransport {
41
41
  private hostDisconnectCleanup?;
42
42
  private notificationTokens;
43
43
  private nextNotificationToken;
44
+ private toStaleBondError;
44
45
  private handleBluetoothError;
45
46
  private cleanupDeviceState;
46
47
  init(logger: any, emitter?: EventEmitter): void;
@@ -78,6 +79,7 @@ export default class ElectronBleTransport {
78
79
  private createMtuSubscription;
79
80
  private writeProtocolV2Frame;
80
81
  private handleNotification;
82
+ private readProtocolV2LinkDisabledFailure;
81
83
  private handleProtocolV2Notification;
82
84
  private getProtocolV2FrameQueue;
83
85
  private resolveProtocolV2Frame;
@@ -1 +1 @@
1
- {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ declare class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
19
19
  private protocolV2SchemaSource;
20
20
  /** Per-path protocol type detected by active wire-level probe. */
21
21
  private deviceProtocol;
22
+ /** Protocols previously confirmed by an active response for this transport instance. */
23
+ private confirmedDeviceProtocols;
22
24
  private deviceProtocolHints;
23
25
  /**
24
26
  * Paths whose cached protocol must be re-probed on the next acquire (a USB
@@ -240,6 +242,7 @@ declare class ElectronBleTransport {
240
242
  private hostDisconnectCleanup?;
241
243
  private notificationTokens;
242
244
  private nextNotificationToken;
245
+ private toStaleBondError;
243
246
  private handleBluetoothError;
244
247
  private cleanupDeviceState;
245
248
  init(logger: any, emitter?: EventEmitter): void;
@@ -282,6 +285,7 @@ declare class ElectronBleTransport {
282
285
  private createMtuSubscription;
283
286
  private writeProtocolV2Frame;
284
287
  private handleNotification;
288
+ private readProtocolV2LinkDisabledFailure;
285
289
  private handleProtocolV2Notification;
286
290
  private getProtocolV2FrameQueue;
287
291
  private resolveProtocolV2Frame;
package/dist/index.js CHANGED
@@ -79,6 +79,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
79
79
  logPrefix: 'ProtocolV2 WebUSB',
80
80
  });
81
81
  this.deviceProtocol = new Map();
82
+ this.confirmedDeviceProtocols = new Map();
82
83
  this.deviceProtocolHints = new Map();
83
84
  this.staleProtocolPaths = new Set();
84
85
  this.acquiredPaths = new Set();
@@ -182,7 +183,7 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
182
183
  });
183
184
  }
184
185
  acquire(input) {
185
- var _a, _b, _c, _d, _e, _f;
186
+ var _a, _b, _c, _d, _e, _f, _g;
186
187
  return __awaiter(this, void 0, void 0, function* () {
187
188
  if (!input.path)
188
189
  return;
@@ -190,35 +191,53 @@ class WebUsbTransport extends transport.ProtocolV2UsbTransportBase {
190
191
  yield this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
191
192
  yield this.closeOpenDevice(input.path);
192
193
  yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
193
- if (input.forceProtocolDetection) {
194
+ if (input.skipProtocolProbe) {
195
+ if (!input.expectedProtocol) {
196
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires an expected protocol');
197
+ }
198
+ if (this.confirmedDeviceProtocols.get(input.path) !== input.expectedProtocol) {
199
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'skipProtocolProbe requires a previously confirmed protocol for this WebUSB endpoint');
200
+ }
201
+ this.staleProtocolPaths.delete(input.path);
202
+ this.deviceProtocol.set(input.path, input.expectedProtocol);
203
+ const currentDevice = (_b = this.deviceList.find(d => d.path === input.path)) === null || _b === void 0 ? void 0 : _b.device;
204
+ if (currentDevice) {
205
+ this.probedDeviceObjects.set(input.path, currentDevice);
206
+ }
207
+ }
208
+ else if (input.forceProtocolDetection) {
194
209
  this.staleProtocolPaths.delete(input.path);
195
210
  this.deviceProtocol.delete(input.path);
196
211
  this.deviceProtocolHints.delete(input.path);
197
212
  }
198
- if (this.staleProtocolPaths.has(input.path)) {
213
+ if (!input.skipProtocolProbe && this.staleProtocolPaths.has(input.path)) {
199
214
  this.staleProtocolPaths.delete(input.path);
200
215
  this.deviceProtocol.delete(input.path);
201
216
  }
202
- if (this.deviceProtocol.has(input.path)) {
203
- const currentDevice = (_b = this.deviceList.find(d => d.path === input.path)) === null || _b === void 0 ? void 0 : _b.device;
217
+ if (!input.skipProtocolProbe && this.deviceProtocol.has(input.path)) {
218
+ const currentDevice = (_c = this.deviceList.find(d => d.path === input.path)) === null || _c === void 0 ? void 0 : _c.device;
204
219
  if (!currentDevice || currentDevice !== this.probedDeviceObjects.get(input.path)) {
205
220
  this.deviceProtocol.delete(input.path);
206
221
  }
207
222
  }
208
223
  const cachedProtocol = this.deviceProtocol.get(input.path);
209
- if (cachedProtocol && input.expectedProtocol && cachedProtocol !== input.expectedProtocol) {
224
+ if (!input.skipProtocolProbe &&
225
+ cachedProtocol &&
226
+ input.expectedProtocol &&
227
+ cachedProtocol !== input.expectedProtocol) {
210
228
  this.deviceProtocol.delete(input.path);
211
229
  }
212
230
  if (!this.deviceProtocol.has(input.path)) {
213
- const usbDevice = (_c = this.deviceList.find(device => device.path === input.path)) === null || _c === void 0 ? void 0 : _c.device;
231
+ const usbDevice = (_d = this.deviceList.find(device => device.path === input.path)) === null || _d === void 0 ? void 0 : _d.device;
214
232
  const protocolHint = input.expectedProtocol
215
233
  ? undefined
216
- : (_e = (_d = input.protocolHint) !== null && _d !== void 0 ? _d : this.deviceProtocolHints.get(input.path)) !== null && _e !== void 0 ? _e : hdShared.inferProtocolHintFromUsbId(usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.vendorId, usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.productId);
234
+ : (_f = (_e = input.protocolHint) !== null && _e !== void 0 ? _e : this.deviceProtocolHints.get(input.path)) !== null && _f !== void 0 ? _f : hdShared.inferProtocolHintFromUsbId(usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.vendorId, usbDevice === null || usbDevice === void 0 ? void 0 : usbDevice.productId);
217
235
  if (protocolHint) {
218
236
  this.deviceProtocolHints.set(input.path, protocolHint);
219
237
  }
220
- yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
221
- const probedDevice = (_f = this.deviceList.find(d => d.path === input.path)) === null || _f === void 0 ? void 0 : _f.device;
238
+ const detectedProtocol = yield this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
239
+ this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
240
+ const probedDevice = (_g = this.deviceList.find(d => d.path === input.path)) === null || _g === void 0 ? void 0 : _g.device;
222
241
  if (probedDevice) {
223
242
  this.probedDeviceObjects.set(input.path, probedDevice);
224
243
  }
@@ -852,7 +871,29 @@ class ElectronBleTransport {
852
871
  this.notificationTokens = new Map();
853
872
  this.nextNotificationToken = 1;
854
873
  }
855
- handleBluetoothError(error) {
874
+ toStaleBondError(error) {
875
+ var _a;
876
+ if (hdShared.isBleStaleBondHardwareError(error)) {
877
+ return error;
878
+ }
879
+ const errorMessage = error && typeof error === 'object' && 'message' in error
880
+ ? String((_a = error.message) !== null && _a !== void 0 ? _a : '')
881
+ : String(error !== null && error !== void 0 ? error : '');
882
+ if (!hdShared.isBleStaleBondErrorText(errorMessage)) {
883
+ return null;
884
+ }
885
+ const normalizedErrorMessage = errorMessage.toLowerCase();
886
+ return hdShared.ERRORS.TypedError(normalizedErrorMessage.includes('peer removed pairing information')
887
+ ? hdShared.HardwareErrorCode.BlePeerRemovedPairingInformation
888
+ : hdShared.HardwareErrorCode.BleDeviceBondError, errorMessage);
889
+ }
890
+ handleBluetoothError(error, mapProtocolV2StaleBond = false) {
891
+ if (mapProtocolV2StaleBond) {
892
+ const staleBondError = this.toStaleBondError(error);
893
+ if (staleBondError) {
894
+ throw staleBondError;
895
+ }
896
+ }
856
897
  if (error && typeof error === 'object') {
857
898
  if ('code' in error) {
858
899
  if (error.code === hdShared.HardwareErrorCode.BlePoweredOff) {
@@ -983,6 +1024,9 @@ class ElectronBleTransport {
983
1024
  var _a, _b, _c, _d, _e, _f, _g;
984
1025
  return __awaiter(this, void 0, void 0, function* () {
985
1026
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
1027
+ const shouldMapProtocolV2StaleBond = expectedProtocol
1028
+ ? expectedProtocol === 'V2'
1029
+ : this.confirmedProtocolV2.has(uuid);
986
1030
  if (!uuid) {
987
1031
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
988
1032
  }
@@ -1014,7 +1058,7 @@ class ElectronBleTransport {
1014
1058
  this.connectedDevices.add(uuid);
1015
1059
  }
1016
1060
  catch (error) {
1017
- this.handleBluetoothError(error);
1061
+ this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
1018
1062
  }
1019
1063
  const mtuCleanup = this.createMtuSubscription(uuid);
1020
1064
  if (mtuCleanup) {
@@ -1022,7 +1066,12 @@ class ElectronBleTransport {
1022
1066
  }
1023
1067
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1024
1068
  this.v2Assemblers.set(uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
1025
- yield window.desktopApi.nobleBle.subscribe(uuid);
1069
+ try {
1070
+ yield window.desktopApi.nobleBle.subscribe(uuid);
1071
+ }
1072
+ catch (error) {
1073
+ this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
1074
+ }
1026
1075
  yield this.refreshBlePacketCapacity(uuid);
1027
1076
  const cleanup = this.createNotificationSubscription(uuid);
1028
1077
  this.notificationCleanups.set(uuid, cleanup);
@@ -1128,12 +1177,9 @@ class ElectronBleTransport {
1128
1177
  var _a, _b, _c;
1129
1178
  return __awaiter(this, void 0, void 0, function* () {
1130
1179
  if (expectedProtocol === 'V1') {
1131
- if (yield this.probeProtocolV1(uuid)) {
1132
- this.deviceProtocol.set(uuid, 'V1');
1133
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V1 (expected)`);
1134
- return 'V1';
1135
- }
1136
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
1180
+ this.deviceProtocol.set(uuid, 'V1');
1181
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V1 (expected, no probe)`);
1182
+ return 'V1';
1137
1183
  }
1138
1184
  if (expectedProtocol === 'V2') {
1139
1185
  if (yield this.probeProtocolV2(uuid)) {
@@ -1211,6 +1257,9 @@ class ElectronBleTransport {
1211
1257
  catch (error) {
1212
1258
  this.clearProbeProtocol(uuid, 'V1');
1213
1259
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
1260
+ if (transport.isProtocolV2LinkDisabledError(error)) {
1261
+ throw error;
1262
+ }
1214
1263
  return false;
1215
1264
  }
1216
1265
  });
@@ -1233,6 +1282,7 @@ class ElectronBleTransport {
1233
1282
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1234
1283
  this.resetProtocolV2Frames(uuid);
1235
1284
  },
1285
+ shouldRethrow: error => hdShared.isBleStaleBondHardwareError(error) || transport.isProtocolV2LinkDisabledError(error),
1236
1286
  });
1237
1287
  if (!detected) {
1238
1288
  this.clearProbeProtocol(uuid, 'V2');
@@ -1247,7 +1297,16 @@ class ElectronBleTransport {
1247
1297
  if (!nobleBle) {
1248
1298
  throw new Error('Noble BLE API not available');
1249
1299
  }
1250
- yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
1300
+ try {
1301
+ yield nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
1302
+ }
1303
+ catch (error) {
1304
+ const staleBondError = this.toStaleBondError(error);
1305
+ if (staleBondError) {
1306
+ throw staleBondError;
1307
+ }
1308
+ throw error;
1309
+ }
1251
1310
  });
1252
1311
  }
1253
1312
  refreshBlePacketCapacity(uuid) {
@@ -1316,8 +1375,27 @@ class ElectronBleTransport {
1316
1375
  this.handleProtocolV2Notification(deviceId, hexData);
1317
1376
  return;
1318
1377
  }
1378
+ const linkDisabledError = this.readProtocolV2LinkDisabledFailure(deviceId, hexData);
1379
+ if (linkDisabledError) {
1380
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
1381
+ this.runPromise.reject(linkDisabledError);
1382
+ }
1383
+ return;
1384
+ }
1319
1385
  this.handleProtocolV1Notification(deviceId, hexData);
1320
1386
  }
1387
+ readProtocolV2LinkDisabledFailure(deviceId, hexData) {
1388
+ if (!this._messages || !this._messagesV2)
1389
+ return undefined;
1390
+ const assembler = this.v2Assemblers.get(deviceId);
1391
+ if (!assembler)
1392
+ return undefined;
1393
+ return transport.detectProtocolV2LinkDisabledError({
1394
+ schemas: { protocolV1: this._messages, protocolV2: this._messagesV2 },
1395
+ assembler,
1396
+ bytes: transport.hexToBytes(hexData),
1397
+ });
1398
+ }
1321
1399
  handleProtocolV2Notification(deviceId, hexData) {
1322
1400
  var _a;
1323
1401
  try {
package/dist/webusb.d.ts CHANGED
@@ -13,6 +13,7 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
13
13
  messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
14
14
  private protocolV2SchemaSource;
15
15
  private deviceProtocol;
16
+ private confirmedDeviceProtocols;
16
17
  private deviceProtocolHints;
17
18
  private staleProtocolPaths;
18
19
  private acquiredPaths;
@@ -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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-web-device",
3
- "version": "1.2.2-alpha.100",
3
+ "version": "1.2.2-alpha.2",
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.2-alpha.100",
25
- "@onekeyfe/hd-transport": "1.2.2-alpha.100"
24
+ "@onekeyfe/hd-shared": "1.2.2-alpha.2",
25
+ "@onekeyfe/hd-transport": "1.2.2-alpha.2"
26
26
  },
27
27
  "devDependencies": {
28
- "@onekeyfe/hd-transport-electron": "1.2.2-alpha.100",
28
+ "@onekeyfe/hd-transport-electron": "1.2.2-alpha.2",
29
29
  "@types/w3c-web-usb": "^1.0.6",
30
30
  "@types/web-bluetooth": "^0.0.17"
31
31
  },
32
- "gitHead": "c40dad085297b0e3cc2020bd218c146dff6ed3e1"
32
+ "gitHead": "fc914d0361028f1012ca764e53f996b9893b40f9"
33
33
  }
@@ -6,7 +6,9 @@ import transport, {
6
6
  ProtocolV2LinkManager,
7
7
  TRANSPORT_EVENT,
8
8
  bytesToHex,
9
+ detectProtocolV2LinkDisabledError,
9
10
  hexToBytes,
11
+ isProtocolV2LinkDisabledError,
10
12
  probeProtocolV2 as probeProtocolV2Helper,
11
13
  writeProtocolV2BleFrame,
12
14
  } from '@onekeyfe/hd-transport';
@@ -16,6 +18,8 @@ import {
16
18
  HardwareErrorCode,
17
19
  HardwareErrorCodeMessage,
18
20
  createDeferred,
21
+ isBleStaleBondErrorText,
22
+ isBleStaleBondHardwareError,
19
23
  isHeaderChunk,
20
24
  } from '@onekeyfe/hd-shared';
21
25
 
@@ -159,7 +163,33 @@ export default class ElectronBleTransport {
159
163
 
160
164
  private nextNotificationToken = 1;
161
165
 
162
- private handleBluetoothError(error: any): never {
166
+ private toStaleBondError(error: unknown): Error | null {
167
+ if (isBleStaleBondHardwareError(error)) {
168
+ return error as Error;
169
+ }
170
+ const errorMessage =
171
+ error && typeof error === 'object' && 'message' in error
172
+ ? String((error as { message?: unknown }).message ?? '')
173
+ : String(error ?? '');
174
+ if (!isBleStaleBondErrorText(errorMessage)) {
175
+ return null;
176
+ }
177
+ const normalizedErrorMessage = errorMessage.toLowerCase();
178
+ return ERRORS.TypedError(
179
+ normalizedErrorMessage.includes('peer removed pairing information')
180
+ ? HardwareErrorCode.BlePeerRemovedPairingInformation
181
+ : HardwareErrorCode.BleDeviceBondError,
182
+ errorMessage
183
+ );
184
+ }
185
+
186
+ private handleBluetoothError(error: any, mapProtocolV2StaleBond = false): never {
187
+ if (mapProtocolV2StaleBond) {
188
+ const staleBondError = this.toStaleBondError(error);
189
+ if (staleBondError) {
190
+ throw staleBondError;
191
+ }
192
+ }
163
193
  if (error && typeof error === 'object') {
164
194
  if ('code' in error) {
165
195
  if (error.code === HardwareErrorCode.BlePoweredOff) {
@@ -320,6 +350,9 @@ export default class ElectronBleTransport {
320
350
 
321
351
  async acquire(input: BleAcquireInput) {
322
352
  const { uuid, forceCleanRunPromise, expectedProtocol } = input;
353
+ const shouldMapProtocolV2StaleBond = expectedProtocol
354
+ ? expectedProtocol === 'V2'
355
+ : this.confirmedProtocolV2.has(uuid);
323
356
 
324
357
  if (!uuid) {
325
358
  throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
@@ -356,7 +389,7 @@ export default class ElectronBleTransport {
356
389
  await window.desktopApi.nobleBle.connect(uuid);
357
390
  this.connectedDevices.add(uuid);
358
391
  } catch (error) {
359
- this.handleBluetoothError(error);
392
+ this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
360
393
  }
361
394
 
362
395
  const mtuCleanup = this.createMtuSubscription(uuid);
@@ -367,7 +400,11 @@ export default class ElectronBleTransport {
367
400
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
368
401
  this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
369
402
 
370
- await window.desktopApi.nobleBle.subscribe(uuid);
403
+ try {
404
+ await window.desktopApi.nobleBle.subscribe(uuid);
405
+ } catch (error) {
406
+ this.handleBluetoothError(error, shouldMapProtocolV2StaleBond);
407
+ }
371
408
  await this.refreshBlePacketCapacity(uuid);
372
409
 
373
410
  const cleanup = this.createNotificationSubscription(uuid);
@@ -487,13 +524,20 @@ export default class ElectronBleTransport {
487
524
  expectedProtocol?: ProtocolType,
488
525
  protocolHint?: ProtocolType
489
526
  ): Promise<ProtocolType> {
527
+ // A declared V1 is taken at face value, as the React Native transport
528
+ // already does on iOS: the caller reads the protocol off its own device
529
+ // record, so probing re-asks a question that is already answered and adds a
530
+ // round trip to every cold connect. It also fails in a way that costs the
531
+ // session: a device whose protocol session has stalled ignores the probe
532
+ // frame, and the timeout became a protocol-mismatch error that stopped Core
533
+ // from ever sending Initialize — the one frame such a device still answers,
534
+ // and how it gets revived. A declared V2 keeps probing, matching iOS, so a
535
+ // USB-priority "link disabled" surfaces here rather than as an unmapped
536
+ // error later. An undeclared protocol still goes through full detection.
490
537
  if (expectedProtocol === 'V1') {
491
- if (await this.probeProtocolV1(uuid)) {
492
- this.deviceProtocol.set(uuid, 'V1');
493
- this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V1 (expected)`);
494
- return 'V1';
495
- }
496
- throw this.createProtocolMismatchError(expectedProtocol, uuid);
538
+ this.deviceProtocol.set(uuid, 'V1');
539
+ this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V1 (expected, no probe)`);
540
+ return 'V1';
497
541
  }
498
542
 
499
543
  if (expectedProtocol === 'V2') {
@@ -588,6 +632,9 @@ export default class ElectronBleTransport {
588
632
  } catch (error) {
589
633
  this.clearProbeProtocol(uuid, 'V1');
590
634
  this.Log?.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
635
+ if (isProtocolV2LinkDisabledError(error)) {
636
+ throw error;
637
+ }
591
638
  return false;
592
639
  }
593
640
  }
@@ -608,6 +655,8 @@ export default class ElectronBleTransport {
608
655
  this.v2Assemblers.get(uuid)?.reset();
609
656
  this.resetProtocolV2Frames(uuid);
610
657
  },
658
+ shouldRethrow: error =>
659
+ isBleStaleBondHardwareError(error) || isProtocolV2LinkDisabledError(error),
611
660
  });
612
661
  if (!detected) {
613
662
  this.clearProbeProtocol(uuid, 'V2');
@@ -621,7 +670,15 @@ export default class ElectronBleTransport {
621
670
  throw new Error('Noble BLE API not available');
622
671
  }
623
672
 
624
- await nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
673
+ try {
674
+ await nobleBle.write(uuid, hexData, { pacingDelayMs: 0 });
675
+ } catch (error) {
676
+ const staleBondError = this.toStaleBondError(error);
677
+ if (staleBondError) {
678
+ throw staleBondError;
679
+ }
680
+ throw error;
681
+ }
625
682
  }
626
683
 
627
684
  private async refreshBlePacketCapacity(uuid: string): Promise<void> {
@@ -695,9 +752,29 @@ export default class ElectronBleTransport {
695
752
  this.handleProtocolV2Notification(deviceId, hexData);
696
753
  return;
697
754
  }
755
+ const linkDisabledError = this.readProtocolV2LinkDisabledFailure(deviceId, hexData);
756
+ if (linkDisabledError) {
757
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
758
+ this.runPromise.reject(linkDisabledError);
759
+ }
760
+ return;
761
+ }
698
762
  this.handleProtocolV1Notification(deviceId, hexData);
699
763
  }
700
764
 
765
+ private readProtocolV2LinkDisabledFailure(deviceId: string, hexData: string) {
766
+ if (!this._messages || !this._messagesV2) return undefined;
767
+
768
+ const assembler = this.v2Assemblers.get(deviceId);
769
+ if (!assembler) return undefined;
770
+
771
+ return detectProtocolV2LinkDisabledError({
772
+ schemas: { protocolV1: this._messages, protocolV2: this._messagesV2 },
773
+ assembler,
774
+ bytes: hexToBytes(hexData),
775
+ });
776
+ }
777
+
701
778
  private handleProtocolV2Notification(deviceId: string, hexData: string): void {
702
779
  try {
703
780
  const bytes = hexToBytes(hexData);
package/src/webusb.ts CHANGED
@@ -107,6 +107,9 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
107
107
  /** Per-path protocol type detected by active wire-level probe. */
108
108
  private deviceProtocol: Map<string, ProtocolType> = new Map();
109
109
 
110
+ /** Protocols previously confirmed by an active response for this transport instance. */
111
+ private confirmedDeviceProtocols: Map<string, ProtocolType> = new Map();
112
+
110
113
  private deviceProtocolHints: Map<string, ProtocolType> = new Map();
111
114
 
112
115
  /**
@@ -309,7 +312,26 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
309
312
  await this.rotateProtocolV2UsbGeneration(input.path, 'WebUSB transport acquired');
310
313
  await this.closeOpenDevice(input.path);
311
314
  await this.connect(input.path ?? '', true);
312
- if (input.forceProtocolDetection) {
315
+ if (input.skipProtocolProbe) {
316
+ if (!input.expectedProtocol) {
317
+ throw ERRORS.TypedError(
318
+ HardwareErrorCode.RuntimeError,
319
+ 'skipProtocolProbe requires an expected protocol'
320
+ );
321
+ }
322
+ if (this.confirmedDeviceProtocols.get(input.path) !== input.expectedProtocol) {
323
+ throw ERRORS.TypedError(
324
+ HardwareErrorCode.RuntimeError,
325
+ 'skipProtocolProbe requires a previously confirmed protocol for this WebUSB endpoint'
326
+ );
327
+ }
328
+ this.staleProtocolPaths.delete(input.path);
329
+ this.deviceProtocol.set(input.path, input.expectedProtocol);
330
+ const currentDevice = this.deviceList.find(d => d.path === input.path)?.device;
331
+ if (currentDevice) {
332
+ this.probedDeviceObjects.set(input.path, currentDevice);
333
+ }
334
+ } else if (input.forceProtocolDetection) {
313
335
  // Explicit recovery/discovery (e.g. detectDeviceConnectProtocol) must
314
336
  // probe on the wire regardless of any cached result — the cached
315
337
  // binding may be exactly what the caller is trying to recover from.
@@ -317,13 +339,13 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
317
339
  this.deviceProtocol.delete(input.path);
318
340
  this.deviceProtocolHints.delete(input.path);
319
341
  }
320
- if (this.staleProtocolPaths.has(input.path)) {
342
+ if (!input.skipProtocolProbe && this.staleProtocolPaths.has(input.path)) {
321
343
  // The device disconnected (possibly rebooting into another mode) since
322
344
  // the protocol was probed — drop the cache so it is re-probed below.
323
345
  this.staleProtocolPaths.delete(input.path);
324
346
  this.deviceProtocol.delete(input.path);
325
347
  }
326
- if (this.deviceProtocol.has(input.path)) {
348
+ if (!input.skipProtocolProbe && this.deviceProtocol.has(input.path)) {
327
349
  const currentDevice = this.deviceList.find(d => d.path === input.path)?.device;
328
350
  if (!currentDevice || currentDevice !== this.probedDeviceObjects.get(input.path)) {
329
351
  // The OS re-enumerated the device since the probe (a replug/reboot we
@@ -333,7 +355,12 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
333
355
  }
334
356
  }
335
357
  const cachedProtocol = this.deviceProtocol.get(input.path);
336
- if (cachedProtocol && input.expectedProtocol && cachedProtocol !== input.expectedProtocol) {
358
+ if (
359
+ !input.skipProtocolProbe &&
360
+ cachedProtocol &&
361
+ input.expectedProtocol &&
362
+ cachedProtocol !== input.expectedProtocol
363
+ ) {
337
364
  // The caller expects a different protocol than the cached probe result;
338
365
  // the cache is stale — drop it and re-probe on the wire below.
339
366
  this.deviceProtocol.delete(input.path);
@@ -348,7 +375,12 @@ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string>
348
375
  if (protocolHint) {
349
376
  this.deviceProtocolHints.set(input.path, protocolHint);
350
377
  }
351
- await this.detectProtocol(input.path, input.expectedProtocol, protocolHint);
378
+ const detectedProtocol = await this.detectProtocol(
379
+ input.path,
380
+ input.expectedProtocol,
381
+ protocolHint
382
+ );
383
+ this.confirmedDeviceProtocols.set(input.path, detectedProtocol);
352
384
  const probedDevice = this.deviceList.find(d => d.path === input.path)?.device;
353
385
  if (probedDevice) {
354
386
  this.probedDeviceObjects.set(input.path, probedDevice);