@onekeyfe/hd-transport-web-device 1.2.0-alpha.3 → 1.2.0-alpha.30

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.
@@ -0,0 +1,7 @@
1
+ import type { ProtocolType } from '@onekeyfe/hd-transport';
2
+ export declare function shouldSuppressHighVolumeCallLog(name: string): boolean;
3
+ export declare function createTransportCallLog(name: string, protocol: ProtocolType): {
4
+ name: string;
5
+ protocol: ProtocolType;
6
+ };
7
+ //# sourceMappingURL=transportLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transportLog.d.ts","sourceRoot":"","sources":["../src/transportLog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAI3D,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,WAE3D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY;;;EAE1E"}
package/dist/webusb.d.ts CHANGED
@@ -1,19 +1,17 @@
1
1
  /// <reference types="w3c-web-usb" />
2
- import transport from '@onekeyfe/hd-transport';
3
- import type { AcquireInput, OneKeyDeviceInfoBase, ProtocolType, TransportCallOptions } from '@onekeyfe/hd-transport';
2
+ import transport, { ProtocolV2UsbTransportBase } from '@onekeyfe/hd-transport';
3
+ import type { AcquireInput, OneKeyDeviceInfoBase, ProtocolType, ProtocolV2CallContext, ProtocolV2Schemas, TransportCallOptions } from '@onekeyfe/hd-transport';
4
4
  export interface DeviceInfo extends OneKeyDeviceInfoBase {
5
5
  path: string;
6
6
  device: USBDevice;
7
7
  protocolType?: ProtocolType;
8
8
  }
