@onekeyfe/hd-transport-web-device 1.2.0-alpha.7 → 1.2.0-alpha.70

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,24 @@ 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);
316
+ const connectionToken = this.notificationTokens.get(uuid);
317
+
318
+ const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
277
319
 
278
320
  const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected(
279
321
  (disconnectedDevice: any) => {
280
- if (disconnectedDevice.id === uuid) {
322
+ if (
323
+ disconnectedDevice.id === uuid &&
324
+ this.notificationTokens.get(uuid) === connectionToken
325
+ ) {
281
326
  this.cleanupDeviceState(uuid);
282
- this.emitter?.emit('device-disconnect', {
327
+ this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
283
328
  name: disconnectedDevice.name,
284
329
  id: disconnectedDevice.id,
285
330
  connectId: disconnectedDevice.id,
@@ -289,14 +334,6 @@ export default class ElectronBleTransport {
289
334
  );
290
335
  this.disconnectCleanups.set(uuid, disconnectCleanup);
291
336
 
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
337
  return {
301
338
  ...toBleDescriptor({ id: device.id, name: device.name }, protocolType),
302
339
  uuid,
@@ -317,6 +354,16 @@ export default class ElectronBleTransport {
317
354
  }
318
355
 
319
356
  async release(id: string) {
357
+ try {
358
+ await this.protocolV2Links.invalidateLink(id, 'Electron BLE transport released');
359
+ await this.releaseNative(id);
360
+ } catch (error) {
361
+ this.Log?.error('[Electron BLE] release failed:', error);
362
+ this.cleanupDeviceState(id);
363
+ }
364
+ }
365
+
366
+ private async releaseNative(id: string) {
320
367
  try {
321
368
  if (this.connectedDevices.has(id)) {
322
369
  if (window.desktopApi?.nobleBle) {
@@ -341,7 +388,7 @@ export default class ElectronBleTransport {
341
388
  private createProtocolDetectionError() {
342
389
  return ERRORS.TypedError(
343
390
  HardwareErrorCode.BleTimeoutError,
344
- 'Unable to detect BLE protocol: device did not respond to Protocol V1 Initialize or Protocol V2 Ping'
391
+ 'Unable to detect BLE protocol: device did not respond to Protocol V1 GetFeatures or Protocol V2 Ping'
345
392
  );
346
393
  }
347
394
 
@@ -366,23 +413,23 @@ export default class ElectronBleTransport {
366
413
  }
367
414
 
368
415
  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';
416
+ if (await this.probeProtocolV2(uuid)) {
417
+ this.deviceProtocol.set(uuid, 'V2');
418
+ this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
419
+ return 'V2';
420
+ }
421
+ throw this.createProtocolMismatchError(expectedProtocol);
374
422
  }
375
423
 
376
- // 项目约束:协议判断必须在连接后主动探测,不能依赖设备名/PID/descriptor。
377
- // 设备名 hint(如 "Pro 2")只用于调整探测顺序:hint=V2 时先探 V2、失败回落 V1,
378
- // 不能作为最终结论。
424
+ // Protocol must be actively probed after connection. Name, PID, and descriptors only
425
+ // influence probe order; a V2 hint probes V2 first and falls back to V1.
379
426
  const probeOrder: ProtocolType[] =
380
427
  protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
381
428
 
382
429
  for (let i = 0; i < probeOrder.length; i += 1) {
383
430
  const protocol = probeOrder[i];
384
431
  if (i > 0) {
385
- // 上一个协议探测失败后,重置订阅与缓冲,避免残留数据干扰下一个协议的探测。
432
+ // Reset subscriptions and buffers after a failed probe before trying another protocol.
386
433
  await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
387
434
  }
388
435
  const detected =
@@ -415,16 +462,18 @@ export default class ElectronBleTransport {
415
462
  }
416
463
 
417
464
  private async resetProbeStateAfterProtocolProbe(uuid: string, protocol: ProtocolType) {
465
+ await this.protocolV2Links.invalidateLink(
466
+ uuid,
467
+ `Reset notify state after Protocol ${protocol} probe`
468
+ );
418
469
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
419
470
  this.v2Assemblers.get(uuid)?.reset();
420
471
  this.resetProtocolV2Frames(uuid);
421
- if (this.activeProtocolV2Call?.uuid === uuid) {
422
- this.activeProtocolV2Call = null;
423
- }
424
- if (this.runPromise) {
472
+ if (this.runPromise && this.runPromiseDeviceId === uuid) {
425
473
  const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
426
474
  this.runPromise.reject(error);
427
475
  this.runPromise = null;
476
+ this.runPromiseDeviceId = null;
428
477
  }
429
478
 
430
479
  const notifyCleanup = this.notificationCleanups.get(uuid);
@@ -440,9 +489,17 @@ export default class ElectronBleTransport {
440
489
  this.Log?.debug(`[Electron BLE] unsubscribe after Protocol ${protocol} probe failed:`, error);
441
490
  }
442
491
  try {
492
+ await window.desktopApi?.nobleBle?.disconnect(uuid);
493
+ } catch (error) {
494
+ this.Log?.debug(`[Electron BLE] disconnect after Protocol ${protocol} probe failed:`, error);
495
+ }
496
+ this.connectedDevices.delete(uuid);
497
+ try {
498
+ await window.desktopApi?.nobleBle?.connect(uuid);
499
+ this.connectedDevices.add(uuid);
443
500
  await window.desktopApi?.nobleBle?.subscribe(uuid);
444
501
  } catch (error) {
445
- this.Log?.debug(`[Electron BLE] resubscribe after Protocol ${protocol} probe failed:`, error);
502
+ this.Log?.debug(`[Electron BLE] reconnect after Protocol ${protocol} probe failed:`, error);
446
503
  throw error;
447
504
  }
448
505
 
@@ -457,11 +514,13 @@ export default class ElectronBleTransport {
457
514
 
458
515
  try {
459
516
  this.deviceProtocol.set(uuid, 'V1');
460
- await this.callProtocolV1(uuid, 'Initialize', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
517
+ // GetFeatures identifies Protocol V1 without resetting an existing wallet
518
+ // session before Core has a chance to restore a hidden wallet.
519
+ await this.callProtocolV1(uuid, 'GetFeatures', {}, { timeoutMs: PROTOCOL_PROBE_TIMEOUT_MS });
461
520
  return true;
462
521
  } catch (error) {
463
522
  this.clearProbeProtocol(uuid, 'V1');
464
- this.Log?.debug('[Electron BLE] Protocol V1 Initialize probe failed:', error);
523
+ this.Log?.debug('[Electron BLE] Protocol V1 GetFeatures probe failed:', error);
465
524
  return false;
466
525
  }
467
526
  }
@@ -489,28 +548,6 @@ export default class ElectronBleTransport {
489
548
  return detected;
490
549
  }
491
550
 
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
551
  private async writeOnce(uuid: string, hexData: string): Promise<void> {
515
552
  const nobleBle = window.desktopApi?.nobleBle;
516
553
  if (!nobleBle) {
@@ -520,13 +557,33 @@ export default class ElectronBleTransport {
520
557
  await nobleBle.write(uuid, hexData);
521
558
  }
522
559
 
560
+ private writeProtocolV2Frame(
561
+ uuid: string,
562
+ frame: Uint8Array,
563
+ context: ProtocolV2CallContext,
564
+ assertCurrentGeneration: () => void
565
+ ) {
566
+ return writeProtocolV2BleFrame({
567
+ frame,
568
+ packetCapacity: BLE_PACKET_SIZE,
569
+ assertActive: assertCurrentGeneration,
570
+ signal: context.signal,
571
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
572
+ initialDelayMs: frame.length <= BLE_PACKET_SIZE ? BLE_WRITE_DELAY_MS : 0,
573
+ burstSize: 1,
574
+ burstPauseMs: BLE_WRITE_DELAY_MS,
575
+ writePacket: packet => this.writeOnce(uuid, bytesToHex(packet)),
576
+ });
577
+ }
578
+
523
579
  private handleNotification(deviceId: string, hexData: string): void {
524
580
  if (hexData === 'PAIRING_REJECTED') {
525
581
  this.Log?.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
526
- if (this.runPromise) {
527
- const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
582
+ const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
583
+ if (this.deviceProtocol.get(deviceId) === 'V2') {
584
+ this.rejectProtocolV2Frames(deviceId, error);
585
+ } else if (this.runPromise && this.runPromiseDeviceId === deviceId) {
528
586
  this.runPromise.reject(error);
529
- this.rejectAllProtocolV2Frames(error);
530
587
  }
531
588
  return;
532
589
  }
@@ -545,12 +602,6 @@ export default class ElectronBleTransport {
545
602
 
546
603
  private handleProtocolV2Notification(deviceId: string, hexData: string): void {
547
604
  try {
548
- if (!this.runPromise || this.activeProtocolV2Call?.uuid !== deviceId) {
549
- this.v2Assemblers.get(deviceId)?.reset();
550
- this.resetProtocolV2Frames(deviceId);
551
- return;
552
- }
553
-
554
605
  const bytes = hexToBytes(hexData);
555
606
  if (bytes.length === 0) return;
556
607
 
@@ -562,11 +613,8 @@ export default class ElectronBleTransport {
562
613
  }
563
614
  } catch (error) {
564
615
  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
- }
616
+ const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
617
+ this.rejectProtocolV2Frames(deviceId, notifyError);
570
618
  }
571
619
  }
572
620
 
@@ -589,21 +637,17 @@ export default class ElectronBleTransport {
589
637
  this.getProtocolV2FrameQueue(uuid).push(frame);
590
638
  }
591
639
 
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
640
  private resetProtocolV2Frames(uuid: string) {
601
- this.v2FrameQueues.delete(uuid);
602
- this.v2FramePromises.delete(uuid);
641
+ this.rejectProtocolV2Frames(uuid, new Error(`Protocol V2 frame state reset for ${uuid}`));
603
642
  }
604
643
 
605
- private isActiveProtocolV2Call(uuid: string, token: number) {
606
- return this.activeProtocolV2Call?.uuid === uuid && this.activeProtocolV2Call.token === token;
644
+ private rejectProtocolV2Frames(uuid: string, error: Error) {
645
+ this.v2FrameQueues.delete(uuid);
646
+ const framePromise = this.v2FramePromises.get(uuid);
647
+ if (framePromise) {
648
+ this.v2FramePromises.delete(uuid);
649
+ framePromise.reject(error);
650
+ }
607
651
  }
608
652
 
609
653
  private async readProtocolV2Frame(uuid: string) {
@@ -628,13 +672,18 @@ export default class ElectronBleTransport {
628
672
 
629
673
  if (result.error) {
630
674
  this.Log?.error('[Electron BLE] Protocol V1 packet processing error:', result.error);
631
- if (this.runPromise) {
675
+ if (this.runPromise && this.runPromiseDeviceId === deviceId) {
632
676
  this.runPromise.reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError));
633
677
  }
634
678
  return;
635
679
  }
636
680
 
637
- if (result.isComplete && result.completePacket && this.runPromise) {
681
+ if (
682
+ result.isComplete &&
683
+ result.completePacket &&
684
+ this.runPromise &&
685
+ this.runPromiseDeviceId === deviceId
686
+ ) {
638
687
  this.runPromise.resolve(result.completePacket);
639
688
  }
640
689
  }
@@ -660,12 +709,8 @@ export default class ElectronBleTransport {
660
709
  `Device protocol has not been detected for ${uuid}`
661
710
  );
662
711
  }
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);
712
+ if (!shouldSuppressHighVolumeCallLog(name)) {
713
+ this.Log?.debug('transport call', createTransportCallLog(name, protocol, data));
669
714
  }
670
715
 
671
716
  if (protocol === 'V2') {
@@ -692,6 +737,7 @@ export default class ElectronBleTransport {
692
737
  const runPromise = createDeferred<Uint8Array | string>();
693
738
  runPromise.promise.catch(() => undefined);
694
739
  this.runPromise = runPromise;
740
+ this.runPromiseDeviceId = uuid;
695
741
  const messages = this._messages;
696
742
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
697
743
  let timeout: ReturnType<typeof setTimeout> | undefined;
@@ -736,11 +782,24 @@ export default class ElectronBleTransport {
736
782
  return check.call(jsonData);
737
783
  } catch (e) {
738
784
  this.Log?.error('[Electron BLE] Protocol V1 call error:', e);
785
+ const isProbeTimeout =
786
+ name === 'GetFeatures' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS;
787
+ if ((e as { errorCode?: unknown })?.errorCode === HardwareErrorCode.BleTimeoutError) {
788
+ this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
789
+ const notifyCleanup = this.notificationCleanups.get(uuid);
790
+ notifyCleanup?.();
791
+ this.notificationCleanups.delete(uuid);
792
+ this.notificationTokens.delete(uuid);
793
+ if (!isProbeTimeout) {
794
+ await this.releaseNative(uuid);
795
+ }
796
+ }
739
797
  throw e;
740
798
  } finally {
741
799
  if (timeout) clearTimeout(timeout);
742
800
  if (this.runPromise === runPromise) {
743
801
  this.runPromise = null;
802
+ this.runPromiseDeviceId = null;
744
803
  }
745
804
  }
746
805
  }
@@ -755,77 +814,61 @@ export default class ElectronBleTransport {
755
814
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
756
815
  }
757
816
 
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;
817
+ const callOptions = options;
818
+
819
+ try {
820
+ return await this.protocolV2Links.call(
821
+ uuid,
822
+ () => this.createProtocolV2Adapter(uuid),
823
+ name,
824
+ data,
825
+ callOptions
826
+ );
827
+ } catch (e) {
828
+ this.Log?.error('[Electron BLE] Protocol V2 call error:', e);
829
+ throw e;
768
830
  }
831
+ }
769
832
 
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,
833
+ private createProtocolV2Adapter(uuid: string) {
834
+ const generation = this.notificationTokens.get(uuid) ?? 0;
835
+ const assertCurrentGeneration = () => {
836
+ if (this.notificationTokens.get(uuid) !== generation) {
837
+ throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
838
+ }
781
839
  };
782
840
 
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)) {
841
+ return {
842
+ router: PROTOCOL_V2_CHANNEL_BLE_UART,
843
+ maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
844
+ generation,
845
+ prepareCall: () => {
846
+ assertCurrentGeneration();
812
847
  this.v2Assemblers.get(uuid)?.reset();
813
848
  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();
849
+ },
850
+ writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
851
+ this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
852
+ readFrame: async () => {
853
+ assertCurrentGeneration();
854
+ const rxFrame = await this.readProtocolV2Frame(uuid);
855
+ if (!(rxFrame instanceof Uint8Array)) {
856
+ throw new Error('Response is not Uint8Array');
821
857
  }
822
- this.resetProtocolV2Frames(uuid);
823
- this.activeProtocolV2Call = null;
824
- }
825
- if (this.runPromise === runPromise) {
826
- this.runPromise = null;
827
- }
828
- }
858
+ return rxFrame;
859
+ },
860
+ reset: (reason: string) => {
861
+ this.v2Assemblers.get(uuid)?.reset();
862
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
863
+ },
864
+ logger: this.Log,
865
+ logPrefix: 'ProtocolV2 BLE',
866
+ createTimeoutError: (messageName: string, timeout: number) =>
867
+ ERRORS.TypedError(
868
+ HardwareErrorCode.BleTimeoutError,
869
+ `BLE response timeout after ${timeout}ms for ${messageName}`
870
+ ),
871
+ };
829
872
  }
830
873
 
831
874
  private processProtocolV1Notification(deviceId: string, hexData: string): PacketProcessResult {
@@ -0,0 +1 @@
1
+ export { createTransportCallLog, shouldSuppressHighVolumeCallLog } from '@onekeyfe/hd-transport';