@onekeyfe/hd-transport-web-device 1.2.0-alpha.4 → 1.2.0-alpha.41

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,12 +1,14 @@
1
1
  import transport, {
2
- LogBlockCommand,
3
2
  PROTOCOL_V1_MESSAGE_HEADER_SIZE,
3
+ PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
4
4
  PROTOCOL_V2_CHANNEL_BLE_UART,
5
5
  ProtocolV2FrameAssembler,
6
- ProtocolV2Session,
6
+ ProtocolV2LinkManager,
7
+ TRANSPORT_EVENT,
7
8
  bytesToHex,
8
9
  hexToBytes,
9
10
  probeProtocolV2 as probeProtocolV2Helper,
11
+ writeProtocolV2BleFrame,
10
12
  } from '@onekeyfe/hd-transport';
11
13
  import {
12
14
  ERRORS,
@@ -14,25 +16,20 @@ import {
14
16
  HardwareErrorCodeMessage,
15
17
  createDeferred,
16
18
  isHeaderChunk,
17
- wait,
18
19
  } from '@onekeyfe/hd-shared';
19
20
 
21
+ import { createTransportCallLog, shouldSuppressHighVolumeCallLog } from './transportLog';
22
+
20
23
  import type { Deferred } from '@onekeyfe/hd-shared';
21
24
  import type { DesktopAPI } from '@onekeyfe/hd-transport-electron';
22
- import type { OneKeyDeviceInfo, ProtocolType, TransportCallOptions } from '@onekeyfe/hd-transport';
25
+ import type {
26
+ OneKeyDeviceInfo,
27
+ ProtocolType,
28
+ ProtocolV2CallContext,
29
+ TransportCallOptions,
30
+ } from '@onekeyfe/hd-transport';
23
31
  import type EventEmitter from 'events';
24
32
 
25
- const FILE_WRITE_LOG_BLOCK_PATTERN = /(?:^|[^a-z])(?:raw)?(?:filesystem|emmc)?filewrite$/i;
26
-
27
- function shouldSuppressHighVolumeCallLog(name: string) {
28
- const normalized = name.replace(/[_\s-]/g, '');
29
- return FILE_WRITE_LOG_BLOCK_PATTERN.test(normalized);
30
- }
31
-
32
- function isLogBlockCommand(name: string) {
33
- return (LogBlockCommand as Set<string> | undefined)?.has?.(name) ?? false;
34
- }
35
-
36
33
  const { parseConfigure, ProtocolV1, check } = transport;
37
34
 
38
35
  declare global {
@@ -45,6 +42,7 @@ export type BleAcquireInput = {
45
42
  uuid: string;
46
43
  forceCleanRunPromise?: boolean;
47
44
  expectedProtocol?: ProtocolType;
45
+ protocolHint?: ProtocolType;
48
46
  };
49
47
 
50
48
  interface PacketProcessResult {
@@ -72,7 +70,6 @@ const toBleDescriptor = (
72
70
 
73
71
  const BLE_PACKET_SIZE = 192;
74
72
  const BLE_WRITE_DELAY_MS = 5;
75
- const BLE_RESPONSE_TIMEOUT_MS = 30_000;
76
73
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
77
74
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
78
75
 
@@ -80,19 +77,23 @@ const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
80
77
  * Desktop Electron BLE transport with automatic Protocol V1/V2 detection.
81
78
  *
82
79
  * Protocol V1 devices continue using chunked packets. Protocol V2 is detected
83
- * after a Protocol V1 Initialize timeout by probing Protocol V2 Ping.
80
+ * after a Protocol V1 GetFeatures timeout by probing Protocol V2 Ping.
84
81
  */
85
82
  export default class ElectronBleTransport {
86
83
  private _messages: ReturnType<typeof transport.parseConfigure> | undefined;
87
84
 
88
85
  private _messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
89
86
 
87
+ private protocolV2SchemaConfiguration: string | undefined;
88
+
90
89
  name = 'ElectronBleTransport';
91
90
 
92
91
  configured = false;
93
92
 
94
93
  runPromise: Deferred<Uint8Array | string> | null = null;
95
94
 
95
+ private runPromiseDeviceId: string | null = null;
96
+
96
97
  Log?: any;
97
98
 
98
99
  emitter?: EventEmitter;
@@ -111,9 +112,26 @@ export default class ElectronBleTransport {
111
112
 
112
113
  private v2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
113
114
 
114
- private activeProtocolV2Call: { uuid: string; token: number } | null = null;
115
-
116
- private nextProtocolV2CallToken = 1;
115
+ private protocolV2Links = new ProtocolV2LinkManager<string>({
116
+ getSchemas: () => {
117
+ if (!this._messages || !this._messagesV2) {
118
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
119
+ }
120
+ return {
121
+ protocolV1: this._messages,
122
+ protocolV2: this._messagesV2,
123
+ };
124
+ },
125
+ classifyError: () => 'link-fatal',
126
+ onLinkInvalidated: async (uuid, reason) => {
127
+ this.v2Assemblers.get(uuid)?.reset();
128
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
129
+ this.Log?.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
130
+ if (reason.startsWith('Protocol V2 link-fatal error:')) {
131
+ await this.releaseNative(uuid);
132
+ }
133
+ },
134
+ });
117
135
 
118
136
  private notificationCleanups: Map<string, () => void> = new Map();
119
137
 
@@ -155,6 +173,9 @@ export default class ElectronBleTransport {
155
173
  }
156
174
 
157
175
  private cleanupDeviceState(deviceId: string): void {
176
+ this.protocolV2Links
177
+ .invalidateLink(deviceId, 'Electron BLE device state cleaned')
178
+ .catch(error => this.Log?.debug('[Electron BLE] link cleanup failed:', error));
158
179
  this.connectedDevices.delete(deviceId);
159
180
  this.deviceProtocol.delete(deviceId);
160
181
  // Keep deviceProtocolHints — it's inferred from device name (e.g. "Pro 2" → V2)
@@ -162,10 +183,12 @@ export default class ElectronBleTransport {
162
183
  this.v1Buffers.delete(deviceId);
163
184
  this.v2Assemblers.delete(deviceId);
164
185
  this.resetProtocolV2Frames(deviceId);
165
- if (this.activeProtocolV2Call?.uuid === deviceId) {
166
- this.activeProtocolV2Call = null;
167
- }
168
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
+ }
169
192
 
170
193
  const notifyCleanup = this.notificationCleanups.get(deviceId);
171
194
  if (notifyCleanup) {
@@ -200,8 +223,19 @@ export default class ElectronBleTransport {
200
223
  }
201
224
 
202
225
  configureProtocolV2(signedData: any) {
226
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
227
+ if (this.protocolV2SchemaConfiguration === configuration) {
228
+ return;
229
+ }
230
+
231
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
203
232
  this._messagesV2 = parseConfigure(signedData);
204
- this.Log?.debug('[Electron BLE] Protocol V2 schema configured');
233
+ this.protocolV2SchemaConfiguration = configuration;
234
+ if (isReconfiguration) {
235
+ this.protocolV2Links
236
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
237
+ .catch(error => this.Log?.debug('[Electron BLE] schema link cleanup failed:', error));
238
+ }
205
239
  }
206
240
 
207
241
  async listen() {
@@ -236,12 +270,15 @@ export default class ElectronBleTransport {
236
270
  throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
237
271
  }
238
272
 
273
+ if (this.connectedDevices.has(uuid)) {
274
+ await this.release(uuid);
275
+ }
276
+
239
277
  if (forceCleanRunPromise && this.runPromise) {
240
278
  const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
241
279
  this.runPromise.reject(error);
242
- this.rejectAllProtocolV2Frames(error);
243
280
  this.runPromise = null;
244
- this.activeProtocolV2Call = null;
281
+ this.runPromiseDeviceId = null;
245
282
  }
246
283
 
247
284
  try {
@@ -255,7 +292,9 @@ export default class ElectronBleTransport {
255
292
  }
256
293
  const protocolHint = expectedProtocol
257
294
  ? undefined
258
- : this.deviceProtocolHints.get(uuid) ?? inferProtocolHintFromDeviceName(device.name);
295
+ : input.protocolHint ??
296
+ this.deviceProtocolHints.get(uuid) ??
297
+ inferProtocolHintFromDeviceName(device.name);
259
298
  if (protocolHint) {
260
299
  this.deviceProtocolHints.set(uuid, protocolHint);
261
300
  }
@@ -268,18 +307,20 @@ export default class ElectronBleTransport {
268
307
  }
269
308
 
270
309
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
271
- this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
310
+ this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
272
311
 
273
312
  await window.desktopApi.nobleBle.subscribe(uuid);
274
313
 
275
314
  const cleanup = this.createNotificationSubscription(uuid);
276
315
  this.notificationCleanups.set(uuid, cleanup);
277
316
 
317
+ const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
318
+
278
319
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected(
279
320
  (disconnectedDevice: any) => {
280
321
  if (disconnectedDevice.id === uuid) {
281
322
  this.cleanupDeviceState(uuid);
282
- this.emitter?.emit('device-disconnect', {
323
+ this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
283
324
  name: disconnectedDevice.name,
284
325
  id: disconnectedDevice.id,
285
326
  connectId: disconnectedDevice.id,
@@ -289,14 +330,6 @@ export default class ElectronBleTransport {
289
330
  );
290
331
  this.disconnectCleanups.set(uuid, disconnectCleanup);
291
332
 
292
- const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
293
-
294
- this.emitter?.emit('device-connect', {
295
- name: device.name,
296
- id: device.id,
297
- connectId: device.id,
298
- });
299
-
300
333
  return {
301
334
  ...toBleDescriptor({ id: device.id, name: device.name }, protocolType),
302
335
  uuid,
@@ -317,6 +350,16 @@ export default class ElectronBleTransport {
317
350
  }
318
351
 
319
352
  async release(id: string) {
353
+ try {
354
+ await this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
355
+ await this.releaseNative(id);
356
+ } catch (error) {
357
+ this.Log?.error('[Electron BLE] release failed:', error);
358
+ this.cleanupDeviceState(id);
359
+ }
360
+ }
361
+
362
+ private async releaseNative(id: string) {
320
363
  try {
321
364
  if (this.connectedDevices.has(id)) {
322
365
  if (window.desktopApi?.nobleBle) {
@@ -341,7 +384,7 @@ export default class ElectronBleTransport {
341
384
  private createProtocolDetectionError() {
342
385
  return ERRORS.TypedError(
343
386
  HardwareErrorCode.BleTimeoutError,
344
- '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'
345
388
  );
346
389
  }
347
390
 
@@ -366,23 +409,23 @@ export default class ElectronBleTransport {
366
409
  }
367
410
 
368
411
  if (expectedProtocol === 'V2') {
369
- // 免探测路径:调用方显式承诺该设备是 V2(例如固件升级重启后的重连场景,
370
- // 上层已经探测过协议并通过 expectedProtocol 传回),这里不再重复探测。
371
- this.deviceProtocol.set(uuid, 'V2');
372
- this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
373
- return 'V2';
412
+ if (await this.probeProtocolV2(uuid)) {
413
+ this.deviceProtocol.set(uuid, 'V2');
414
+ this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
415
+ return 'V2';
416
+ }
417
+ throw this.createProtocolMismatchError(expectedProtocol);
374
418
  }
375
419
 
376
- // 项目约束:协议判断必须在连接后主动探测,不能依赖设备名/PID/descriptor。
377
- // 设备名 hint(如 "Pro 2")只用于调整探测顺序:hint=V2 时先探 V2、失败回落 V1,
378
- // 不能作为最终结论。
420
+ // Protocol must be actively probed after connection. Name, PID, and descriptors only
421
+ // influence probe order; a V2 hint probes V2 first and falls back to V1.
379
422
  const probeOrder: ProtocolType[] =
380
423
  protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
381
424
 
382
425
  for (let i = 0; i < probeOrder.length; i += 1) {
383
426
  const protocol = probeOrder[i];
384
427
  if (i > 0) {
385
- // 上一个协议探测失败后,重置订阅与缓冲,避免残留数据干扰下一个协议的探测。
428
+ // Reset subscriptions and buffers after a failed probe before trying another protocol.
386
429
  await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
387
430
  }
388
431
  const detected =
@@ -415,16 +458,18 @@ export default class ElectronBleTransport {
415
458
  }
416
459
 
417
460
  private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
461
+ await this.protocolV2Links.invalidateLink(
462
+ uuid,
463
+ `Reset notify state after Protocol ${protocol} probe`
464
+ );
418
465
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
419
466
  this.v2Assemblers.get(uuid)?.reset();
420
467
  this.resetProtocolV2Frames(uuid);
421
- if (this.activeProtocolV2Call?.uuid === uuid) {
422
- this.activeProtocolV2Call = null;
423
- }
424
- if (this.runPromise) {
468
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
425
469
  const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
426
470
  this.runPromise.reject(error);
427
471
  this.runPromise = null;
472
+ this.runPromiseDeviceId = null;
428
473
  }
429
474
 
430
475
  const notifyCleanup = this.notificationCleanups.get(uuid);
@@ -440,9 +485,17 @@ export default class ElectronBleTransport {
440
485
  this.Log?.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
441
486
  }
442
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);
443
496
  await window.desktopApi?.nobleBle?.subscribe(uuid);
444
497
  } catch (error) {
445
- 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);
446
499
  throw error;
447
500
  }
448
501
 
@@ -457,11 +510,13 @@ export default class ElectronBleTransport {
457
510
 
458
511
  try {
459
512
  this.deviceProtocol.set(uuid, 'V1');
460
- 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 });
461
516
  return true;
462
517
  } catch (error) {
463
518
  this.clearProbeProtocol(uuid, 'V1');
464
- this.Log?.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
519
+ this.Log?.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
465
520
  return false;
466
521
  }
467
522
  }
@@ -489,28 +544,6 @@ export default class ElectronBleTransport {
489
544
  return detected;
490
545
  }
491
546
 
492
- private async writeWithChunking(uuid: string, hexData: string): Promise<void> {
493
- const totalBytes = hexData.length / 2;
494
-
495
- if (totalBytes <= BLE_PACKET_SIZE) {
496
- await wait(BLE_WRITE_DELAY_MS);
497
- await this.writeOnce(uuid, hexData);
498
- return;
499
- }
500
-
501
- for (let offset = 0; offset < hexData.length; ) {
502
- const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
503
- const chunkHex = hexData.substring(offset, offset + chunkHexLen);
504
- offset += chunkHexLen;
505
-
506
- await this.writeOnce(uuid, chunkHex);
507
-
508
- if (offset < hexData.length) {
509
- await wait(BLE_WRITE_DELAY_MS);
510
- }
511
- }
512
- }
513
-
514
547
  private async writeOnce(uuid: string, hexData: string): Promise<void> {
515
548
  const nobleBle = window.desktopApi?.nobleBle;
516
549
  if (!nobleBle) {
@@ -520,13 +553,33 @@ export default class ElectronBleTransport {
520
553
  await nobleBle.write(uuid, hexData);
521
554
  }
522
555
 
556
+ private writeProtocolV2Frame(
557
+ uuid: string,
558
+ frame: Uint8Array,
559
+ context: ProtocolV2CallContext,
560
+ assertCurrentGeneration: () => void
561
+ ) {
562
+ return writeProtocolV2BleFrame({
563
+ frame,
564
+ packetCapacity: BLE_PACKET_SIZE,
565
+ assertActive: assertCurrentGeneration,
566
+ signal: context.signal,
567
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
568
+ initialDelayMs: frame.length <= BLE_PACKET_SIZE ? BLE_WRITE_DELAY_MS : 0,
569
+ burstSize: 1,
570
+ burstPauseMs: BLE_WRITE_DELAY_MS,
571
+ writePacket: packet => this.writeOnce(uuid, bytesToHex(packet)),
572
+ });
573
+ }
574
+
523
575
  private handleNotification(deviceId: string, hexData: string): void {
524
576
  if (hexData === 'PAIRING_REJECTED') {
525
577
  this.Log?.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
526
- if (this.runPromise) {
527
- const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
578
+ const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
579
+ if (this.deviceProtocol.get(deviceId) === 'V2') {
580
+ this.rejectProtocolV2Frames(deviceId, error);
581
+ } else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
528
582
  this.runPromise.reject(error);
529
- this.rejectAllProtocolV2Frames(error);
530
583
  }
531
584
  return;
532
585
  }
@@ -545,12 +598,6 @@ export default class ElectronBleTransport {
545
598
 
546
599
  private handleProtocolV2Notification(deviceId: string, hexData: string): void {
547
600
  try {
548
- if (!this.runPromise || this.activeProtocolV2Call?.uuid !== deviceId) {
549
- this.v2Assemblers.get(deviceId)?.reset();
550
- this.resetProtocolV2Frames(deviceId);
551
- return;
552
- }
553
-
554
601
  const bytes = hexToBytes(hexData);
555
602
  if (bytes.length === 0) return;
556
603
 
@@ -562,11 +609,8 @@ export default class ElectronBleTransport {
562
609
  }
563
610
  } catch (error) {
564
611
  this.Log?.error('[Electron BLE] Protocol V2 notification error:', error);
565
- if (this.runPromise) {
566
- const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
567
- this.runPromise.reject(notifyError);
568
- this.rejectAllProtocolV2Frames(notifyError);
569
- }
612
+ const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
613
+ this.rejectProtocolV2Frames(deviceId, notifyError);
570
614
  }
571
615
  }
572
616
 
@@ -589,21 +633,17 @@ export default class ElectronBleTransport {
589
633
  this.getProtocolV2FrameQueue(uuid).push(frame);
590
634
  }
591
635
 
592
- private rejectAllProtocolV2Frames(error: Error) {
593
- this.v2FrameQueues.clear();
594
- for (const framePromise of this.v2FramePromises.values()) {
595
- framePromise.reject(error);
596
- }
597
- this.v2FramePromises.clear();
598
- }
599
-
600
636
  private resetProtocolV2Frames(uuid: string) {
601
- this.v2FrameQueues.delete(uuid);
602
- this.v2FramePromises.delete(uuid);
637
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
603
638
  }
604
639
 
605
- private isActiveProtocolV2Call(uuid: string, token: number) {
606
- return this.activeProtocolV2Call?.uuid === uuid && this.activeProtocolV2Call.token === token;
640
+ private rejectProtocolV2Frames(uuid: string, error: Error) {
641
+ this.v2FrameQueues.delete(uuid);
642
+ const framePromise = this.v2FramePromises.get(uuid);
643
+ if (framePromise) {
644
+ this.v2FramePromises.delete(uuid);
645
+ framePromise.reject(error);
646
+ }
607
647
  }
608
648
 
609
649
  private async readProtocolV2Frame(uuid: string) {
@@ -628,13 +668,18 @@ export default class ElectronBleTransport {
628
668
 
629
669
  if (result.error) {
630
670
  this.Log?.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
631
- if (this.runPromise) {
671
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
632
672
  this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
633
673
  }
634
674
  return;
635
675
  }
636
676
 
637
- if (result.isComplete && result.completePacket && this.runPromise) {
677
+ if (
678
+ result.isComplete &&
679
+ result.completePacket &&
680
+ this.runPromise &&
681
+ this.runPromiseDeviceId === deviceId
682
+ ) {
638
683
  this.runPromise.resolve(result.completePacket);
639
684
  }
640
685
  }
@@ -660,12 +705,8 @@ export default class ElectronBleTransport {
660
705
  `Device protocol has not been detected for ${uuid}`
661
706
  );
662
707
  }
663
- if (shouldSuppressHighVolumeCallLog(name)) {
664
- // 高频文件写入不要逐包发 debug 事件,否则调试日志会反向拖慢传输。
665
- } else if (isLogBlockCommand(name)) {
666
- this.Log?.debug('[Electron BLE] call', 'name:', name, 'protocol:', protocol);
667
- } else {
668
- this.Log?.debug('[Electron BLE] call', 'name:', name, 'data:', data, 'protocol:', protocol);
708
+ if (!shouldSuppressHighVolumeCallLog(name)) {
709
+ this.Log?.debug('transport call', createTransportCallLog(name, protocol, data));
669
710
  }
670
711
 
671
712
  if (protocol === 'V2') {
@@ -692,6 +733,7 @@ export default class ElectronBleTransport {
692
733
  const runPromise = createDeferred<Uint8Array | string>();
693
734
  runPromise.promise.catch(() => undefined);
694
735
  this.runPromise = runPromise;
736
+ this.runPromiseDeviceId = uuid;
695
737
  const messages = this._messages;
696
738
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
697
739
  let timeout: ReturnType<typeof setTimeout> | undefined;
@@ -736,11 +778,24 @@ export default class ElectronBleTransport {
736
778
  return check.call(jsonData);
737
779
  } catch (e) {
738
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
+ }
739
793
  throw e;
740
794
  } finally {
741
795
  if (timeout) clearTimeout(timeout);
742
796
  if (this.runPromise === runPromise) {
743
797
  this.runPromise = null;
798
+ this.runPromiseDeviceId = null;
744
799
  }
745
800
  }
746
801
  }
@@ -755,77 +810,61 @@ export default class ElectronBleTransport {
755
810
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
756
811
  }
757
812
 
758
- const forceRun = name === 'Initialize' || name === 'Cancel' || name === 'Ping';
759
- if (this.runPromise) {
760
- if (!forceRun) {
761
- throw ERRORS.TypedError(HardwareErrorCode.TransportCallInProgress);
762
- }
763
- const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
764
- this.runPromise.reject(error);
765
- this.rejectAllProtocolV2Frames(error);
766
- this.runPromise = null;
767
- this.activeProtocolV2Call = null;
813
+ const callOptions = options;
814
+
815
+ try {
816
+ return await this.protocolV2Links.call(
817
+ uuid,
818
+ () => this.createProtocolV2Adapter(uuid),
819
+ name,
820
+ data,
821
+ callOptions
822
+ );
823
+ } catch (e) {
824
+ this.Log?.error('[Electron BLE] Protocol V2 call error:', e);
825
+ throw e;
768
826
  }
827
+ }
769
828
 
770
- const runPromise = createDeferred<Uint8Array | string>();
771
- runPromise.promise.catch(() => undefined);
772
- this.runPromise = runPromise;
773
- const callToken = this.nextProtocolV2CallToken++;
774
- this.activeProtocolV2Call = { uuid, token: callToken };
775
- this.v2Assemblers.get(uuid)?.reset();
776
- this.resetProtocolV2Frames(uuid);
777
- let completed = false;
778
- const callOptions = {
779
- ...options,
780
- timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
829
+ private createProtocolV2Adapter(uuid: string) {
830
+ const generation = this.notificationTokens.get(uuid) ?? 0;
831
+ const assertCurrentGeneration = () => {
832
+ if (this.notificationTokens.get(uuid) !== generation) {
833
+ throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
834
+ }
781
835
  };
782
836
 
783
- try {
784
- const session = new ProtocolV2Session({
785
- schemas: {
786
- protocolV1: this._messages,
787
- protocolV2: this._messagesV2,
788
- },
789
- router: PROTOCOL_V2_CHANNEL_BLE_UART,
790
- writeFrame: (frame: Uint8Array) => this.writeWithChunking(uuid, bytesToHex(frame)),
791
- readFrame: async () => {
792
- const rxFrame = await this.readProtocolV2Frame(uuid);
793
- if (!(rxFrame instanceof Uint8Array)) {
794
- throw new Error('Response is not Uint8Array');
795
- }
796
- return rxFrame;
797
- },
798
- logger: this.Log,
799
- logPrefix: 'ProtocolV2 BLE',
800
- createTimeoutError: (_messageName: string, timeout: number) =>
801
- ERRORS.TypedError(
802
- HardwareErrorCode.BleTimeoutError,
803
- `BLE response timeout after ${timeout}ms for ${name}`
804
- ),
805
- });
806
-
807
- const result = await session.call(name, data, callOptions);
808
- completed = true;
809
- return result;
810
- } catch (e) {
811
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
837
+ return {
838
+ router: PROTOCOL_V2_CHANNEL_BLE_UART,
839
+ maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
840
+ generation,
841
+ prepareCall: () => {
842
+ assertCurrentGeneration();
812
843
  this.v2Assemblers.get(uuid)?.reset();
813
844
  this.resetProtocolV2Frames(uuid);
814
- }
815
- this.Log?.error('[Electron BLE] Protocol V2 call error:', e);
816
- throw e;
817
- } finally {
818
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
819
- if (!completed) {
820
- this.v2Assemblers.get(uuid)?.reset();
845
+ },
846
+ writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
847
+ this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
848
+ readFrame: async () => {
849
+ assertCurrentGeneration();
850
+ const rxFrame = await this.readProtocolV2Frame(uuid);
851
+ if (!(rxFrame instanceof Uint8Array)) {
852
+ throw new Error('Response is not Uint8Array');
821
853
  }
822
- this.resetProtocolV2Frames(uuid);
823
- this.activeProtocolV2Call = null;
824
- }
825
- if (this.runPromise === runPromise) {
826
- this.runPromise = null;
827
- }
828
- }
854
+ return rxFrame;
855
+ },
856
+ reset: (reason: string) => {
857
+ this.v2Assemblers.get(uuid)?.reset();
858
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
859
+ },
860
+ logger: this.Log,
861
+ logPrefix: 'ProtocolV2 BLE',
862
+ createTimeoutError: (messageName: string, timeout: number) =>
863
+ ERRORS.TypedError(
864
+ HardwareErrorCode.BleTimeoutError,
865
+ `BLE response timeout after ${timeout}ms for ${messageName}`
866
+ ),
867
+ };
829
868
  }
830
869
 
831
870
  private processProtocolV1Notification(deviceId: string, hexData: string): PacketProcessResult {
@@ -0,0 +1 @@
1
+ export { createTransportCallLog, shouldSuppressHighVolumeCallLog } from '@onekeyfe/hd-transport';