9
- export default class WebUsbTransport {
9
+ export default class WebUsbTransport extends ProtocolV2UsbTransportBase<string> {
10
10
  messages: ReturnType<typeof transport.parseConfigure> | undefined;
11
11
  messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
12
+ private protocolV2SchemaSource;
12
13
  private deviceProtocol;
13
14
  private deviceProtocolHints;
14
- private protocolV2Assemblers;
15
- private protocolV2Sessions;
16
- private protocolV2ReadTimeouts;
17
15
  private deviceEndpoints;
18
16
  private mockSerialPaths;
19
17
  private mockSerialCounter;
@@ -26,6 +24,7 @@ export default class WebUsbTransport {
26
24
  configurationId: number;
27
25
  endpointId: number;
28
26
  interfaceId: number;
27
+ constructor();
29
28
  init(logger: any): void;
30
29
  configure(signedData: any): void;
31
30
  configureProtocolV2(signedData: any): void;
@@ -35,6 +34,7 @@ export default class WebUsbTransport {
35
34
  getConnectedDevices(): Promise<DeviceInfo[]>;
36
35
  acquire(input: AcquireInput): Promise<string | undefined>;
37
36
  private createProtocolMismatchError;
37
+ private createProtocolProbeTimeoutError;
38
38
  private createProtocolDetectionError;
39
39
  private detectProtocol;
40
40
  findDevice(path: string): Promise<USBDevice>;
@@ -50,7 +50,9 @@ export default class WebUsbTransport {
50
50
  private getTransferInData;
51
51
  private toArrayBuffer;
52
52
  private transferOutWithRetry;
53
+ private transferOutOnce;
53
54
  private transferInWithRetry;
55
+ private transferInOnce;
54
56
  private resetConnectionAfterProbe;
55
57
  private withProtocolReadTimeout;
56
58
  private probeProtocolV1;
@@ -58,9 +60,15 @@ export default class WebUsbTransport {
58
60
  call(path: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<import("@onekeyfe/hd-transport").MessageFromOneKey>;
59
61
  private callProtocolV1;
60
62
  private callProtocolV2;
61
- private receiveProtocolV2Frame;
62
63
  receiveData(path: string, timeoutMs?: number): Promise<string>;
63
64
  release(path: string): Promise<void>;
65
+ protected getProtocolV2UsbSchemas(): ProtocolV2Schemas;
66
+ protected getProtocolV2UsbLogger(): any;
67
+ protected writeProtocolV2UsbPacket(path: string, frame: Uint8Array, _context: ProtocolV2CallContext): Promise<void>;
68
+ protected readProtocolV2UsbPacket(path: string, _context: ProtocolV2CallContext): Promise<Uint8Array>;
69
+ protected resetProtocolV2UsbNativeLink(path: string, _reason: string): Promise<void>;
70
+ protected onProtocolV2UsbLinkInvalidated(path: string, reason: string): void;
71
+ protected createProtocolV2UsbTimeoutError(name: string, timeoutMs: number): Error;
64
72
  getProtocolType(path: string): ProtocolType | undefined;
65
73
  }
66
74
  //# sourceMappingURL=webusb.d.ts.map
@@ -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":"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"}
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ preset: '../../jest.config.js',
3
+ testEnvironment: 'node',
4
+ modulePathIgnorePatterns: ['node_modules', '<rootDir>/dist'],
5
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-web-device",
3
- "version": "1.2.0-alpha.3",
3
+ "version": "1.2.0-alpha.30",
4
4
  "author": "OneKey",
5
5
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
6
6
  "license": "MIT",
@@ -16,17 +16,18 @@
16
16
  "scripts": {
17
17
  "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
18
18
  "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
19
+ "test": "jest",
19
20
  "lint": "eslint .",
20
21
  "lint:fix": "eslint . --fix"
21
22
  },
22
23
  "dependencies": {
23
- "@onekeyfe/hd-shared": "1.2.0-alpha.3",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.3"
24
+ "@onekeyfe/hd-shared": "1.2.0-alpha.30",
25
+ "@onekeyfe/hd-transport": "1.2.0-alpha.30"
25
26
  },
26
27
  "devDependencies": {
27
- "@onekeyfe/hd-transport-electron": "1.2.0-alpha.3",
28
+ "@onekeyfe/hd-transport-electron": "1.2.0-alpha.30",
28
29
  "@types/w3c-web-usb": "^1.0.6",
29
30
  "@types/web-bluetooth": "^0.0.17"
30
31
  },
31
- "gitHead": "54a4a40baee7e376b9a810dade60007d2c9b00c9"
32
+ "gitHead": "07405d2e3dcdd0ba351ae98b7cf4f61690ef2913"
32
33
  }
@@ -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 {
@@ -72,9 +69,6 @@ const toBleDescriptor = (
72
69
 
73
70
  const BLE_PACKET_SIZE = 192;
74
71
  const BLE_WRITE_DELAY_MS = 5;
75
- const BLE_WRITE_MAX_RETRIES = 3;
76
- const BLE_WRITE_RETRY_DELAY_MS = 300;
77
- const BLE_RESPONSE_TIMEOUT_MS = 30_000;
78
72
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
79
73
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
80
74
 
@@ -89,6 +83,8 @@ export default class ElectronBleTransport {
89
83
 
90
84
  private _messagesV2: ReturnType<typeof transport.parseConfigure> | undefined;
91
85
 
86
+ private protocolV2SchemaConfiguration: string | undefined;
87
+
92
88
  name = 'ElectronBleTransport';
93
89
 
94
90
  configured = false;
@@ -113,9 +109,26 @@ export default class ElectronBleTransport {
113
109
 
114
110
  private v2FramePromises: Map<string, Deferred<Uint8Array>> = new Map();
115
111
 
116
- private activeProtocolV2Call: { uuid: string; token: number } | null = null;
117
-
118
- private nextProtocolV2CallToken = 1;
112
+ private protocolV2Links = new ProtocolV2LinkManager<string>({
113
+ getSchemas: () => {
114
+ if (!this._messages || !this._messagesV2) {
115
+ throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
116
+ }
117
+ return {
118
+ protocolV1: this._messages,
119
+ protocolV2: this._messagesV2,
120
+ };
121
+ },
122
+ classifyError: () => 'link-fatal',
123
+ onLinkInvalidated: async (uuid, reason) => {
124
+ this.v2Assemblers.get(uuid)?.reset();
125
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
126
+ this.Log?.debug('[Electron BLE] Protocol V2 link invalidated:', uuid, reason);
127
+ if (reason.startsWith('Protocol V2 link-fatal error:')) {
128
+ await this.release(uuid);
129
+ }
130
+ },
131
+ });
119
132
 
120
133
  private notificationCleanups: Map<string, () => void> = new Map();
121
134
 
@@ -157,6 +170,9 @@ export default class ElectronBleTransport {
157
170
  }
158
171
 
159
172
  private cleanupDeviceState(deviceId: string): void {
173
+ this.protocolV2Links
174
+ .invalidateLink(deviceId, 'Electron BLE device state cleaned')
175
+ .catch(error => this.Log?.debug('[Electron BLE] link cleanup failed:', error));
160
176
  this.connectedDevices.delete(deviceId);
161
177
  this.deviceProtocol.delete(deviceId);
162
178
  // Keep deviceProtocolHints — it's inferred from device name (e.g. "Pro 2" → V2)
@@ -164,9 +180,6 @@ export default class ElectronBleTransport {
164
180
  this.v1Buffers.delete(deviceId);
165
181
  this.v2Assemblers.delete(deviceId);
166
182
  this.resetProtocolV2Frames(deviceId);
167
- if (this.activeProtocolV2Call?.uuid === deviceId) {
168
- this.activeProtocolV2Call = null;
169
- }
170
183
  this.notificationTokens.delete(deviceId);
171
184
 
172
185
  const notifyCleanup = this.notificationCleanups.get(deviceId);
@@ -202,8 +215,19 @@ export default class ElectronBleTransport {
202
215
  }
203
216
 
204
217
  configureProtocolV2(signedData: any) {
218
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
219
+ if (this.protocolV2SchemaConfiguration === configuration) {
220
+ return;
221
+ }
222
+
223
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
205
224
  this._messagesV2 = parseConfigure(signedData);
206
- this.Log?.debug('[Electron BLE] Protocol V2 schema configured');
225
+ this.protocolV2SchemaConfiguration = configuration;
226
+ if (isReconfiguration) {
227
+ this.protocolV2Links
228
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
229
+ .catch(error => this.Log?.debug('[Electron BLE] schema link cleanup failed:', error));
230
+ }
207
231
  }
208
232
 
209
233
  async listen() {
@@ -238,12 +262,15 @@ export default class ElectronBleTransport {
238
262
  throw ERRORS.TypedError(HardwareErrorCode.BleRequiredUUID);
239
263
  }
240
264
 
265
+ if (this.connectedDevices.has(uuid)) {
266
+ await this.release(uuid);
267
+ }
268
+
241
269
  if (forceCleanRunPromise && this.runPromise) {
242
270
  const error = ERRORS.TypedError(HardwareErrorCode.BleForceCleanRunPromise);
243
271
  this.runPromise.reject(error);
244
272
  this.rejectAllProtocolV2Frames(error);
245
273
  this.runPromise = null;
246
- this.activeProtocolV2Call = null;
247
274
  }
248
275
 
249
276
  try {
@@ -270,7 +297,7 @@ export default class ElectronBleTransport {
270
297
  }
271
298
 
272
299
  this.v1Buffers.set(uuid, { buffer: [], bufferLength: 0 });
273
- this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler());
300
+ this.v2Assemblers.set(uuid, new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
274
301
 
275
302
  await window.desktopApi.nobleBle.subscribe(uuid);
276
303
 
@@ -281,7 +308,7 @@ export default class ElectronBleTransport {
281
308
  (disconnectedDevice: any) => {
282
309
  if (disconnectedDevice.id === uuid) {
283
310
  this.cleanupDeviceState(uuid);
284
- this.emitter?.emit('device-disconnect', {
311
+ this.emitter?.emit(TRANSPORT_EVENT.DEVICE_DISCONNECT, {
285
312
  name: disconnectedDevice.name,
286
313
  id: disconnectedDevice.id,
287
314
  connectId: disconnectedDevice.id,
@@ -293,12 +320,6 @@ export default class ElectronBleTransport {
293
320
 
294
321
  const protocolType = await this.detectProtocol(uuid, expectedProtocol, protocolHint);
295
322
 
296
- this.emitter?.emit('device-connect', {
297
- name: device.name,
298
- id: device.id,
299
- connectId: device.id,
300
- });
301
-
302
323
  return {
303
324
  ...toBleDescriptor({ id: device.id, name: device.name }, protocolType),
304
325
  uuid,
@@ -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);
@@ -368,23 +390,23 @@ export default class ElectronBleTransport {
368
390
  }
369
391
 
370
392
  if (expectedProtocol === 'V2') {
371
- // 免探测路径:调用方显式承诺该设备是 V2(例如固件升级重启后的重连场景,
372
- // 上层已经探测过协议并通过 expectedProtocol 传回),这里不再重复探测。
373
- this.deviceProtocol.set(uuid, 'V2');
374
- this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
375
- return 'V2';
393
+ if (await this.probeProtocolV2(uuid)) {
394
+ this.deviceProtocol.set(uuid, 'V2');
395
+ this.Log?.debug(`[Electron BLE] detectProtocol: uuid=${uuid} -> V2 (expected)`);
396
+ return 'V2';
397
+ }
398
+ throw this.createProtocolMismatchError(expectedProtocol);
376
399
  }
377
400
 
378
- // 项目约束:协议判断必须在连接后主动探测,不能依赖设备名/PID/descriptor。
379
- // 设备名 hint(如 "Pro 2")只用于调整探测顺序:hint=V2 时先探 V2、失败回落 V1,
380
- // 不能作为最终结论。
401
+ // Protocol must be actively probed after connection. Name, PID, and descriptors only
402
+ // influence probe order; a V2 hint probes V2 first and falls back to V1.
381
403
  const probeOrder: ProtocolType[] =
382
404
  protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
383
405
 
384
406
  for (let i = 0; i < probeOrder.length; i += 1) {
385
407
  const protocol = probeOrder[i];
386
408
  if (i > 0) {
387
- // 上一个协议探测失败后,重置订阅与缓冲,避免残留数据干扰下一个协议的探测。
409
+ // Reset subscriptions and buffers after a failed probe before trying another protocol.
388
410
  await this.resetProbeStateAfterProtocolProbe(uuid, probeOrder[i - 1]);
389
411
  }
390
412
  const detected =
@@ -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);
@@ -491,63 +514,42 @@ export default class ElectronBleTransport {
491
514
  return detected;
492
515
  }
493
516
 
494
- private async writeWithChunking(uuid: string, hexData: string): Promise<void> {
495
- const totalBytes = hexData.length / 2;
496
-
497
- if (totalBytes <= BLE_PACKET_SIZE) {
498
- await wait(BLE_WRITE_DELAY_MS);
499
- await this.writeWithRetry(uuid, hexData);
500
- return;
501
- }
502
-
503
- for (let offset = 0; offset < hexData.length; ) {
504
- const chunkHexLen = Math.min(BLE_PACKET_SIZE * 2, hexData.length - offset);
505
- const chunkHex = hexData.substring(offset, offset + chunkHexLen);
506
- offset += chunkHexLen;
507
-
508
- await this.writeWithRetry(uuid, chunkHex);
509
-
510
- if (offset < hexData.length) {
511
- await wait(BLE_WRITE_DELAY_MS);
512
- }
513
- }
514
- }
515
-
516
- private async writeWithRetry(uuid: string, hexData: string): Promise<void> {
517
- let lastError: any;
517
+ private async writeOnce(uuid: string, hexData: string): Promise<void> {
518
518
  const nobleBle = window.desktopApi?.nobleBle;
519
519
  if (!nobleBle) {
520
520
  throw new Error('Noble BLE API not available');
521
521
  }
522
522
 
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
- );
523
+ await nobleBle.write(uuid, hexData);
524
+ }
525
+
526
+ private writeProtocolV2Frame(
527
+ uuid: string,
528
+ frame: Uint8Array,
529
+ context: ProtocolV2CallContext,
530
+ assertCurrentGeneration: () => void
531
+ ) {
532
+ return writeProtocolV2BleFrame({
533
+ frame,
534
+ packetCapacity: BLE_PACKET_SIZE,
535
+ assertActive: assertCurrentGeneration,
536
+ signal: context.signal,
537
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
538
+ initialDelayMs: frame.length <= BLE_PACKET_SIZE ? BLE_WRITE_DELAY_MS : 0,
539
+ burstSize: 1,
540
+ burstPauseMs: BLE_WRITE_DELAY_MS,
541
+ writePacket: packet => this.writeOnce(uuid, bytesToHex(packet)),
542
+ });
542
543
  }
543
544
 
544
545
  private handleNotification(deviceId: string, hexData: string): void {
545
546
  if (hexData === 'PAIRING_REJECTED') {
546
547
  this.Log?.debug('[Electron BLE] Pairing rejection detected for device:', deviceId);
547
- if (this.runPromise) {
548
- const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
548
+ const error = ERRORS.TypedError(HardwareErrorCode.BleDeviceBondedCanceled);
549
+ if (this.deviceProtocol.get(deviceId) === 'V2') {
550
+ this.rejectProtocolV2Frames(deviceId, error);
551
+ } else if (this.runPromise) {
549
552
  this.runPromise.reject(error);
550
- this.rejectAllProtocolV2Frames(error);
551
553
  }
552
554
  return;
553
555
  }
@@ -566,12 +568,6 @@ export default class ElectronBleTransport {
566
568
 
567
569
  private handleProtocolV2Notification(deviceId: string, hexData: string): void {
568
570
  try {
569
- if (!this.runPromise || this.activeProtocolV2Call?.uuid !== deviceId) {
570
- this.v2Assemblers.get(deviceId)?.reset();
571
- this.resetProtocolV2Frames(deviceId);
572
- return;
573
- }
574
-
575
571
  const bytes = hexToBytes(hexData);
576
572
  if (bytes.length === 0) return;
577
573
 
@@ -583,11 +579,8 @@ export default class ElectronBleTransport {
583
579
  }
584
580
  } catch (error) {
585
581
  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
- }
582
+ const notifyError = ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError);
583
+ this.rejectProtocolV2Frames(deviceId, notifyError);
591
584
  }
592
585
  }
593
586
 
@@ -623,8 +616,13 @@ export default class ElectronBleTransport {
623
616
  this.v2FramePromises.delete(uuid);
624
617
  }
625
618
 
626
- private isActiveProtocolV2Call(uuid: string, token: number) {
627
- return this.activeProtocolV2Call?.uuid === uuid && this.activeProtocolV2Call.token === token;
619
+ private rejectProtocolV2Frames(uuid: string, error: Error) {
620
+ this.v2FrameQueues.delete(uuid);
621
+ const framePromise = this.v2FramePromises.get(uuid);
622
+ if (framePromise) {
623
+ this.v2FramePromises.delete(uuid);
624
+ framePromise.reject(error);
625
+ }
628
626
  }
629
627
 
630
628
  private async readProtocolV2Frame(uuid: string) {
@@ -681,12 +679,8 @@ export default class ElectronBleTransport {
681
679
  `Device protocol has not been detected for ${uuid}`
682
680
  );
683
681
  }
684
- if (shouldSuppressHighVolumeCallLog(name)) {
685
- // 高频文件写入不要逐包发 debug 事件,否则调试日志会反向拖慢传输。
686
- } else if (isLogBlockCommand(name)) {
687
- this.Log?.debug('[Electron BLE] call', 'name:', name, 'protocol:', protocol);
688
- } else {
689
- this.Log?.debug('[Electron BLE] call', 'name:', name, 'data:', data, 'protocol:', protocol);
682
+ if (!shouldSuppressHighVolumeCallLog(name)) {
683
+ this.Log?.debug('transport call', createTransportCallLog(name, protocol));
690
684
  }
691
685
 
692
686
  if (protocol === 'V2') {
@@ -776,77 +770,61 @@ export default class ElectronBleTransport {
776
770
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
777
771
  }
778
772
 
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;
773
+ const callOptions = options;
774
+
775
+ try {
776
+ return await this.protocolV2Links.call(
777
+ uuid,
778
+ () => this.createProtocolV2Adapter(uuid),
779
+ name,
780
+ data,
781
+ callOptions
782
+ );
783
+ } catch (e) {
784
+ this.Log?.error('[Electron BLE] Protocol V2 call error:', e);
785
+ throw e;
789
786
  }
787
+ }
790
788
 
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
- const callOptions = {
800
- ...options,
801
- timeoutMs: options?.timeoutMs ?? BLE_RESPONSE_TIMEOUT_MS,
789
+ private createProtocolV2Adapter(uuid: string) {
790
+ const generation = this.notificationTokens.get(uuid) ?? 0;
791
+ const assertCurrentGeneration = () => {
792
+ if (this.notificationTokens.get(uuid) !== generation) {
793
+ throw new Error(`Protocol V2 notification generation changed for ${uuid}`);
794
+ }
802
795
  };
803
796
 
804
- 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;
831
- } catch (e) {
832
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
797
+ return {
798
+ router: PROTOCOL_V2_CHANNEL_BLE_UART,
799
+ maxFrameBytes: PROTOCOL_V2_BLE_FRAME_MAX_BYTES,
800
+ generation,
801
+ prepareCall: () => {
802
+ assertCurrentGeneration();
833
803
  this.v2Assemblers.get(uuid)?.reset();
834
804
  this.resetProtocolV2Frames(uuid);
835
- }
836
- this.Log?.error('[Electron BLE] Protocol V2 call error:', e);
837
- throw e;
838
- } finally {
839
- if (this.isActiveProtocolV2Call(uuid, callToken)) {
840
- if (!completed) {
841
- this.v2Assemblers.get(uuid)?.reset();
805
+ },
806
+ writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
807
+ this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
808
+ readFrame: async () => {
809
+ assertCurrentGeneration();
810
+ const rxFrame = await this.readProtocolV2Frame(uuid);
811
+ if (!(rxFrame instanceof Uint8Array)) {
812
+ throw new Error('Response is not Uint8Array');
842
813
  }
843
- this.resetProtocolV2Frames(uuid);
844
- this.activeProtocolV2Call = null;
845
- }
846
- if (this.runPromise === runPromise) {
847
- this.runPromise = null;
848
- }
849
- }
814
+ return rxFrame;
815
+ },
816
+ reset: (reason: string) => {
817
+ this.v2Assemblers.get(uuid)?.reset();
818
+ this.rejectProtocolV2Frames(uuid, new Error(reason));
819
+ },
820
+ logger: this.Log,
821
+ logPrefix: 'ProtocolV2 BLE',
822
+ createTimeoutError: (messageName: string, timeout: number) =>
823
+ ERRORS.TypedError(
824
+ HardwareErrorCode.BleTimeoutError,
825
+ `BLE response timeout after ${timeout}ms for ${messageName}`
826
+ ),
827
+ };
850
828
  }
851
829
 
852
830
  private processProtocolV1Notification(deviceId: string, hexData: string): PacketProcessResult {
@@ -0,0 +1,11 @@
1
+ import type { ProtocolType } from '@onekeyfe/hd-transport';
2
+
3
+ const HIGH_VOLUME_CALLS = new Set(['FileWrite', 'FilesystemFileWrite', 'EmmcFileWrite']);
4
+
5
+ export function shouldSuppressHighVolumeCallLog(name: string) {
6
+ return HIGH_VOLUME_CALLS.has(name);
7
+ }
8
+
9
+ export function createTransportCallLog(name: string, protocol: ProtocolType) {
10
+ return { name, protocol };
11
+ }