@onekeyfe/hd-transport-lowlevel 1.2.0-alpha.15 → 1.2.0-alpha.151

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,42 @@
1
+ /* eslint-disable @typescript-eslint/no-var-requires */
2
+ const { getProtocolV1SendOptions, shouldLogFirmwareUploadProgress } = require('../src');
3
+
4
+ describe('firmware upload progress logging', () => {
5
+ test('按 5% 进度限流打印', () => {
6
+ expect(
7
+ shouldLogFirmwareUploadProgress({
8
+ percent: 9,
9
+ lastLoggedPercent: 5,
10
+ now: 5_000,
11
+ lastLoggedAt: 0,
12
+ })
13
+ ).toBe(false);
14
+
15
+ expect(
16
+ shouldLogFirmwareUploadProgress({
17
+ percent: 10,
18
+ lastLoggedPercent: 5,
19
+ now: 5_000,
20
+ lastLoggedAt: 0,
21
+ })
22
+ ).toBe(true);
23
+ });
24
+
25
+ test('进度不足 5% 时最长每 10 秒打印一次心跳', () => {
26
+ expect(
27
+ shouldLogFirmwareUploadProgress({
28
+ percent: 7,
29
+ lastLoggedPercent: 5,
30
+ now: 10_000,
31
+ lastLoggedAt: 0,
32
+ })
33
+ ).toBe(true);
34
+ });
35
+ });
36
+
37
+ describe('firmware upload write mode', () => {
38
+ test('固件上传使用带响应写入,普通命令保持默认模式', () => {
39
+ expect(getProtocolV1SendOptions('FirmwareUpload')).toEqual({ withoutResponse: false });
40
+ expect(getProtocolV1SendOptions('Initialize')).toBeUndefined();
41
+ });
42
+ });
@@ -68,12 +68,25 @@ const protocolV2Schema = {
68
68
  },
69
69
  },
70
70
  },
71
+ Failure: {
72
+ fields: {
73
+ code: {
74
+ type: 'uint32',
75
+ id: 1,
76
+ },
77
+ message: {
78
+ type: 'string',
79
+ id: 2,
80
+ },
81
+ },
82
+ },
71
83
  MessageType: {
72
84
  values: {
73
85
  MessageType_ProtocolInfoRequest: 60200,
74
86
  MessageType_ProtocolInfo: 60201,
75
87
  MessageType_Ping: 60206,
76
88
  MessageType_Success: 60207,
89
+ MessageType_Failure: 60208,
77
90
  },
78
91
  },
79
92
  },
@@ -122,6 +135,49 @@ const splitFrame = (frame, index) => [
122
135
  ];
123
136
 
