@onekeyfe/hd-transport-web-device 1.2.0-alpha.1 → 1.2.0-alpha.10

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,4 +1,5 @@
1
1
  import transport, { PROTOCOL_V2_CHANNEL_BLE_UART, bytesToHex } from '@onekeyfe/hd-transport';
2
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
3
 
3
4
  import ElectronBleTransport from '../src/electron-ble-transport';
4
5
 
@@ -28,21 +29,25 @@ const protocolV1Schema = {
28
29
 
29
30
  const protocolV2Schema = {
30
31
  nested: {
31
- GetProtoVersion: {
32
+ ProtocolInfoRequest: {
32
33
  fields: {},
33
34
  },
34
- ProtoVersion: {
35
+ ProtocolInfo: {
35
36
  fields: {
36
- major_version: {
37
+ version: {
37
38
  type: 'uint32',
38
39
  id: 1,
39
40
  },
40
- minor_version: {
41
+ supported_messages: {
42
+ rule: 'repeated',
41
43
  type: 'uint32',
42
44
  id: 2,
45
+ options: {
46
+ packed: false,
47
+ },
43
48
  },
44
- patch_version: {
45
- type: 'uint32',
49
+ protobuf_definition: {
50
+ type: 'string',
46
51
  id: 3,
47
52
  },
48
53
  },
@@ -65,8 +70,8 @@ const protocolV2Schema = {
65
70
  },
66
71
  MessageType: {
67
72
  values: {
68
- MessageType_GetProtoVersion: 60200,
69
- MessageType_ProtoVersion: 60201,
73
+ MessageType_ProtocolInfoRequest: 60200,
74
+ MessageType_ProtocolInfo: 60201,
70
75
  MessageType_Ping: 60206,
71
76
  MessageType_Success: 60207,
72
77
  },
@@ -241,6 +246,95 @@ describe('ElectronBleTransport protocol detection', () => {
241
246
  );
242
247
  expect(nobleBle.write).toHaveBeenCalledTimes(1);
243
248
  expect(transport.getProtocolType(device.id)).toBe('V2');
249
+ await expect(transport.call(device.id, 'Ping', { message: 'after-probe' })).resolves.toEqual({
250
+ type: 'Success',
251
+ message: { message: 'ok' },
252
+ });
253
+ const sentSeqs = nobleBle.write.mock.calls.map(([, hex]) =>
254
+ Number.parseInt(hex.slice(12, 14), 16)
255
+ );
256
+ expect(sentSeqs).toEqual([1, 2]);
257
+ } finally {
258
+ await transport.release(device.id);
259
+ }
260
+ });
261
+
262
+ test('rejects the active Protocol V2 reader when pairing is rejected', async () => {
263
+ const device = { id: 'pairing-rejected-pro2-id', name: 'OneKey Pro 2' };
264
+ const nobleBle = createNobleBle(device);
265
+ let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
266
+ let pairingRejected = false;
267
+ const probeResponse = ProtocolV2.encodeFrame(
268
+ schemas,
269
+ 'Success',
270
+ { message: 'ok' },
271
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
272
+ );
273
+
274
+ nobleBle.onNotification.mockImplementation(handler => {
275
+ notificationHandler = handler;
276
+ return jest.fn();
277
+ });
278
+ nobleBle.write.mockImplementation(() => {
279
+ setTimeout(
280
+ () =>
281
+ notificationHandler?.(
282
+ device.id,
283
+ pairingRejected ? 'PAIRING_REJECTED' : bytesToHex(probeResponse)
284
+ ),
285
+ 0
286
+ );
287
+ return Promise.resolve();
288
+ });
289
+ const transport = configureTransport(nobleBle);
290
+
291
+ try {
292
+ await transport.acquire({ uuid: device.id });
293
+ pairingRejected = true;
294
+
295
+ await expect(
296
+ transport.call(device.id, 'Ping', { message: 'pairing' }, { timeoutMs: 50 })
297
+ ).rejects.toMatchObject({ errorCode: HardwareErrorCode.BleDeviceBondedCanceled });
298
+ } finally {
299
+ await transport.release(device.id);
300
+ }
301
+ });
302
+
303
+ test('rebuilds the active link when Core acquires the same device again', async () => {
304
+ const device = { id: 'repeated-acquire-pro2-id', name: 'OneKey Pro 2' };
305
+ const nobleBle = createNobleBle(device);
306
+ let notificationHandler: ((deviceId: string, data: string) => void) | undefined;
307
+ const response = ProtocolV2.encodeFrame(
308
+ schemas,
309
+ 'Success',
310
+ { message: 'ok' },
311
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
312
+ );
313
+
314
+ nobleBle.onNotification.mockImplementation(handler => {
315
+ notificationHandler = handler;
316
+ return jest.fn();
317
+ });
318
+ nobleBle.write.mockImplementation(() => {
319
+ setTimeout(() => notificationHandler?.(device.id, bytesToHex(response)), 0);
320
+ return Promise.resolve();
321
+ });
322
+ const transport = configureTransport(nobleBle);
323
+
324
+ try {
325
+ await transport.acquire({ uuid: device.id });
326
+ await transport.acquire({ uuid: device.id, expectedProtocol: 'V2' });
327
+ await expect(
328
+ transport.call(device.id, 'Ping', { message: 'after-reacquire' })
329
+ ).resolves.toEqual({
330
+ type: 'Success',
331
+ message: { message: 'ok' },
332
+ });
333
+
334
+ const sentSeqs = nobleBle.write.mock.calls.map(([, hex]) =>
335
+ Number.parseInt(hex.slice(12, 14), 16)
336
+ );
337
+ expect(sentSeqs).toEqual([1, 2]);
244
338
  } finally {
245
339
  await transport.release(device.id);
246
340
  }
@@ -28,8 +28,7 @@ export default class ElectronBleTransport {
28
28
  private v2Assemblers;
29
29
  private v2FrameQueues;
30
30
  private v2FramePromises;
31
- private activeProtocolV2Call;
32
- private nextProtocolV2CallToken;
31
+ private protocolV2Links;
33
32
  private notificationCleanups;
34
33
  private disconnectCleanups;
35
34
  private notificationTokens;
@@ -62,19 +61,20 @@ export default class ElectronBleTransport {
62
61
  private probeProtocolV1;
63
62
  private probeProtocolV2;
64
63
  private writeWithChunking;
65
- private writeWithRetry;
64
+ private writeOnce;
66
65
  private handleNotification;
67
66
  private handleProtocolV2Notification;
68
67
  private getProtocolV2FrameQueue;
69
68
  private resolveProtocolV2Frame;
70
69
  private rejectAllProtocolV2Frames;
71
70
  private resetProtocolV2Frames;
72
- private isActiveProtocolV2Call;
71
+ private rejectProtocolV2Frames;
73
72
  private readProtocolV2Frame;
74
73
  private handleProtocolV1Notification;
75
74
  call(uuid: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<import("@onekeyfe/hd-transport").MessageFromOneKey>;
76
75
  private callProtocolV1;
77
76
  private callProtocolV2;
77
+ private createProtocolV2Adapter;
78
78
  private processProtocolV1Notification;
79
79
  getProtocolType(path: string): ProtocolType | undefined;
80
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"electron-ble-transport.d.ts","sourceRoot":"","sources":["../src/electron-ble-transport.ts"],"names":[],"mappings":";AAmBA,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;AAevC,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;AAuCF,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,oBAAoB,CAAgD;IAE5E,OAAO,CAAC,uBAAuB,CAAK;IAEpC,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;IAK7B,MAAM;IAIN,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAqBxC,OAAO,CAAC,KAAK,EAAE,eAAe;;;;;;;;;;;IAuF9B,OAAO,CAAC,EAAE,EAAE,MAAM;IAexB,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;IA+C5B,OAAO,CAAC,8BAA8B;YAgBxB,iCAAiC;YAoCjC,eAAe;YAgBf,eAAe;YAuBf,iBAAiB;YAsBjB,cAAc;IA4B5B,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,4BAA4B;IA2BpC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,sBAAsB;YAIhB,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;YA+BlB,cAAc;YAuEd,cAAc;IAmF5B,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":";AAmBA,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;AAevC,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;IAQ7B,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;YA+BlB,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
@@ -53,6 +53,7 @@ declare class WebUsbTransport {
53
53
  private getTransferInData;
54
54
  private toArrayBuffer;
55
55
  private transferOutWithRetry;
56
+ private transferOutOnce;
56
57
  private transferInWithRetry;
57
58
  private resetConnectionAfterProbe;
58
59
  private withProtocolReadTimeout;
@@ -92,8 +93,7 @@ declare class ElectronBleTransport {
92
93
  private v2Assemblers;
93
94
  private v2FrameQueues;
94
95
  private v2FramePromises;
95
- private activeProtocolV2Call;
96
- private nextProtocolV2CallToken;
96
+ private protocolV2Links;
97
97
  private notificationCleanups;
98
98
  private disconnectCleanups;
99
99
  private notificationTokens;
@@ -126,19 +126,20 @@ declare class ElectronBleTransport {
126
126
  private probeProtocolV1;
127
127
  private probeProtocolV2;
128
128
  private writeWithChunking;
129
- private writeWithRetry;
129
+ private writeOnce;
130
130
  private handleNotification;
131
131
  private handleProtocolV2Notification;
132
132
  private getProtocolV2FrameQueue;
133
133
  private resolveProtocolV2Frame;
134
134
  private rejectAllProtocolV2Frames;
135
135
  private resetProtocolV2Frames;
136
- private isActiveProtocolV2Call;
136
+ private rejectProtocolV2Frames;
137
137
  private readProtocolV2Frame;
138
138
  private handleProtocolV1Notification;
139
139
  call(uuid: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<_onekeyfe_hd_transport.MessageFromOneKey>;
140
140
  private callProtocolV1;
141
141
  private callProtocolV2;
142
+ private createProtocolV2Adapter;
142
143
  private processProtocolV1Notification;
143
144
  getProtocolType(path: string): ProtocolType | undefined;
144
145
  }
package/dist/index.js CHANGED
@@ -420,19 +420,11 @@ class WebUsbTransport {
420
420
  return copied.buffer;
421
421
  }
422
422
  transferOutWithRetry(path, packet) {
423
- var _a;
424
423
  return __awaiter(this, void 0, void 0, function* () {
425
424
  let lastError;
426
425
  for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
427
426
  try {
428
- const device = yield this.findDevice(path);
429
- if (!device.opened) {
430
- yield this.connect(path, false);
431
- }
432
- const endpoints = this.deviceEndpoints.get(path);
433
- const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
434
- const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
435
- yield device.transferOut(endpointOut, transferBuffer);
427
+ yield this.transferOutOnce(path, packet);
436
428
  return;
437
429
  }
438
430
  catch (error) {
@@ -453,6 +445,19 @@ class WebUsbTransport {
453
445
  throw lastError;
454
446
  });
455
447
  }
448
+ transferOutOnce(path, packet) {
449
+ var _a;
450
+ return __awaiter(this, void 0, void 0, function* () {
451
+ const device = yield this.findDevice(path);
452
+ if (!device.opened) {
453
+ yield this.connect(path, false);
454
+ }
455
+ const endpoints = this.deviceEndpoints.get(path);
456
+ const endpointOut = (_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.endpointOut) !== null && _a !== void 0 ? _a : this.endpointId;
457
+ const transferBuffer = this.toArrayBuffer(packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength));
458
+ yield device.transferOut(endpointOut, transferBuffer);
459
+ });
460
+ }
456
461
  transferInWithRetry(path, length, cancelToken) {
457
462
  var _a;
458
463
  return __awaiter(this, void 0, void 0, function* () {
@@ -642,7 +647,7 @@ class WebUsbTransport {
642
647
  protocolV2: this.messagesV2,
643
648
  },
644
649
  router: transport.PROTOCOL_V2_CHANNEL_USB,
645
- writeFrame: (frame) => this.transferOutWithRetry(path, frame),
650
+ writeFrame: (frame) => this.transferOutOnce(path, frame),
646
651
  readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
647
652
  logger: this.Log,
648
653
  logPrefix: 'ProtocolV2 WebUSB',
@@ -761,8 +766,6 @@ function inferProtocolHintFromDeviceName(name) {
761
766
  const toBleDescriptor = (device, protocolType) => (Object.assign({ id: device.id, name: device.name, path: device.id, debug: false, commType: 'electron-ble' }, (protocolType ? { protocolType } : {})));
762
767
  const BLE_PACKET_SIZE = 192;
763
768
  const BLE_WRITE_DELAY_MS = 5;
764
- const BLE_WRITE_MAX_RETRIES = 3;
765
- const BLE_WRITE_RETRY_DELAY_MS = 300;
766
769
  const BLE_RESPONSE_TIMEOUT_MS = 30000;
767
770
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
768
771
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
@@ -778,8 +781,27 @@ class ElectronBleTransport {
778
781
  this.v2Assemblers = new Map();
779
782
  this.v2FrameQueues = new Map();
780
783
  this.v2FramePromises = new Map();
781
- this.activeProtocolV2Call = null;
782
- this.nextProtocolV2CallToken = 1;
784
+ this.protocolV2Links = new transport.ProtocolV2LinkManager({
785
+ getSchemas: () => {
786
+ if (!this._messages || !this._messagesV2) {
787
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
788
+ }
789
+ return {
790
+ protocolV1: this._messages,
791
+ protocolV2: this._messagesV2,
792
+ };
793
+ },
794
+ classifyError: () => 'link-fatal',
795
+ onLinkInvalidated: (uuid, reason) => __awaiter(this, void 0, void 0, function* () {
796
+ var _a, _b;
797
+ (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
798
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
799
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
800
+ if (reason.startsWith('Protocol V2 link-fatal error:')) {
801
+ yield this.release(uuid);
802
+ }
803
+ }),
804
+ });
783
805
  this.notificationCleanups = new Map();
784
806
  this.disconnectCleanups = new Map();
785
807
  this.notificationTokens = new Map();
@@ -815,15 +837,14 @@ class ElectronBleTransport {
815
837
  throw error;
816
838
  }
817
839
  cleanupDeviceState(deviceId) {
818
- var _a;
840
+ this.protocolV2Links
841
+ .invalidateLink(deviceId, 'Electron BLE device state cleaned')
842
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] link cleanup failed:', error); });
819
843
  this.connectedDevices.delete(deviceId);
820
844
  this.deviceProtocol.delete(deviceId);
821
845
  this.v1Buffers.delete(deviceId);
822
846
  this.v2Assemblers.delete(deviceId);
823
847
  this.resetProtocolV2Frames(deviceId);
824
- if (((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === deviceId) {
825
- this.activeProtocolV2Call = null;
826
- }
827
848
  this.notificationTokens.delete(deviceId);
828
849
  const notifyCleanup = this.notificationCleanups.get(deviceId);
829
850
  if (notifyCleanup) {
@@ -852,6 +873,9 @@ class ElectronBleTransport {
852
873
  configureProtocolV2(signedData) {
853
874
  var _a;
854
875
  this._messagesV2 = parseConfigure(signedData);
876
+ this.protocolV2Links
877
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
878
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] schema link cleanup failed:', error); });
855
879
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Protocol V2 schema configured');
856
880
  }
857
881
  listen() {
@@ -890,12 +914,14 @@ class ElectronBleTransport {
890
914
  if (!uuid) {
891
915
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleRequiredUUID);
892
916
  }
917
+ if (this.connectedDevices.has(uuid)) {
918
+ yield this.release(uuid);
919
+ }
893
920
  if (forceCleanRunPromise && this.runPromise) {
894
921
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
895
922
  this.runPromise.reject(error);
896
923
  this.rejectAllProtocolV2Frames(error);
897
924
  this.runPromise = null;
898
- this.activeProtocolV2Call = null;
899
925
  }
900
926
  try {
901
927
  if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
@@ -963,6 +989,7 @@ class ElectronBleTransport {
963
989
  var _a, _b;
964
990
  return __awaiter(this, void 0, void 0, function* () {
965
991
  try {
992
+ yield this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
966
993
  if (this.connectedDevices.has(id)) {
967
994
  if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
968
995
  yield window.desktopApi.nobleBle.unsubscribe(id);
@@ -1036,14 +1063,12 @@ class ElectronBleTransport {
1036
1063
  });
1037
1064
  }
1038
1065
  resetProbeStateAfterProtocolProbe(uuid, protocol) {
1039
- var _a, _b, _c, _d, _e, _f, _g, _h;
1066
+ var _a, _b, _c, _d, _e, _f, _g;
1040
1067
  return __awaiter(this, void 0, void 0, function* () {
1068
+ yield this.protocolV2Links.invalidateLink(uuid, `Reset notify state after Protocol ${protocol} probe`);
1041
1069
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
1042
1070
  (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1043
1071
  this.resetProtocolV2Frames(uuid);
1044
- if (((_b = this.activeProtocolV2Call) === null || _b === void 0 ? void 0 : _b.uuid) === uuid) {
1045
- this.activeProtocolV2Call = null;
1046
- }
1047
1072
  if (this.runPromise) {
1048
1073
  const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1049
1074
  this.runPromise.reject(error);
@@ -1056,16 +1081,16 @@ class ElectronBleTransport {
1056
1081
  }
1057
1082
  this.notificationTokens.delete(uuid);
1058
1083
  try {
1059
- yield ((_d = (_c = window.desktopApi) === null || _c === void 0 ? void 0 : _c.nobleBle) === null || _d === void 0 ? void 0 : _d.unsubscribe(uuid));
1084
+ yield ((_c = (_b = window.desktopApi) === null || _b === void 0 ? void 0 : _b.nobleBle) === null || _c === void 0 ? void 0 : _c.unsubscribe(uuid));
1060
1085
  }
1061
1086
  catch (error) {
1062
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1087
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
1063
1088
  }
1064
1089
  try {
1065
- yield ((_g = (_f = window.desktopApi) === null || _f === void 0 ? void 0 : _f.nobleBle) === null || _g === void 0 ? void 0 : _g.subscribe(uuid));
1090
+ yield ((_f = (_e = window.desktopApi) === null || _e === void 0 ? void 0 : _e.nobleBle) === null || _f === void 0 ? void 0 : _f.subscribe(uuid));
1066
1091
  }
1067
1092
  catch (error) {
1068
- (_h = this.Log) === null || _h === void 0 ? void 0 : _h.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
1093
+ (_g = this.Log) === null || _g === void 0 ? void 0 : _g.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
1069
1094
  throw error;
1070
1095
  }
1071
1096
  const cleanup = this.createNotificationSubscription(uuid);
@@ -1120,52 +1145,40 @@ class ElectronBleTransport {
1120
1145
  const totalBytes = hexData.length / 2;
1121
1146
  if (totalBytes <= BLE_PACKET_SIZE) {
1122
1147
  yield hdShared.wait(BLE_WRITE_DELAY_MS);
1123
- yield this.writeWithRetry(uuid, hexData);
1148
+ yield this.writeOnce(uuid, hexData);
1124
1149
  return;
1125
1150
  }
1126
1151
  for (let offset = 0; offset < hexData.length;) {
1127
1152
  const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
1128
1153
  const chunkHex = hexData.substring(offset, offset + chunkHexLen);
1129
1154
  offset += chunkHexLen;
1130
- yield this.writeWithRetry(uuid, chunkHex);
1155
+ yield this.writeOnce(uuid, chunkHex);
1131
1156
  if (offset < hexData.length) {
1132
1157
  yield hdShared.wait(BLE_WRITE_DELAY_MS);
1133
1158
  }
1134
1159
  }
1135
1160
  });
1136
1161
  }
1137
- writeWithRetry(uuid, hexData) {
1138
- var _a, _b, _c;
1162
+ writeOnce(uuid, hexData) {
1163
+ var _a;
1139
1164
  return __awaiter(this, void 0, void 0, function* () {
1140
- let lastError;
1141
1165
  const nobleBle = (_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle;
1142
1166
  if (!nobleBle) {
1143
1167
  throw new Error('Noble BLE API not available');
1144
1168
  }
1145
- for (let attempt = 1; attempt <= BLE_WRITE_MAX_RETRIES; attempt++) {
1146
- try {
1147
- yield nobleBle.write(uuid, hexData);
1148
- return;
1149
- }
1150
- catch (error) {
1151
- lastError = error;
1152
- (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error(`[Electron BLE] write failed (attempt ${attempt}/${BLE_WRITE_MAX_RETRIES}):`, error);
1153
- if (attempt < BLE_WRITE_MAX_RETRIES) {
1154
- yield hdShared.wait(BLE_WRITE_RETRY_DELAY_MS);
1155
- }
1156
- }
1157
- }
1158
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError, `BLE write failed after ${BLE_WRITE_MAX_RETRIES} attempts: ${(_c = lastError === null || lastError === void 0 ? void 0 : lastError.message) !== null && _c !== void 0 ? _c : lastError}`);
1169
+ yield nobleBle.write(uuid, hexData);
1159
1170
  });
1160
1171
  }
1161
1172
  handleNotification(deviceId, hexData) {
1162
1173
  var _a, _b;
1163
1174
  if (hexData === 'PAIRING_REJECTED') {
1164
1175
  (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
1165
- if (this.runPromise) {
1166
- const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled);
1176
+ const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleDeviceBondedCanceled);
1177
+ if (this.deviceProtocol.get(deviceId) === 'V2') {
1178
+ this.rejectProtocolV2Frames(deviceId, error);
1179
+ }
1180
+ else if (this.runPromise) {
1167
1181
  this.runPromise.reject(error);
1168
- this.rejectAllProtocolV2Frames(error);
1169
1182
  }
1170
1183
  return;
1171
1184
  }
@@ -1181,13 +1194,8 @@ class ElectronBleTransport {
1181
1194
  this.handleProtocolV1Notification(deviceId, hexData);
1182
1195
  }
1183
1196
  handleProtocolV2Notification(deviceId, hexData) {
1184
- var _a, _b, _c;
1197
+ var _a;
1185
1198
  try {
1186
- if (!this.runPromise || ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) !== deviceId) {
1187
- (_b = this.v2Assemblers.get(deviceId)) === null || _b === void 0 ? void 0 : _b.reset();
1188
- this.resetProtocolV2Frames(deviceId);
1189
- return;
1190
- }
1191
1199
  const bytes = transport.hexToBytes(hexData);
1192
1200
  if (bytes.length === 0)
1193
1201
  return;
@@ -1199,12 +1207,9 @@ class ElectronBleTransport {
1199
1207
  }
1200
1208
  }
1201
1209
  catch (error) {
1202
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Electron BLE] Protocol V2 notification error:', error);
1203
- if (this.runPromise) {
1204
- const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1205
- this.runPromise.reject(notifyError);
1206
- this.rejectAllProtocolV2Frames(notifyError);
1207
- }
1210
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.error('[Electron BLE] Protocol V2 notification error:', error);
1211
+ const notifyError = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleWriteCharacteristicError);
1212
+ this.rejectProtocolV2Frames(deviceId, notifyError);
1208
1213
  }
1209
1214
  }
1210
1215
  getProtocolV2FrameQueue(uuid) {
@@ -1235,9 +1240,13 @@ class ElectronBleTransport {
1235
1240
  this.v2FrameQueues.delete(uuid);
1236
1241
  this.v2FramePromises.delete(uuid);
1237
1242
  }
1238
- isActiveProtocolV2Call(uuid, token) {
1239
- var _a;
1240
- return ((_a = this.activeProtocolV2Call) === null || _a === void 0 ? void 0 : _a.uuid) === uuid && this.activeProtocolV2Call.token === token;
1243
+ rejectProtocolV2Frames(uuid, error) {
1244
+ this.v2FrameQueues.delete(uuid);
1245
+ const framePromise = this.v2FramePromises.get(uuid);
1246
+ if (framePromise) {
1247
+ this.v2FramePromises.delete(uuid);
1248
+ framePromise.reject(error);
1249
+ }
1241
1250
  }
1242
1251
  readProtocolV2Frame(uuid) {
1243
1252
  return __awaiter(this, void 0, void 0, function* () {
@@ -1360,76 +1369,60 @@ class ElectronBleTransport {
1360
1369
  });
1361
1370
  }
1362
1371
  callProtocolV2(uuid, name, data, options) {
1363
- var _a, _b, _c, _d, _e;
1372
+ var _a, _b;
1364
1373
  return __awaiter(this, void 0, void 0, function* () {
1365
1374
  if (!this._messages || !this._messagesV2) {
1366
1375
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
1367
1376
  }
1368
- const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'Ping';
1369
- if (this.runPromise) {
1370
- if (!forceRun) {
1371
- throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportCallInProgress);
1372
- }
1373
- const error = hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleForceCleanRunPromise);
1374
- this.runPromise.reject(error);
1375
- this.rejectAllProtocolV2Frames(error);
1376
- this.runPromise = null;
1377
- this.activeProtocolV2Call = null;
1378
- }
1379
- const runPromise = hdShared.createDeferred();
1380
- runPromise.promise.catch(() => undefined);
1381
- this.runPromise = runPromise;
1382
- const callToken = this.nextProtocolV2CallToken++;
1383
- this.activeProtocolV2Call = { uuid, token: callToken };
1384
- (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1385
- this.resetProtocolV2Frames(uuid);
1386
- let completed = false;
1387
- const callOptions = Object.assign(Object.assign({}, options), { timeoutMs: (_b = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _b !== void 0 ? _b : BLE_RESPONSE_TIMEOUT_MS });
1377
+ const callOptions = Object.assign(Object.assign({}, options), { timeoutMs: (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : BLE_RESPONSE_TIMEOUT_MS });
1388
1378
  try {
1389
- const session = new transport.ProtocolV2Session({
1390
- schemas: {
1391
- protocolV1: this._messages,
1392
- protocolV2: this._messagesV2,
1393
- },
1394
- router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1395
- writeFrame: (frame) => this.writeWithChunking(uuid, transport.bytesToHex(frame)),
1396
- readFrame: () => __awaiter(this, void 0, void 0, function* () {
1397
- const rxFrame = yield this.readProtocolV2Frame(uuid);
1398
- if (!(rxFrame instanceof Uint8Array)) {
1399
- throw new Error('Response is not Uint8Array');
1400
- }
1401
- return rxFrame;
1402
- }),
1403
- logger: this.Log,
1404
- logPrefix: 'ProtocolV2 BLE',
1405
- createTimeoutError: (_messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${name}`),
1406
- });
1407
- const result = yield session.call(name, data, callOptions);
1408
- completed = true;
1409
- return result;
1379
+ return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, callOptions);
1410
1380
  }
1411
1381
  catch (e) {
1412
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1413
- (_c = this.v2Assemblers.get(uuid)) === null || _c === void 0 ? void 0 : _c.reset();
1414
- this.resetProtocolV2Frames(uuid);
1415
- }
1416
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.error('[Electron BLE] Protocol V2 call error:', e);
1382
+ (_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Electron BLE] Protocol V2 call error:', e);
1417
1383
  throw e;
1418
1384
  }
1419
- finally {
1420
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
1421
- if (!completed) {
1422
- (_e = this.v2Assemblers.get(uuid)) === null || _e === void 0 ? void 0 : _e.reset();
1423
- }
1424
- this.resetProtocolV2Frames(uuid);
1425
- this.activeProtocolV2Call = null;
1426
- }
1427
- if (this.runPromise === runPromise) {
1428
- this.runPromise = null;
1429
- }
1430
- }
1431
1385
  });
1432
1386
  }
1387
+ createProtocolV2Adapter(uuid) {
1388
+ var _a;
1389
+ const generation = (_a = this.notificationTokens.get(uuid)) !== null && _a !== void 0 ? _a : 0;
1390
+ const assertCurrentGeneration = () => {
1391
+ if (this.notificationTokens.get(uuid) !== generation) {
1392
+ throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
1393
+ }
1394
+ };
1395
+ return {
1396
+ router: transport.PROTOCOL_V2_CHANNEL_BLE_UART,
1397
+ generation,
1398
+ prepareCall: () => {
1399
+ var _a;
1400
+ assertCurrentGeneration();
1401
+ (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1402
+ this.resetProtocolV2Frames(uuid);
1403
+ },
1404
+ writeFrame: (frame) => {
1405
+ assertCurrentGeneration();
1406
+ return this.writeWithChunking(uuid, transport.bytesToHex(frame));
1407
+ },
1408
+ readFrame: () => __awaiter(this, void 0, void 0, function* () {
1409
+ assertCurrentGeneration();
1410
+ const rxFrame = yield this.readProtocolV2Frame(uuid);
1411
+ if (!(rxFrame instanceof Uint8Array)) {
1412
+ throw new Error('Response is not Uint8Array');
1413
+ }
1414
+ return rxFrame;
1415
+ }),
1416
+ reset: (reason) => {
1417
+ var _a;
1418
+ (_a = this.v2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
1419
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
1420
+ },
1421
+ logger: this.Log,
1422
+ logPrefix: 'ProtocolV2 BLE',
1423
+ createTimeoutError: (messageName, timeout) => hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.BleTimeoutError, `BLE response timeout after ${timeout}ms for ${messageName}`),
1424
+ };
1425
+ }
1433
1426
  processProtocolV1Notification(deviceId, hexData) {
1434
1427
  try {
1435
1428
  if (typeof hexData !== 'string') {
package/dist/webusb.d.ts CHANGED
@@ -50,6 +50,7 @@ export default class WebUsbTransport {
50
50
  private getTransferInData;
51
51
  private toArrayBuffer;
52
52
  private transferOutWithRetry;
53
+ private transferOutOnce;
53
54
  private transferInWithRetry;
54
55
  private resetConnectionAfterProbe;
55
56
  private withProtocolReadTimeout;
@@ -1 +1 @@
1
- {"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";AACA,OAAO,SAWN,MAAM,wBAAwB,CAAC;AAIhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAqChC,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;IAY7B,kBAAkB;IAmBlB,SAAS;IASf,OAAO,CAAC,aAAa;IAmBf,mBAAmB;IAsCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA0BjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;IA6CtB,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;YA0CpC,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;YAoCpB,mBAAmB;YA2CnB,yBAAyB;YAyBzB,uBAAuB;YAoCvB,eAAe;YAcf,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;YAkClB,cAAc;YAkCd,cAAc;YA4Cd,sBAAsB;IA4C9B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAiB1B,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,SAWN,MAAM,wBAAwB,CAAC;AAIhC,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAqChC,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;IAY7B,kBAAkB;IAmBlB,SAAS;IASf,OAAO,CAAC,aAAa;IAmBf,mBAAmB;IAsCnB,OAAO,CAAC,KAAK,EAAE,YAAY;IA0BjC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;YAOtB,cAAc;IA6CtB,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;YA0CpC,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;YAoCvB,eAAe;YAcf,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;YAkClB,cAAc;YAkCd,cAAc;YA4Cd,sBAAsB;IA4C9B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAgD5C,OAAO,CAAC,IAAI,EAAE,MAAM;IAiB1B,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.1",
3
+ "version": "1.2.0-alpha.10",
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.1",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.1"
23
+ "@onekeyfe/hd-shared": "1.2.0-alpha.10",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.10"
25
25
  },
26
26
  "devDependencies": {
27
- "@onekeyfe/hd-transport-electron": "1.2.0-alpha.1",
27
+ "@onekeyfe/hd-transport-electron": "1.2.0-alpha.10",
28
28
  "@types/w3c-web-usb": "^1.0.6",
29
29
  "@types/web-bluetooth": "^0.0.17"
30
30
  },
31
- "gitHead": "4f4aae3a47be7bd54b4db4a10c400056784e01bd"
31
+ "gitHead": "db8135cc75ab2d8b87efa14e28517da892c7aa34"
32
32
  }
@@ -3,7 +3,7 @@ import transport, {
3
3
  PROTOCOL_V1_MESSAGE_HEADER_SIZE,
4
4
  PROTOCOL_V2_CHANNEL_BLE_UART,
5
5
  ProtocolV2FrameAssembler,
6
- ProtocolV2Session,
6
+ ProtocolV2LinkManager,
7
7
  bytesToHex,
8
8
  hexToBytes,
9
9
  probeProtocolV2 as probeProtocolV2Helper,
@@ -72,8 +72,6 @@ const toBleDescriptor = (
72
72
 
73
73
  const BLE_PACKET_SIZE = 192;
74
74
  const BLE_WRITE_DELAY_MS = 5;
75
- const BLE_WRITE_MAX_RETRIES = 3;
76
- const BLE_WRITE_RETRY_DELAY_MS = 300;
77
75
  const BLE_RESPONSE_TIMEOUT_MS = 30_000;
78
76
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
79
77
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
@@ -113,9 +111,26 @@ export default class ElectronBleTransport {
113
111
 
114
112
  private v2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
115
113
 
116
- private activeProtocolV2Call: { uuid: string; token: number } | null = null;
117
-
118
- private nextProtocolV2CallToken = 1;
114
+ private protocolV2Links = new ProtocolV2LinkManager<string>({
115
+ getSchemas: () => {
116
+ if (!this._messages || !this._messagesV2) {
117
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
118
+ }
119
+ return {
120
+ protocolV1: this._messages,
121
+ protocolV2: this._messagesV2,
122
+ };
123
+ },
124
+ classifyError: () => 'link-fatal',
125
+ onLinkInvalidated: async (uuid, reason) => {
126
+ this.v2Assemblers.get(uuid)?.reset();
127
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
128
+ this.Log?.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
129
+ if (reason.startsWith('Protocol V2 link-fatal error:')) {
130
+ await this.release(uuid);
131
+ }
132
+ },
133
+ });
119
134
 
120
135
  private notificationCleanups: Map<string, () => void> = new Map();
121
136
 
@@ -157,6 +172,9 @@ export default class ElectronBleTransport {
157
172
  }
158
173
 
159
174
  private cleanupDeviceState(deviceId: string): void {
175
+ this.protocolV2Links
176
+ .invalidateLink(deviceId, 'Electron BLE device state cleaned')
177
+ .catch(error => this.Log?.debug('[Electron BLE] link cleanup failed:', error));
160
178
  this.connectedDevices.delete(deviceId);
161
179
  this.deviceProtocol.delete(deviceId);
162
180
  // Keep deviceProtocolHints — it's inferred from device name (e.g. "Pro 2" → V2)
@@ -164,9 +182,6 @@ export default class ElectronBleTransport {
164
182
  this.v1Buffers.delete(deviceId);
165
183
  this.v2Assemblers.delete(deviceId);
166
184
  this.resetProtocolV2Frames(deviceId);
167
- if (this.activeProtocolV2Call?.uuid === deviceId) {
168
- this.activeProtocolV2Call = null;
169
- }
170
185
  this.notificationTokens.delete(deviceId);
171
186
 
172
187
  const notifyCleanup = this.notificationCleanups.get(deviceId);
@@ -203,6 +218,9 @@ export default class ElectronBleTransport {
203
218
 
204
219
  configureProtocolV2(signedData: any) {
205
220
  this._messagesV2 = parseConfigure(signedData);
221
+ this.protocolV2Links
222
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
223
+ .catch(error => this.Log?.debug('[Electron BLE] schema link cleanup failed:', error));
206
224
  this.Log?.debug('[Electron BLE] Protocol V2 schema configured');
207
225
  }
208
226
 
@@ -238,12 +256,15 @@ export default class ElectronBleTransport {
238
256
  throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
239
257
  }
240
258
 
259
+ if (this.connectedDevices.has(uuid)) {
260
+ await this.release(uuid);
261
+ }
262
+
241
263
  if (forceCleanRunPromise && this.runPromise) {
242
264
  const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
243
265
  this.runPromise.reject(error);
244
266
  this.rejectAllProtocolV2Frames(error);
245
267
  this.runPromise = null;
246
- this.activeProtocolV2Call = null;
247
268
  }
248
269
 
249
270
  try {
@@ -320,6 +341,7 @@ export default class ElectronBleTransport {
320
341
 
321
342
  async release(id: string) {
322
343
  try {
344
+ await this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
323
345
  if (this.connectedDevices.has(id)) {
324
346
  if (window.desktopApi?.nobleBle) {
325
347
  await window.desktopApi.nobleBle.unsubscribe(id);
@@ -417,12 +439,13 @@ export default class ElectronBleTransport {
417
439
  }
418
440
 
419
441
  private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
442
+ await this.protocolV2Links.invalidateLink(
443
+ uuid,
444
+ `Reset notify state after Protocol ${protocol} probe`
445
+ );
420
446
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
421
447
  this.v2Assemblers.get(uuid)?.reset();
422
448
  this.resetProtocolV2Frames(uuid);
423
- if (this.activeProtocolV2Call?.uuid === uuid) {
424
- this.activeProtocolV2Call = null;
425
- }
426
449
  if (this.runPromise) {
427
450
  const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
428
451
  this.runPromise.reject(error);
@@ -496,7 +519,7 @@ export default class ElectronBleTransport {
496
519
 
497
520
  if (totalBytes <= BLE_PACKET_SIZE) {
498
521
  await wait(BLE_WRITE_DELAY_MS);
499
- await this.writeWithRetry(uuid, hexData);
522
+ await this.writeOnce(uuid, hexData);
500
523
  return;
501
524
  }
502
525
 
@@ -505,7 +528,7 @@ export default class ElectronBleTransport {
505
528
  const chunkHex = hexData.substring(offset, offset + chunkHexLen);
506
529
  offset += chunkHexLen;
507
530
 
508
- await this.writeWithRetry(uuid, chunkHex);
531
+ await this.writeOnce(uuid, chunkHex);
509
532
 
510
533
  if (offset < hexData.length) {
511
534
  await wait(BLE_WRITE_DELAY_MS);
@@ -513,41 +536,23 @@ export default class ElectronBleTransport {
513
536
  }
514
537
  }
515
538
 
516
- private async writeWithRetry(uuid: string, hexData: string): Promise<void> {
517
- let lastError: any;
539
+ private async writeOnce(uuid: string, hexData: string): Promise<void> {
518
540
  const nobleBle = window.desktopApi?.nobleBle;
519
541
  if (!nobleBle) {
520
542
  throw new Error('Noble BLE API not available');
521
543
  }
522
544
 
523
- for (let attempt = 1; attempt <= BLE_WRITE_MAX_RETRIES; attempt++) {
524
- try {
525
- await nobleBle.write(uuid, hexData);
526
- return;
527
- } catch (error) {
528
- lastError = error;
529
- this.Log?.error(
530
- `[Electron BLE] write failed (attempt ${attempt}/${BLE_WRITE_MAX_RETRIES}):`,
531
- error
532
- );
533
- if (attempt < BLE_WRITE_MAX_RETRIES) {
534
- await wait(BLE_WRITE_RETRY_DELAY_MS);
535
- }
536
- }
537
- }
538
- throw ERRORS.TypedError(
539
- HardwareErrorCode.BleWriteCharacteristicError,
540
- `BLE write failed after ${BLE_WRITE_MAX_RETRIES} attempts: ${lastError?.message ?? lastError}`
541
- );
545
+ await nobleBle.write(uuid, hexData);
542
546
  }
543
547
 
544
548
  private handleNotification(deviceId: string, hexData: string): void {
545
549
  if (hexData === 'PAIRING_REJECTED') {
546
550
  this.Log?.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
547
- if (this.runPromise) {
548
- const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
551
+ const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
552
+ if (this.deviceProtocol.get(deviceId) === 'V2') {
553
+ this.rejectProtocolV2Frames(deviceId, error);
554
+ } else if (this.runPromise) {
549
555
  this.runPromise.reject(error);
550
- this.rejectAllProtocolV2Frames(error);
551
556
  }
552
557
  return;
553
558
  }
@@ -566,12 +571,6 @@ export default class ElectronBleTransport {
566
571
 
567
572
  private handleProtocolV2Notification(deviceId: string, hexData: string): void {
568
573
  try {
569
- if (!this.runPromise || this.activeProtocolV2Call?.uuid !== deviceId) {
570
- this.v2Assemblers.get(deviceId)?.reset();
571
- this.resetProtocolV2Frames(deviceId);
572
- return;
573
- }
574
-
575
574
  const bytes = hexToBytes(hexData);
576
575
  if (bytes.length === 0) return;
577
576
 
@@ -583,11 +582,8 @@ export default class ElectronBleTransport {
583
582
  }
584
583
  } catch (error) {
585
584
  this.Log?.error('[Electron BLE] Protocol V2 notification error:', error);
586
- if (this.runPromise) {
587
- const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
588
- this.runPromise.reject(notifyError);
589
- this.rejectAllProtocolV2Frames(notifyError);
590
- }
585
+ const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
586
+ this.rejectProtocolV2Frames(deviceId, notifyError);
591
587
  }
592
588
  }
593
589
 
@@ -623,8 +619,13 @@ export default class ElectronBleTransport {
623
619
  this.v2FramePromises.delete(uuid);
624
620
  }
625
621
 
626
- private isActiveProtocolV2Call(uuid: string, token: number) {
627
- return this.activeProtocolV2Call?.uuid === uuid && this.activeProtocolV2Call.token === token;
622
+ private rejectProtocolV2Frames(uuid: string, error: Error) {
623
+ this.v2FrameQueues.delete(uuid);
624
+ const framePromise = this.v2FramePromises.get(uuid);
625
+ if (framePromise) {
626
+ this.v2FramePromises.delete(uuid);
627
+ framePromise.reject(error);
628
+ }
628
629
  }
629
630
 
630
631
  private async readProtocolV2Frame(uuid: string) {
@@ -776,79 +777,67 @@ export default class ElectronBleTransport {
776
777
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
777
778
  }
778
779
 
779
- const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'Ping';
780
- if (this.runPromise) {
781
- if (!forceRun) {
782
- throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
783
- }
784
- const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
785
- this.runPromise.reject(error);
786
- this.rejectAllProtocolV2Frames(error);
787
- this.runPromise = null;
788
- this.activeProtocolV2Call = null;
789
- }
790
-
791
- const runPromise = createDeferred<Uint8Array | string>();
792
- runPromise.promise.catch(() => undefined);
793
- this.runPromise = runPromise;
794
- const callToken = this.nextProtocolV2CallToken++;
795
- this.activeProtocolV2Call = { uuid, token: callToken };
796
- this.v2Assemblers.get(uuid)?.reset();
797
- this.resetProtocolV2Frames(uuid);
798
- let completed = false;
799
780
  const callOptions = {
800
781
  ...options,
801
782
  timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
802
783
  };
803
784
 
804
785
  try {
805
- const session = new ProtocolV2Session({
806
- schemas: {
807
- protocolV1: this._messages,
808
- protocolV2: this._messagesV2,
809
- },
810
- router: PROTOCOL_V2_CHANNEL_BLE_UART,
811
- writeFrame: (frame: Uint8Array) => this.writeWithChunking(uuid, bytesToHex(frame)),
812
- readFrame: async () => {
813
- const rxFrame = await this.readProtocolV2Frame(uuid);
814
- if (!(rxFrame instanceof Uint8Array)) {
815
- throw new Error('Response is not Uint8Array');
816
- }
817
- return rxFrame;
818
- },
819
- logger: this.Log,
820
- logPrefix: 'ProtocolV2 BLE',
821
- createTimeoutError: (_messageName: string, timeout: number) =>
822
- ERRORS.TypedError(
823
- HardwareErrorCode.BleTimeoutError,
824
- `BLE response timeout after ${timeout}ms for ${name}`
825
- ),
826
- });
827
-
828
- const result = await session.call(name, data, callOptions);
829
- completed = true;
830
- return result;
786
+ return await this.protocolV2Links.call(
787
+ uuid,
788
+ () => this.createProtocolV2Adapter(uuid),
789
+ name,
790
+ data,
791
+ callOptions
792
+ );
831
793
  } catch (e) {
832
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
833
- this.v2Assemblers.get(uuid)?.reset();
834
- this.resetProtocolV2Frames(uuid);
835
- }
836
794
  this.Log?.error('[Electron BLE] Protocol V2 call error:', e);
837
795
  throw e;
838
- } finally {
839
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
840
- if (!completed) {
841
- this.v2Assemblers.get(uuid)?.reset();
842
- }
843
- this.resetProtocolV2Frames(uuid);
844
- this.activeProtocolV2Call = null;
845
- }
846
- if (this.runPromise === runPromise) {
847
- this.runPromise = null;
848
- }
849
796
  }
850
797
  }
851
798
 
799
+ private createProtocolV2Adapter(uuid: string) {
800
+ const generation = this.notificationTokens.get(uuid) ?? 0;
801
+ const assertCurrentGeneration = () => {
802
+ if (this.notificationTokens.get(uuid) !== generation) {
803
+ throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
804
+ }
805
+ };
806
+
807
+ return {
808
+ router: PROTOCOL_V2_CHANNEL_BLE_UART,
809
+ generation,
810
+ prepareCall: () => {
811
+ assertCurrentGeneration();
812
+ this.v2Assemblers.get(uuid)?.reset();
813
+ this.resetProtocolV2Frames(uuid);
814
+ },
815
+ writeFrame: (frame: Uint8Array) => {
816
+ assertCurrentGeneration();
817
+ return this.writeWithChunking(uuid, bytesToHex(frame));
818
+ },
819
+ readFrame: async () => {
820
+ assertCurrentGeneration();
821
+ const rxFrame = await this.readProtocolV2Frame(uuid);
822
+ if (!(rxFrame instanceof Uint8Array)) {
823
+ throw new Error('Response is not Uint8Array');
824
+ }
825
+ return rxFrame;
826
+ },
827
+ reset: (reason: string) => {
828
+ this.v2Assemblers.get(uuid)?.reset();
829
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
830
+ },
831
+ logger: this.Log,
832
+ logPrefix: 'ProtocolV2 BLE',
833
+ createTimeoutError: (messageName: string, timeout: number) =>
834
+ ERRORS.TypedError(
835
+ HardwareErrorCode.BleTimeoutError,
836
+ `BLE response timeout after ${timeout}ms for ${messageName}`
837
+ ),
838
+ };
839
+ }
840
+
852
841
  private processProtocolV1Notification(deviceId: string, hexData: string): PacketProcessResult {
853
842
  try {
854
843
  if (typeof hexData !== 'string') {
package/src/webusb.ts CHANGED
@@ -571,16 +571,7 @@ export default class WebUsbTransport {
571
571
  let lastError: unknown;
572
572
  for (let attempt = 1; attempt <= PACKET_IO_MAX_RETRIES; attempt += 1) {
573
573
  try {
574
- const device = await this.findDevice(path);
575
- if (!device.opened) {
576
- await this.connect(path, false);
577
- }
578
- const endpoints = this.deviceEndpoints.get(path);
579
- const endpointOut = endpoints?.endpointOut ?? this.endpointId;
580
- const transferBuffer = this.toArrayBuffer(
581
- packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
582
- );
583
- await device.transferOut(endpointOut, transferBuffer);
574
+ await this.transferOutOnce(path, packet);
584
575
  return;
585
576
  } catch (error) {
586
577
  lastError = error;
@@ -603,6 +594,19 @@ export default class WebUsbTransport {
603
594
  throw lastError;
604
595
  }
605
596
 
597
+ private async transferOutOnce(path: string, packet: Uint8Array) {
598
+ const device = await this.findDevice(path);
599
+ if (!device.opened) {
600
+ await this.connect(path, false);
601
+ }
602
+ const endpoints = this.deviceEndpoints.get(path);
603
+ const endpointOut = endpoints?.endpointOut ?? this.endpointId;
604
+ const transferBuffer = this.toArrayBuffer(
605
+ packet.buffer.slice(packet.byteOffset, packet.byteOffset + packet.byteLength)
606
+ );
607
+ await device.transferOut(endpointOut, transferBuffer);
608
+ }
609
+
606
610
  private async transferInWithRetry(
607
611
  path: string,
608
612
  length: number,
@@ -835,7 +839,7 @@ export default class WebUsbTransport {
835
839
  protocolV2: this.messagesV2,
836
840
  },
837
841
  router: PROTOCOL_V2_CHANNEL_USB,
838
- writeFrame: (frame: Uint8Array) => this.transferOutWithRetry(path, frame),
842
+ writeFrame: (frame: Uint8Array) => this.transferOutOnce(path, frame),
839
843
  readFrame: () => this.receiveProtocolV2Frame(path, this.protocolV2ReadTimeouts.get(path)),
840
844
  logger: this.Log,
841
845
  logPrefix: 'ProtocolV2 WebUSB',