124
137
  describe('LowlevelTransport protocol framing', () => {
138
+ test('falls back to Protocol V2 when a cached V1 hint is stale', async () => {
139
+ const plugin = createPlugin({ devices: [], responses: [] });
140
+ const lowlevel = configureTransport(plugin);
141
+ const events = [];
142
+ lowlevel.probeProtocolV1 = jest.fn().mockImplementation(() => {
143
+ events.push('probe-v1');
144
+ return Promise.resolve(false);
145
+ });
146
+ lowlevel.resetConnectionAfterProbe = jest.fn().mockImplementation(() => {
147
+ events.push('reset');
148
+ return Promise.resolve();
149
+ });
150
+ lowlevel.probeProtocolV2 = jest.fn().mockImplementation(() => {
151
+ events.push('probe-v2');
152
+ return Promise.resolve(true);
153
+ });
154
+
155
+ await expect(lowlevel.detectProtocol('pro-lowlevel', undefined, 'V1')).resolves.toBe('V2');
156
+
157
+ expect(events).toEqual(['probe-v1', 'reset', 'probe-v2']);
158
+ expect(lowlevel.getProtocolType('pro-lowlevel')).toBe('V2');
159
+ });
160
+
161
+ test('keeps active links when the Protocol V2 schema is configured repeatedly', () => {
162
+ const lowlevel = new LowlevelTransport();
163
+ const invalidateAllLinks = jest.fn().mockResolvedValue(undefined);
164
+ lowlevel.protocolV2Links.invalidateAllLinks = invalidateAllLinks;
165
+
166
+ lowlevel.configureProtocolV2(protocolV2Schema);
167
+ lowlevel.configureProtocolV2(protocolV2Schema);
168
+
169
+ expect(invalidateAllLinks).not.toHaveBeenCalled();
170
+
171
+ lowlevel.configureProtocolV2({
172
+ nested: {
173
+ ...protocolV2Schema.nested,
174
+ ExtraMessage: { fields: {} },
175
+ },
176
+ });
177
+
178
+ expect(invalidateAllLinks).toHaveBeenCalledWith('Protocol V2 schema reconfigured');
179
+ });
180
+
125
181
  test('keeps Protocol V1 raw notification chunks compatible', async () => {
126
182
  const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
127
183
  message: 'ok',
@@ -142,6 +198,23 @@ describe('LowlevelTransport protocol framing', () => {
142
198
  });
143
199
  });
144
200
 
201
+ test('uses the Protocol V2 BLE writer with the lowlevel compatibility packet size', async () => {
202
+ const plugin = createPlugin({ devices: [], responses: [] });
203
+ const lowlevel = configureTransport(plugin);
204
+ const context = {
205
+ messageName: 'Ping',
206
+ timeoutMs: 1000,
207
+ highThroughput: false,
208
+ generation: 1,
209
+ signal: new AbortController().signal,
210
+ };
211
+
212
+ await lowlevel.writeProtocolV2Frame('pro2-id', new Uint8Array(130), context, jest.fn());
213
+
214
+ expect(plugin.send).toHaveBeenCalledTimes(3);
215
+ expect(plugin.send.mock.calls.map(([, hex]) => hex.length / 2)).toEqual([64, 64, 2]);
216
+ });
217
+
145
218
  test('rejects calls before protocol detection', async () => {
146
219
  const responseChunks = ProtocolV1.encodeTransportPackets(schemas.protocolV1, 'Success', {
147
220
  message: 'ok',
@@ -172,11 +245,15 @@ describe('LowlevelTransport protocol framing', () => {
172
245
  supported_messages: [60200, 60201, 60206, 60207],
173
246
  protobuf_definition: 'onekey-protocol-v2',
174
247
  },
175
- { router: PROTOCOL_V2_CHANNEL_BLE_UART }
248
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART, seq: 2 }
176
249
  );
177
250
  const plugin = createPlugin({
178
251
  devices: [{ id: 'pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
179
- responses: [...splitFrame(probeResponse, 4), ...splitFrame(callResponse, 5)],
252
+ responses: [
253
+ new Error('Protocol V1 probe timed out'),
254
+ ...splitFrame(probeResponse, 4),
255
+ ...splitFrame(callResponse, 5),
256
+ ],
180
257
  });
181
258
  const lowlevel = configureTransport(plugin);
182
259
 
@@ -196,9 +273,9 @@ describe('LowlevelTransport protocol framing', () => {
196
273
  },
197
274
  });
198
275
  expect(plugin.send).toHaveBeenCalled();
199
- const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
200
- Number.parseInt(hex.slice(12, 14), 16)
201
- );
276
+ const sentSeqs = plugin.send.mock.calls
277
+ .map(([, hex]) => Number.parseInt(hex.slice(12, 14), 16))
278
+ .filter(seq => seq > 0);
202
279
  expect(sentSeqs).toEqual([1, 2]);
203
280
  });
204
281
 
@@ -222,7 +299,7 @@ describe('LowlevelTransport protocol framing', () => {
222
299
  expect(lowlevel.getProtocolType('unknown-pro2-id')).toBe('V2');
223
300
  });
224
301
 
225
- test('retains the Protocol V2 hint and sequence cursor across release and reacquire', async () => {
302
+ test('detects Protocol V2 again after release without a name-derived hint', async () => {
226
303
  const probeResponse = ProtocolV2.encodeFrame(
227
304
  schemas,
228
305
  'Success',
@@ -231,7 +308,12 @@ describe('LowlevelTransport protocol framing', () => {
231
308
  );
232
309
  const plugin = createPlugin({
233
310
  devices: [{ id: 'reconnect-pro2-id', name: 'OneKey Pro 2', commType: 'ble' }],
234
- responses: [bytesToHex(probeResponse), bytesToHex(probeResponse)],
311
+ responses: [
312
+ new Error('Protocol V1 probe timed out'),
313
+ bytesToHex(probeResponse),
314
+ new Error('Protocol V1 probe timed out'),
315
+ bytesToHex(probeResponse),
316
+ ],
235
317
  });
236
318
  const lowlevel = configureTransport(plugin);
237
319
 
@@ -246,22 +328,26 @@ describe('LowlevelTransport protocol framing', () => {
246
328
  protocolType: 'V2',
247
329
  });
248
330
 
249
- const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
250
- Number.parseInt(hex.slice(12, 14), 16)
251
- );
331
+ const sentSeqs = plugin.send.mock.calls
332
+ .map(([, hex]) => Number.parseInt(hex.slice(12, 14), 16))
333
+ .filter(seq => seq > 0);
252
334
  expect(sentSeqs).toEqual([1, 2]);
253
335
  });
254
336
 
255
337
  test('reuses the active generation when Core acquires the same BLE connection again', async () => {
256
- const probeResponse = ProtocolV2.encodeFrame(
257
- schemas,
258
- 'Success',
259
- { message: 'ok' },
260
- { router: PROTOCOL_V2_CHANNEL_BLE_UART }
338
+ const responses = [1, 2, 3, 4].map(seq =>
339
+ bytesToHex(
340
+ ProtocolV2.encodeFrame(
341
+ schemas,
342
+ 'Success',
343
+ { message: 'ok' },
344
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART, seq }
345
+ )
346
+ )
261
347
  );
262
348
  const plugin = createPlugin({
263
349
  devices: [{ id: 'repeated-acquire-id', name: 'OneKey Pro 2', commType: 'ble' }],
264
- responses: [bytesToHex(probeResponse), bytesToHex(probeResponse)],
350
+ responses,
265
351
  });
266
352
  const lowlevel = configureTransport(plugin);
267
353
 
@@ -283,13 +369,19 @@ describe('LowlevelTransport protocol framing', () => {
283
369
  const sentSeqs = plugin.send.mock.calls.map(([, hex]) =>
284
370
  Number.parseInt(hex.slice(12, 14), 16)
285
371
  );
286
- expect(sentSeqs).toEqual([1, 2]);
372
+ expect(sentSeqs).toEqual([1, 2, 3, 4]);
287
373
  });
288
374
 
289
- test('trusts explicit Protocol V2 during bootloader reconnect without probing Ping', async () => {
375
+ test('actively probes explicit Protocol V2 during bootloader reconnect', async () => {
376
+ const probeResponse = ProtocolV2.encodeFrame(
377
+ schemas,
378
+ 'Success',
379
+ { message: 'ok' },
380
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
381
+ );
290
382
  const plugin = createPlugin({
291
383
  devices: [{ id: 'bootloader-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
292
- responses: [],
384
+ responses: [bytesToHex(probeResponse)],
293
385
  });
294
386
  const lowlevel = configureTransport(plugin);
295
387
 
@@ -299,8 +391,35 @@ describe('LowlevelTransport protocol framing', () => {
299
391
  uuid: 'bootloader-v2-id',
300
392
  protocolType: 'V2',
301
393
  });
302
- expect(plugin.send).not.toHaveBeenCalled();
303
- expect(plugin.receive).not.toHaveBeenCalled();
394
+ expect(plugin.send).toHaveBeenCalledTimes(1);
395
+ expect(plugin.receive).toHaveBeenCalledTimes(1);
396
+ });
397
+
398
+ test('disconnects and clears a lowlevel connection when Protocol V2 reports link disabled', async () => {
399
+ const failureResponse = ProtocolV2.encodeFrame(
400
+ schemas,
401
+ 'Failure',
402
+ { code: 5, message: 'link disabled' },
403
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
404
+ );
405
+ const plugin = createPlugin({
406
+ devices: [{ id: 'usb-owned-id', name: 'OneKey Pro 2', commType: 'ble' }],
407
+ responses: [bytesToHex(failureResponse)],
408
+ });
409
+ const lowlevel = configureTransport(plugin);
410
+
411
+ await expect(
412
+ lowlevel.acquire({ uuid: 'usb-owned-id', expectedProtocol: 'V2' })
413
+ ).rejects.toMatchObject({
414
+ name: 'ProtocolV2LinkDisabledError',
415
+ failureCode: 5,
416
+ firmwareMessage: 'link disabled',
417
+ });
418
+
419
+ expect(plugin.disconnect).toHaveBeenCalledWith('usb-owned-id');
420
+ expect(lowlevel.connectedDevices.has('usb-owned-id')).toBe(false);
421
+ expect(lowlevel.getProtocolType('usb-owned-id')).toBeUndefined();
422
+ expect(lowlevel.protocolV2Assemblers.has('usb-owned-id')).toBe(false);
304
423
  });
305
424
 
306
425
  test('resets the lowlevel connection before probing Protocol V2 after a V1 timeout', async () => {
@@ -342,11 +461,23 @@ describe('LowlevelTransport protocol framing', () => {
342
461
  });
343
462
 
344
463
  test('disconnects a tainted Protocol V2 link after a response timeout', async () => {
464
+ const probeResponse = ProtocolV2.encodeFrame(
465
+ schemas,
466
+ 'Success',
467
+ { message: 'ok' },
468
+ { router: PROTOCOL_V2_CHANNEL_BLE_UART }
469
+ );
345
470
  const plugin = createPlugin({
346
471
  devices: [{ id: 'timeout-v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
347
- responses: [],
472
+ responses: [bytesToHex(probeResponse)],
473
+ });
474
+ let receiveCount = 0;
475
+ plugin.receive.mockImplementation(() => {
476
+ receiveCount += 1;
477
+ return receiveCount === 1
478
+ ? Promise.resolve(bytesToHex(probeResponse))
479
+ : new Promise(() => {});
348
480
  });
349
- plugin.receive.mockImplementation(() => new Promise(() => {}));
350
481
  const lowlevel = configureTransport(plugin);
351
482
 
352
483
  await lowlevel.acquire({ uuid: 'timeout-v2-id', expectedProtocol: 'V2' });
@@ -358,6 +489,28 @@ describe('LowlevelTransport protocol framing', () => {
358
489
  expect(plugin.disconnect).toHaveBeenCalledWith('timeout-v2-id');
359
490
  });
360
491
 
492
+ test('preserves an undefined business timeout outside explicit probes', async () => {
493
+ const plugin = createPlugin({
494
+ devices: [{ id: 'v2-id', name: 'OneKey Pro 2', commType: 'ble' }],
495
+ responses: [],
496
+ });
497
+ const lowlevel = configureTransport(plugin);
498
+ lowlevel.deviceProtocol.set('v2-id', 'V2');
499
+ const linkCall = jest
500
+ .spyOn(lowlevel.protocolV2Links, 'call')
501
+ .mockResolvedValue({ type: 'Success', message: {} });
502
+
503
+ await lowlevel.call('v2-id', 'Ping', { message: 'no-business-timeout' });
504
+
505
+ expect(linkCall).toHaveBeenCalledWith(
506
+ 'v2-id',
507
+ expect.any(Function),
508
+ 'Ping',
509
+ { message: 'no-business-timeout' },
510
+ undefined
511
+ );
512
+ });
513
+
361
514
  test('verifies expected Protocol V1 instead of trusting the requested protocol', async () => {
362
515
  const plugin = createPlugin({
363
516
  devices: [{ id: 'v2-id', name: 'Unknown BLE Device', commType: 'ble' }],
package/dist/index.d.ts CHANGED
@@ -1,12 +1,22 @@
1
1
  import * as transport from '@onekeyfe/hd-transport';
2
- import transport__default, { ProtocolType, LowlevelTransportSharedPlugin, LowLevelDevice, TransportCallOptions } from '@onekeyfe/hd-transport';
2
+ import transport__default, { ProtocolType, LowlevelTransportSharedPlugin, TransportCallOptions } from '@onekeyfe/hd-transport';
3
3
  import EventEmitter from 'events';
4
4
 
5
5
  type LowLevelAcquireInput = {
6
6
  uuid: string;
7
7
  expectedProtocol?: ProtocolType;
8
+ protocolHint?: ProtocolType;
8
9
  };
9
10
 
11
+ declare function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, lastLoggedAt, }: {
12
+ percent: number;
13
+ lastLoggedPercent: number;
14
+ now: number;
15
+ lastLoggedAt: number;
16
+ }): boolean;
17
+ declare function getProtocolV1SendOptions(name: string): {
18
+ withoutResponse: boolean;
19
+ } | undefined;
10
20
  declare class LowlevelTransport {
11
21
  _messages: ReturnType<typeof transport__default.parseConfigure> | undefined;
12
22
  _messagesV2: ReturnType<typeof transport__default.parseConfigure> | undefined;
@@ -20,18 +30,20 @@ declare class LowlevelTransport {
20
30
  private protocolV2Generations;
21
31
  private connectedDevices;
22
32
  private protocolV2Links;
33
+ private protocolV2SchemaConfiguration;
23
34
  getProtocolType(path: string): ProtocolType | undefined;
24
35
  init(logger: any, emitter: EventEmitter, plugin: LowlevelTransportSharedPlugin): void;
25
36
  configure(signedData: any): void;
26
37
  configureProtocolV2(signedData: any): void;
27
38
  listen(): void;
28
- enumerate(): Promise<LowLevelDevice[]>;
39
+ enumerate(): Promise<transport.LowLevelDevice[]>;
29
40
  acquire(input: LowLevelAcquireInput): Promise<{
30
41
  uuid: string;
31
42
  protocolType: ProtocolType;
32
43
  }>;
33
44
  release(uuid: string): Promise<boolean>;
34
45
  call(uuid: string, name: string, data: Record<string, unknown>, options?: TransportCallOptions): Promise<transport.MessageFromOneKey>;
46
+ post(uuid: string, name: string, data: Record<string, unknown>): Promise<void>;
35
47
  private callProtocolV1;
36
48
  private createProtocolTimeoutError;
37
49
  private createProtocolMismatchError;
@@ -51,4 +63,4 @@ declare class LowlevelTransport {
51
63
  cancel(): void;
52
64
  }
53
65
 
54
- export { LowlevelTransport as default };
66
+ export { LowlevelTransport as default, getProtocolV1SendOptions, shouldLogFirmwareUploadProgress };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,SAWN,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,YAAY,EACZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAqBpD,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,WAAW,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAErE,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,MAAM,EAAE,6BAA6B,CAAuC;IAE5E,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,oBAAoB,CAAoD;IAEhF,OAAO,CAAC,qBAAqB,CAAkC;IAE/D,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,eAAe,CA6BpB;IAEH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIvD,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,6BAA6B;IAO9E,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAOnC,MAAM;IAIA,SAAS;IAWT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IA6BnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAgBpB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;YAsBlB,cAAc;IAoC5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;YAsDd,yBAAyB;YAiCzB,eAAe;YAgBf,eAAe;YA6Bf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAOpB,cAAc;IA6B5B,OAAO,CAAC,uBAAuB;IAkC/B,OAAO,CAAC,2BAA2B;IAMnC,MAAM;CAGP"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,SAYN,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,YAAY,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,6BAA6B,EAC7B,YAAY,EAEZ,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAUpD,wBAAgB,+BAA+B,CAAC,EAC9C,OAAO,EACP,iBAAiB,EACjB,GAAG,EACH,YAAY,GACb,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;CACtB,WAMA;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM;;cAEpD;AAUD,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,WAAW,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAErE,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB,MAAM,EAAE,6BAA6B,CAAuC;IAE5E,OAAO,CAAC,cAAc,CAAwC;IAE9D,OAAO,CAAC,mBAAmB,CAAwC;IAEnE,OAAO,CAAC,oBAAoB,CAAoD;IAEhF,OAAO,CAAC,qBAAqB,CAAkC;IAE/D,OAAO,CAAC,gBAAgB,CAA0B;IAElD,OAAO,CAAC,eAAe,CA6BpB;IAEH,OAAO,CAAC,6BAA6B,CAAqB;IAE1D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIvD,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,6BAA6B;IAO9E,SAAS,CAAC,UAAU,EAAE,GAAG;IAMzB,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAiBnC,MAAM;IAIA,SAAS;IAKT,OAAO,CAAC,KAAK,EAAE,oBAAoB;;;;IAmDnC,OAAO,CAAC,IAAI,EAAE,MAAM;IAepB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,oBAAoB;IAoB1B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;YAatD,cAAc;IA6E5B,OAAO,CAAC,0BAA0B;IAOlC,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,kBAAkB;YAMZ,cAAc;YA2Cd,yBAAyB;YAiCzB,eAAe;YAgBf,eAAe;YA6Bf,UAAU;YAUV,qBAAqB;YAmBrB,mBAAmB;YAqBnB,oBAAoB;YAgBpB,cAAc;IAwB5B,OAAO,CAAC,uBAAuB;IAgC/B,OAAO,CAAC,2BAA2B;IAMnC,MAAM;CAGP"}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
3
5
  var hdShared = require('@onekeyfe/hd-shared');
4
6
  var transport = require('@onekeyfe/hd-transport');
5
7
 
@@ -40,10 +42,16 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
40
42
  const { check, ProtocolV1, parseConfigure } = transport__default["default"];
41
43
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
42
44
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
43
- const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30000;
44
45
  const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
45
- function inferProtocolHintFromDeviceName(name) {
46
- return /\bpro\s*2\b/i.test(name !== null && name !== void 0 ? name : '') ? 'V2' : undefined;
46
+ const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
47
+ const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10000;
48
+ function shouldLogFirmwareUploadProgress({ percent, lastLoggedPercent, now, lastLoggedAt, }) {
49
+ return (percent === 100 ||
50
+ percent - lastLoggedPercent >= FIRMWARE_UPLOAD_LOG_PERCENT_STEP ||
51
+ now - lastLoggedAt >= FIRMWARE_UPLOAD_LOG_INTERVAL_MS);
52
+ }
53
+ function getProtocolV1SendOptions(name) {
54
+ return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
47
55
  }
48
56
  function isProtocolV1TransportChunk(data) {
49
57
  return data.length >= 9 && data[0] === 0x3f && data[1] === 0x23 && data[2] === 0x23;
@@ -106,27 +114,29 @@ class LowlevelTransport {
106
114
  this._messages = messages;
107
115
  }
108
116
  configureProtocolV2(signedData) {
117
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
118
+ if (this.protocolV2SchemaConfiguration === configuration) {
119
+ return;
120
+ }
121
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
109
122
  this._messagesV2 = parseConfigure(signedData);
110
- this.protocolV2Links
111
- .invalidateAllLinks('Protocol V2 schema reconfigured')
112
- .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('Protocol V2 schema link cleanup failed:', error); });
123
+ this.protocolV2SchemaConfiguration = configuration;
124
+ if (isReconfiguration) {
125
+ this.protocolV2Links
126
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
127
+ .catch(error => { var _a; return (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('Protocol V2 schema link cleanup failed:', error); });
128
+ }
113
129
  }
114
130
  listen() {
115
131
  }
116
132
  enumerate() {
117
133
  return __awaiter(this, void 0, void 0, function* () {
118
134
  const devices = yield this.plugin.enumerate();
119
- return devices.map((device) => {
120
- const protocolHint = inferProtocolHintFromDeviceName(device.name);
121
- if (protocolHint) {
122
- this.deviceProtocolHints.set(device.id, protocolHint);
123
- }
124
- return device;
125
- });
135
+ return devices;
126
136
  });
127
137
  }
128
138
  acquire(input) {
129
- var _a;
139
+ var _a, _b, _c, _d;
130
140
  return __awaiter(this, void 0, void 0, function* () {
131
141
  const alreadyConnected = this.connectedDevices.has(input.uuid);
132
142
  try {
@@ -141,12 +151,35 @@ class LowlevelTransport {
141
151
  this.Log.debug('lowlelvel transport connect error: ', error);
142
152
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.LowlevelTrasnportConnectError, (_a = error.message) !== null && _a !== void 0 ? _a : error);
143
153
  }
144
- this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler());
154
+ this.protocolV2Assemblers.set(input.uuid, new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES));
145
155
  const protocolHint = input.expectedProtocol
146
156
  ? undefined
147
- : this.deviceProtocolHints.get(input.uuid);
148
- const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
149
- return { uuid: input.uuid, protocolType };
157
+ : (_b = input.protocolHint) !== null && _b !== void 0 ? _b : this.deviceProtocolHints.get(input.uuid);
158
+ try {
159
+ const protocolType = yield this.detectProtocol(input.uuid, input.expectedProtocol, protocolHint);
160
+ return { uuid: input.uuid, protocolType };
161
+ }
162
+ catch (error) {
163
+ try {
164
+ yield this.protocolV2Links.invalidateLink(input.uuid, 'Lowlevel transport acquire failed');
165
+ }
166
+ catch (cleanupError) {
167
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[LowlevelTransport] acquire link cleanup failed:', cleanupError);
168
+ }
169
+ try {
170
+ yield this.plugin.disconnect(input.uuid);
171
+ }
172
+ catch (cleanupError) {
173
+ (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug('[LowlevelTransport] acquire disconnect failed:', cleanupError);
174
+ }
175
+ finally {
176
+ this.connectedDevices.delete(input.uuid);
177
+ this.deviceProtocol.delete(input.uuid);
178
+ this.protocolV2Assemblers.delete(input.uuid);
179
+ this.advanceProtocolV2Generation(input.uuid);
180
+ }
181
+ throw error;
182
+ }
150
183
  });
151
184
  }
152
185
  release(uuid) {
@@ -174,24 +207,58 @@ class LowlevelTransport {
174
207
  if (!protocol) {
175
208
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, `Device protocol has not been detected for ${uuid}`);
176
209
  }
177
- this.Log.debug('transport call', { name, protocol });
178
210
  if (protocol === 'V2') {
179
211
  return this.callProtocolV2(uuid, name, data, options);
180
212
  }
181
213
  return this.callProtocolV1(uuid, name, data, options);
182
214
  });
183
215
  }
216
+ post(uuid, name, data) {
217
+ return __awaiter(this, void 0, void 0, function* () {
218
+ if (this.getProtocolType(uuid) === 'V2') {
219
+ yield this.protocolV2Links.sendFlowControl(uuid, () => this.createProtocolV2Adapter(uuid), name, data);
220
+ return;
221
+ }
222
+ yield this.callProtocolV1(uuid, name, data);
223
+ });
224
+ }
184
225
  callProtocolV1(uuid, name, data, options) {
226
+ var _a;
185
227
  return __awaiter(this, void 0, void 0, function* () {
186
228
  if (!this._messages) {
187
229
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
188
230
  }
189
231
  const messages = this._messages;
190
232
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
191
- for (const o of buffers) {
233
+ const isFirmwareUpload = name === 'FirmwareUpload';
234
+ const uploadStartedAt = Date.now();
235
+ const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
236
+ let sentBytes = 0;
237
+ let lastLoggedPercent = 0;
238
+ let lastLoggedAt = uploadStartedAt;
239
+ for (const [index, o] of buffers.entries()) {
192
240
  const outData = o.toString('hex');
193
241
  try {
194
- yield this.plugin.send(uuid, outData);
242
+ yield this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
243
+ sentBytes += o.limit;
244
+ if (isFirmwareUpload) {
245
+ const now = Date.now();
246
+ const percent = Math.floor(((index + 1) / buffers.length) * 100);
247
+ if (shouldLogFirmwareUploadProgress({
248
+ percent,
249
+ lastLoggedPercent,
250
+ now,
251
+ lastLoggedAt,
252
+ })) {
253
+ const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
254
+ const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
255
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
256
+ `(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
257
+ `${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`);
258
+ lastLoggedPercent = percent;
259
+ lastLoggedAt = now;
260
+ }
261
+ }
195
262
  }
196
263
  catch (e) {
197
264
  this.Log.debug('lowlevel transport send error: ', e);
@@ -204,6 +271,15 @@ class LowlevelTransport {
204
271
  return check.call(jsonData);
205
272
  }
206
273
  catch (e) {
274
+ if ((e === null || e === void 0 ? void 0 : e.errorCode) === hdShared.HardwareErrorCode.BleTimeoutError &&
275
+ (options === null || options === void 0 ? void 0 : options.timeoutMs) !== PROTOCOL_PROBE_TIMEOUT_MS) {
276
+ try {
277
+ yield this.resetConnectionAfterProbe(uuid, 'V1');
278
+ }
279
+ catch (resetError) {
280
+ this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
281
+ }
282
+ }
207
283
  if (name === 'Initialize' && (options === null || options === void 0 ? void 0 : options.timeoutMs) === PROTOCOL_PROBE_TIMEOUT_MS) {
208
284
  this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
209
285
  }
@@ -229,12 +305,15 @@ class LowlevelTransport {
229
305
  }
230
306
  }
231
307
  detectProtocol(uuid, expectedProtocol, protocolHint) {
232
- var _a, _b, _c, _d, _e, _f;
308
+ var _a, _b, _c;
233
309
  return __awaiter(this, void 0, void 0, function* () {
234
310
  if (expectedProtocol === 'V2') {
235
- this.deviceProtocol.set(uuid, 'V2');
236
- (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
237
- return 'V2';
311
+ if (yield this.probeProtocolV2(uuid)) {
312
+ this.deviceProtocol.set(uuid, 'V2');
313
+ (_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
314
+ return 'V2';
315
+ }
316
+ throw this.createProtocolMismatchError(expectedProtocol);
238
317
  }
239
318
  if (expectedProtocol === 'V1') {
240
319
  if (yield this.probeProtocolV1(uuid)) {
@@ -244,28 +323,17 @@ class LowlevelTransport {
244
323
  }
245
324
  throw this.createProtocolMismatchError(expectedProtocol);
246
325
  }
247
- if (protocolHint === 'V2' && (yield this.probeProtocolV2(uuid))) {
248
- this.deviceProtocol.set(uuid, 'V2');
249
- (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
250
- return 'V2';
251
- }
252
- const cachedProtocol = this.deviceProtocol.get(uuid);
253
- if (cachedProtocol === 'V2' && (yield this.probeProtocolV2(uuid))) {
254
- this.deviceProtocol.set(uuid, 'V2');
255
- (_d = this.Log) === null || _d === void 0 ? void 0 : _d.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
256
- return 'V2';
257
- }
258
- const protocolV1Detected = yield this.probeProtocolV1(uuid);
259
- if (protocolV1Detected) {
260
- this.deviceProtocol.set(uuid, 'V1');
261
- (_e = this.Log) === null || _e === void 0 ? void 0 : _e.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
262
- return 'V1';
263
- }
264
- yield this.resetConnectionAfterProbe(uuid, 'V1');
265
- if (yield this.probeProtocolV2(uuid)) {
266
- this.deviceProtocol.set(uuid, 'V2');
267
- (_f = this.Log) === null || _f === void 0 ? void 0 : _f.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
268
- return 'V2';
326
+ const probeOrder = protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
327
+ for (const [index, protocol] of probeOrder.entries()) {
328
+ if (index > 0) {
329
+ yield this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
330
+ }
331
+ const detected = protocol === 'V1' ? yield this.probeProtocolV1(uuid) : yield this.probeProtocolV2(uuid);
332
+ if (detected) {
333
+ this.deviceProtocol.set(uuid, protocol);
334
+ (_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
335
+ return protocol;
336
+ }
269
337
  }
270
338
  this.deviceProtocol.delete(uuid);
271
339
  throw this.createProtocolDetectionError();
@@ -373,7 +441,7 @@ class LowlevelTransport {
373
441
  return __awaiter(this, void 0, void 0, function* () {
374
442
  let assembler = this.protocolV2Assemblers.get(uuid);
375
443
  if (!assembler) {
376
- assembler = new transport.ProtocolV2FrameAssembler();
444
+ assembler = new transport.ProtocolV2FrameAssembler(transport.PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
377
445
  this.protocolV2Assemblers.set(uuid, assembler);
378
446
  }
379
447
  const queuedFrame = assembler.push(new Uint8Array(0));
@@ -390,23 +458,25 @@ class LowlevelTransport {
390
458
  return frame;
391
459
  });
392
460
  }
393
- writeProtocolV2Frame(uuid, frame) {
461
+ writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration) {
394
462
  return __awaiter(this, void 0, void 0, function* () {
395
- for (let offset = 0; offset < frame.length; offset += LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH) {
396
- const chunk = frame.slice(offset, offset + LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH);
397
- yield this.plugin.send(uuid, transport.bytesToHex(chunk));
398
- }
463
+ yield transport.writeProtocolV2BleFrame({
464
+ frame,
465
+ packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
466
+ assertActive: assertCurrentGeneration,
467
+ signal: context.signal,
468
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
469
+ writePacket: packet => this.plugin.send(uuid, transport.bytesToHex(packet)),
470
+ });
399
471
  });
400
472
  }
401
473
  callProtocolV2(uuid, name, data, options) {
402
- var _a;
403
474
  return __awaiter(this, void 0, void 0, function* () {
404
475
  if (!this._messages || !this._messagesV2) {
405
476
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.TransportNotConfigured);
406
477
  }
407
- const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : LOWLEVEL_PROTOCOL_TIMEOUT_MS;
408
478
  try {
409
- return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, Object.assign(Object.assign({}, options), { timeoutMs }));
479
+ return yield this.protocolV2Links.call(uuid, () => this.createProtocolV2Adapter(uuid), name, data, options);
410
480
  }
411
481
  catch (e) {
412
482
  this.Log.error('lowlevel Protocol V2 call error: ', e);
@@ -431,10 +501,7 @@ class LowlevelTransport {
431
501
  assertCurrentGeneration();
432
502
  (_a = this.protocolV2Assemblers.get(uuid)) === null || _a === void 0 ? void 0 : _a.reset();
433
503
  },
434
- writeFrame: (frame) => {
435
- assertCurrentGeneration();
436
- return this.writeProtocolV2Frame(uuid, frame);
437
- },
504
+ writeFrame: (frame, context) => this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
438
505
  readFrame: (context) => {
439
506
  assertCurrentGeneration();
440
507
  return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
@@ -459,4 +526,6 @@ class LowlevelTransport {
459
526
  }
460
527
  }
461
528
 
462
- module.exports = LowlevelTransport;
529
+ exports["default"] = LowlevelTransport;
530
+ exports.getProtocolV1SendOptions = getProtocolV1SendOptions;
531
+ exports.shouldLogFirmwareUploadProgress = shouldLogFirmwareUploadProgress;
package/dist/types.d.ts CHANGED
@@ -2,5 +2,6 @@ import type { ProtocolType } from '@onekeyfe/hd-transport';
2
2
  export type LowLevelAcquireInput = {
3
3
  uuid: string;
4
4
  expectedProtocol?: ProtocolType;
5
+ protocolHint?: ProtocolType;
5
6
  };
6
7
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,YAAY,CAAC;CACjC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport-lowlevel",
3
- "version": "1.2.0-alpha.15",
3
+ "version": "1.2.0-alpha.151",
4
4
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -20,8 +20,8 @@
20
20
  "lint:fix": "eslint . --fix"
21
21
  },
22
22
  "dependencies": {
23
- "@onekeyfe/hd-shared": "1.2.0-alpha.15",
24
- "@onekeyfe/hd-transport": "1.2.0-alpha.15"
23
+ "@onekeyfe/hd-shared": "1.2.0-alpha.151",
24
+ "@onekeyfe/hd-transport": "1.2.0-alpha.151"
25
25
  },
26
- "gitHead": "06dd8f9cba892bb66242e5a51038a7103fb05f09"
26
+ "gitHead": "d1fa81c92deb1bfa3903e3963a82edd6384d568a"
27
27
  }
package/src/index.ts CHANGED
@@ -10,13 +10,14 @@ import transport, {
10
10
  hexToBytes,
11
11
  probeProtocolV2 as probeProtocolV2Helper,
12
12
  withProtocolTimeout,
13
+ writeProtocolV2BleFrame,
13
14
  } from '@onekeyfe/hd-transport';
14
15
 
15
16
  import type EventEmitter from 'events';
16
17
  import type {
17
- LowLevelDevice,
18
18
  LowlevelTransportSharedPlugin,
19
19
  ProtocolType,
20
+ ProtocolV2CallContext,
20
21
  TransportCallOptions,
21
22
  } from '@onekeyfe/hd-transport';
22
23
  import type { LowLevelAcquireInput } from './types';
@@ -25,11 +26,30 @@ const { check, ProtocolV1, parseConfigure } = transport;
25
26
 
26
27
  const PROTOCOL_PROBE_TIMEOUT_MS = 1000;
27
28
  const PROTOCOL_V2_PROBE_TIMEOUT_MS = 5000;
28
- const LOWLEVEL_PROTOCOL_TIMEOUT_MS = 30_000;
29
29
  const LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH = 64;
30
+ const FIRMWARE_UPLOAD_LOG_PERCENT_STEP = 5;
31
+ const FIRMWARE_UPLOAD_LOG_INTERVAL_MS = 10_000;
32
+
33
+ export function shouldLogFirmwareUploadProgress({
34
+ percent,
35
+ lastLoggedPercent,
36
+ now,
37
+ lastLoggedAt,
38
+ }: {
39
+ percent: number;
40
+ lastLoggedPercent: number;
41
+ now: number;
42
+ lastLoggedAt: number;
43
+ }) {
44
+ return (
45
+ percent === 100 ||
46
+ percent - lastLoggedPercent >= FIRMWARE_UPLOAD_LOG_PERCENT_STEP ||
47
+ now - lastLoggedAt >= FIRMWARE_UPLOAD_LOG_INTERVAL_MS
48
+ );
49
+ }
30
50
 
31
- function inferProtocolHintFromDeviceName(name?: string | null): ProtocolType | undefined {
32
- return /\bpro\s*2\b/i.test(name ?? '') ? 'V2' : undefined;
51
+ export function getProtocolV1SendOptions(name: string) {
52
+ return name === 'FirmwareUpload' ? { withoutResponse: false } : undefined;
33
53
  }
34
54
 
35
55
  function isProtocolV1TransportChunk(data: Uint8Array) {
@@ -94,6 +114,8 @@ export default class LowlevelTransport {
94
114
  },
95
115
  });
96
116
 
117
+ private protocolV2SchemaConfiguration: string | undefined;
118
+
97
119
  getProtocolType(path: string): ProtocolType | undefined {
98
120
  return this.deviceProtocol.get(path);
99
121
  }
@@ -112,10 +134,20 @@ export default class LowlevelTransport {
112
134
  }
113
135
 
114
136
  configureProtocolV2(signedData: any) {
137
+ const configuration = typeof signedData === 'string' ? signedData : JSON.stringify(signedData);
138
+ if (this.protocolV2SchemaConfiguration === configuration) {
139
+ return;
140
+ }
141
+
142
+ const isReconfiguration = this.protocolV2SchemaConfiguration !== undefined;
115
143
  this._messagesV2 = parseConfigure(signedData);
116
- this.protocolV2Links
117
- .invalidateAllLinks('Protocol V2 schema reconfigured')
118
- .catch(error => this.Log?.debug('Protocol V2 schema link cleanup failed:', error));
144
+ this.protocolV2SchemaConfiguration = configuration;
145
+
146
+ if (isReconfiguration) {
147
+ this.protocolV2Links
148
+ .invalidateAllLinks('Protocol V2 schema reconfigured')
149
+ .catch(error => this.Log?.debug('Protocol V2 schema link cleanup failed:', error));
150
+ }
119
151
  }
120
152
 
121
153
  listen() {
@@ -124,13 +156,7 @@ export default class LowlevelTransport {
124
156
 
125
157
  async enumerate() {
126
158
  const devices = await this.plugin.enumerate();
127
- return devices.map((device: LowLevelDevice) => {
128
- const protocolHint = inferProtocolHintFromDeviceName(device.name);
129
- if (protocolHint) {
130
- this.deviceProtocolHints.set(device.id, protocolHint);
131
- }
132
- return device;
133
- });
159
+ return devices;
134
160
  }
135
161
 
136
162
  async acquire(input: LowLevelAcquireInput) {
@@ -150,16 +176,38 @@ export default class LowlevelTransport {
150
176
  );
151
177
  }
152
178
 
153
- this.protocolV2Assemblers.set(input.uuid, new ProtocolV2FrameAssembler());
154
- const protocolHint = input.expectedProtocol
155
- ? undefined
156
- : this.deviceProtocolHints.get(input.uuid);
157
- const protocolType = await this.detectProtocol(
179
+ this.protocolV2Assemblers.set(
158
180
  input.uuid,
159
- input.expectedProtocol,
160
- protocolHint
181
+ new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES)
161
182
  );
162
- return { uuid: input.uuid, protocolType };
183
+ const protocolHint = input.expectedProtocol
184
+ ? undefined
185
+ : input.protocolHint ?? this.deviceProtocolHints.get(input.uuid);
186
+ try {
187
+ const protocolType = await this.detectProtocol(
188
+ input.uuid,
189
+ input.expectedProtocol,
190
+ protocolHint
191
+ );
192
+ return { uuid: input.uuid, protocolType };
193
+ } catch (error) {
194
+ try {
195
+ await this.protocolV2Links.invalidateLink(input.uuid, 'Lowlevel transport acquire failed');
196
+ } catch (cleanupError) {
197
+ this.Log?.debug('[LowlevelTransport] acquire link cleanup failed:', cleanupError);
198
+ }
199
+ try {
200
+ await this.plugin.disconnect(input.uuid);
201
+ } catch (cleanupError) {
202
+ this.Log?.debug('[LowlevelTransport] acquire disconnect failed:', cleanupError);
203
+ } finally {
204
+ this.connectedDevices.delete(input.uuid);
205
+ this.deviceProtocol.delete(input.uuid);
206
+ this.protocolV2Assemblers.delete(input.uuid);
207
+ this.advanceProtocolV2Generation(input.uuid);
208
+ }
209
+ throw error;
210
+ }
163
211
  }
164
212
 
165
213
  async release(uuid: string) {
@@ -168,8 +216,7 @@ export default class LowlevelTransport {
168
216
  await this.plugin.disconnect(uuid);
169
217
  this.connectedDevices.delete(uuid);
170
218
  this.deviceProtocol.delete(uuid);
171
- // 设备名称推断出的协议提示不依赖当前连接,保留它可以让快速重连
172
- // 直接执行 Protocol V2 探测,避免先发送一次无意义的 V1 Initialize。
219
+ // Confirmed protocol stays on the device endpoint; BLE names are not a probe hint.
173
220
  this.protocolV2Assemblers.delete(uuid);
174
221
  return true;
175
222
  } catch (error) {
@@ -195,8 +242,6 @@ export default class LowlevelTransport {
195
242
  `Device protocol has not been detected for ${uuid}`
196
243
  );
197
244
  }
198
- this.Log.debug('transport call', { name, protocol });
199
-
200
245
  if (protocol === 'V2') {
201
246
  return this.callProtocolV2(uuid, name, data, options);
202
247
  }
@@ -204,6 +249,19 @@ export default class LowlevelTransport {
204
249
  return this.callProtocolV1(uuid, name, data, options);
205
250
  }
206
251
 
252
+ async post(uuid: string, name: string, data: Record<string, unknown>) {
253
+ if (this.getProtocolType(uuid) === 'V2') {
254
+ await this.protocolV2Links.sendFlowControl(
255
+ uuid,
256
+ () => this.createProtocolV2Adapter(uuid),
257
+ name,
258
+ data
259
+ );
260
+ return;
261
+ }
262
+ await this.callProtocolV1(uuid, name, data);
263
+ }
264
+
207
265
  private async callProtocolV1(
208
266
  uuid: string,
209
267
  name: string,
@@ -216,10 +274,41 @@ export default class LowlevelTransport {
216
274
 
217
275
  const messages = this._messages;
218
276
  const buffers = ProtocolV1.encodeTransportPackets(messages, name, data);
219
- for (const o of buffers) {
277
+ const isFirmwareUpload = name === 'FirmwareUpload';
278
+ const uploadStartedAt = Date.now();
279
+ const totalBytes = buffers.reduce((sum, buffer) => sum + buffer.limit, 0);
280
+ let sentBytes = 0;
281
+ let lastLoggedPercent = 0;
282
+ let lastLoggedAt = uploadStartedAt;
283
+
284
+ for (const [index, o] of buffers.entries()) {
220
285
  const outData = o.toString('hex');
221
286
  try {
222
- await this.plugin.send(uuid, outData);
287
+ await this.plugin.send(uuid, outData, getProtocolV1SendOptions(name));
288
+ sentBytes += o.limit;
289
+
290
+ if (isFirmwareUpload) {
291
+ const now = Date.now();
292
+ const percent = Math.floor(((index + 1) / buffers.length) * 100);
293
+ if (
294
+ shouldLogFirmwareUploadProgress({
295
+ percent,
296
+ lastLoggedPercent,
297
+ now,
298
+ lastLoggedAt,
299
+ })
300
+ ) {
301
+ const elapsedSeconds = Math.max((now - uploadStartedAt) / 1000, 0.001);
302
+ const kibPerSecond = sentBytes / 1024 / elapsedSeconds;
303
+ this.Log?.debug(
304
+ `[LowlevelTransport] FirmwareUpload progress: ${percent}% ` +
305
+ `(${index + 1}/${buffers.length} packets, ${sentBytes}/${totalBytes} bytes, ` +
306
+ `${elapsedSeconds.toFixed(1)}s, ${kibPerSecond.toFixed(1)} KiB/s)`
307
+ );
308
+ lastLoggedPercent = percent;
309
+ lastLoggedAt = now;
310
+ }
311
+ }
223
312
  } catch (e) {
224
313
  this.Log.debug('lowlevel transport send error: ', e);
225
314
  throw ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, e.reason);
@@ -231,6 +320,16 @@ export default class LowlevelTransport {
231
320
  const jsonData = ProtocolV1.decodeMessage(messages, response);
232
321
  return check.call(jsonData);
233
322
  } catch (e) {
323
+ if (
324
+ e?.errorCode === HardwareErrorCode.BleTimeoutError &&
325
+ options?.timeoutMs !== PROTOCOL_PROBE_TIMEOUT_MS
326
+ ) {
327
+ try {
328
+ await this.resetConnectionAfterProbe(uuid, 'V1');
329
+ } catch (resetError) {
330
+ this.Log.debug('[LowlevelTransport] reset after Protocol V1 timeout failed:', resetError);
331
+ }
332
+ }
234
333
  if (name === 'Initialize' && options?.timeoutMs === PROTOCOL_PROBE_TIMEOUT_MS) {
235
334
  this.Log.debug('[LowlevelTransport] Protocol V1 Initialize probe call failed:', e);
236
335
  } else {
@@ -273,12 +372,12 @@ export default class LowlevelTransport {
273
372
  protocolHint?: ProtocolType
274
373
  ): Promise<ProtocolType> {
275
374
  if (expectedProtocol === 'V2') {
276
- // 固件升级重启后的 bootloader 不保证响应 Ping。上层显式传入 V2
277
- // 表示协议已经在重启前确认,这里与 RN/Electron BLE 保持一致,
278
- // 直接建立 V2 Link,首个业务命令负责验证链路是否可用。
279
- this.deviceProtocol.set(uuid, 'V2');
280
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
281
- return 'V2';
375
+ if (await this.probeProtocolV2(uuid)) {
376
+ this.deviceProtocol.set(uuid, 'V2');
377
+ this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (expected)`);
378
+ return 'V2';
379
+ }
380
+ throw this.createProtocolMismatchError(expectedProtocol);
282
381
  }
283
382
 
284
383
  if (expectedProtocol === 'V1') {
@@ -290,31 +389,20 @@ export default class LowlevelTransport {
290
389
  throw this.createProtocolMismatchError(expectedProtocol);
291
390
  }
292
391
 
293
- if (protocolHint === 'V2' && (await this.probeProtocolV2(uuid))) {
294
- this.deviceProtocol.set(uuid, 'V2');
295
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (hint)`);
296
- return 'V2';
297
- }
298
-
299
- const cachedProtocol = this.deviceProtocol.get(uuid);
300
- if (cachedProtocol === 'V2' && (await this.probeProtocolV2(uuid))) {
301
- this.deviceProtocol.set(uuid, 'V2');
302
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2 (cached)`);
303
- return 'V2';
304
- }
305
-
306
- const protocolV1Detected = await this.probeProtocolV1(uuid);
307
- if (protocolV1Detected) {
308
- this.deviceProtocol.set(uuid, 'V1');
309
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V1`);
310
- return 'V1';
311
- }
392
+ const probeOrder: ProtocolType[] =
393
+ protocolHint === 'V2' || this.deviceProtocol.get(uuid) === 'V2' ? ['V2', 'V1'] : ['V1', 'V2'];
312
394
 
313
- await this.resetConnectionAfterProbe(uuid, 'V1');
314
- if (await this.probeProtocolV2(uuid)) {
315
- this.deviceProtocol.set(uuid, 'V2');
316
- this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> V2`);
317
- return 'V2';
395
+ for (const [index, protocol] of probeOrder.entries()) {
396
+ if (index > 0) {
397
+ await this.resetConnectionAfterProbe(uuid, probeOrder[index - 1]);
398
+ }
399
+ const detected =
400
+ protocol === 'V1' ? await this.probeProtocolV1(uuid) : await this.probeProtocolV2(uuid);
401
+ if (detected) {
402
+ this.deviceProtocol.set(uuid, protocol);
403
+ this.Log?.debug(`[LowlevelTransport] detectProtocol: uuid=${uuid} -> ${protocol}`);
404
+ return protocol;
405
+ }
318
406
  }
319
407
 
320
408
  this.deviceProtocol.delete(uuid);
@@ -431,7 +519,7 @@ export default class LowlevelTransport {
431
519
  private async readProtocolV2Frame(uuid: string, timeoutMs?: number, commandName = 'ProtocolV2') {
432
520
  let assembler = this.protocolV2Assemblers.get(uuid);
433
521
  if (!assembler) {
434
- assembler = new ProtocolV2FrameAssembler();
522
+ assembler = new ProtocolV2FrameAssembler(PROTOCOL_V2_BLE_FRAME_MAX_BYTES);
435
523
  this.protocolV2Assemblers.set(uuid, assembler);
436
524
  }
437
525
 
@@ -449,11 +537,20 @@ export default class LowlevelTransport {
449
537
  return frame;
450
538
  }
451
539
 
452
- private async writeProtocolV2Frame(uuid: string, frame: Uint8Array) {
453
- for (let offset = 0; offset < frame.length; offset += LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH) {
454
- const chunk = frame.slice(offset, offset + LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH);
455
- await this.plugin.send(uuid, bytesToHex(chunk));
456
- }
540
+ private async writeProtocolV2Frame(
541
+ uuid: string,
542
+ frame: Uint8Array,
543
+ context: ProtocolV2CallContext,
544
+ assertCurrentGeneration: () => void
545
+ ) {
546
+ await writeProtocolV2BleFrame({
547
+ frame,
548
+ packetCapacity: LOWLEVEL_PROTOCOL_V2_PACKET_LENGTH,
549
+ assertActive: assertCurrentGeneration,
550
+ signal: context.signal,
551
+ abortMessage: `Protocol V2 BLE write aborted for ${context.messageName}`,
552
+ writePacket: packet => this.plugin.send(uuid, bytesToHex(packet)),
553
+ });
457
554
  }
458
555
 
459
556
  private async callProtocolV2(
@@ -466,18 +563,13 @@ export default class LowlevelTransport {
466
563
  throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
467
564
  }
468
565
 
469
- const timeoutMs = options?.timeoutMs ?? LOWLEVEL_PROTOCOL_TIMEOUT_MS;
470
-
471
566
  try {
472
567
  return await this.protocolV2Links.call(
473
568
  uuid,
474
569
  () => this.createProtocolV2Adapter(uuid),
475
570
  name,
476
571
  data,
477
- {
478
- ...options,
479
- timeoutMs,
480
- }
572
+ options
481
573
  );
482
574
  } catch (e) {
483
575
  this.Log.error('lowlevel Protocol V2 call error: ', e);
@@ -501,10 +593,8 @@ export default class LowlevelTransport {
501
593
  assertCurrentGeneration();
502
594
  this.protocolV2Assemblers.get(uuid)?.reset();
503
595
  },
504
- writeFrame: (frame: Uint8Array) => {
505
- assertCurrentGeneration();
506
- return this.writeProtocolV2Frame(uuid, frame);
507
- },
596
+ writeFrame: (frame: Uint8Array, context: ProtocolV2CallContext) =>
597
+ this.writeProtocolV2Frame(uuid, frame, context, assertCurrentGeneration),
508
598
  readFrame: (context: { messageName: string; timeoutMs?: number }) => {
509
599
  assertCurrentGeneration();
510
600
  return this.readProtocolV2Frame(uuid, context.timeoutMs, context.messageName);
package/src/types.ts CHANGED
@@ -3,4 +3,5 @@ import type { ProtocolType } from '@onekeyfe/hd-transport';
3
3
  export type LowLevelAcquireInput = {
4
4
  uuid: string;
5
5
  expectedProtocol?: ProtocolType;
6
+ protocolHint?: ProtocolType;
6
7
  };