@onekeyfe/hd-core 1.2.2-alpha.0 → 1.2.2-alpha.1

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.
@@ -54,6 +54,7 @@ const createMethod = ({
54
54
  });
55
55
  (method as any).device = {
56
56
  commands: { typedCall },
57
+ state: { versions: { firmware: firmwareVersion } },
57
58
  ensureProtocolV2RuntimeContext: jest.fn(() =>
58
59
  Promise.resolve({
59
60
  version: 2,
@@ -243,6 +244,32 @@ describe('DeviceUploadNft', () => {
243
244
  });
244
245
  });
245
246
 
247
+ test('keeps the legacy upload flow on a 1.0.1 prerelease', async () => {
248
+ const typedCall = jest.fn((request: string, _response: string, params: any) => {
249
+ if (request === 'FilesystemPathInfoQuery') {
250
+ return { message: { exist: true, directory: true } };
251
+ }
252
+ if (request === 'FilesystemDirList') {
253
+ return { message: { path: 'vol1:/nft', child_files: '' } };
254
+ }
255
+ if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
256
+ if (request === 'NftUpdate') return { message: {} };
257
+ throw new Error(`Unexpected request: ${request}`);
258
+ });
259
+ const method = createMethod({ typedCall, firmwareVersion: '1.0.1-beta.1' });
260
+
261
+ await method.run();
262
+
263
+ const requests = typedCall.mock.calls.map(call => call[0]);
264
+ expect(requests).toContain('FilesystemPathInfoQuery');
265
+ expect(requests).toContain('FilesystemDirList');
266
+ const paths = typedCall.mock.calls
267
+ .filter(call => call[0] === 'FilesystemFileWrite')
268
+ .map(call => call[2].file.path as string);
269
+ expect(paths.some(path => path.endsWith('.okpkg'))).toBe(false);
270
+ expect(paths).toContain('vol1:/nft/nft-deadbeef-1760000000000.bin');
271
+ });
272
+
246
273
  test('treats a missing NFT directory as empty before the first upload', async () => {
247
274
  const typedCall = jest.fn((request: string, _response: string, params: any) => {
248
275
  if (request === 'FilesystemPathInfoQuery') {
@@ -335,7 +335,97 @@ describe('FirmwareUpdateV4 install polling', () => {
335
335
  expect(method.postProgressMessage).toHaveBeenCalledWith(100, 'installingFirmware');
336
336
  });
337
337
 
338
- test('accepts stable finished BLE status after an actual install disconnect hides in-progress', async () => {
338
+ test.each([
339
+ {
340
+ component: 'boot',
341
+ targets: [{ target_id: 3, path: 'vol0:/bootloader.bin' }],
342
+ },
343
+ {
344
+ component: 'P1',
345
+ targets: [{ target_id: 4, path: 'vol0:/application_p1.bin' }],
346
+ },
347
+ {
348
+ component: 'P2',
349
+ targets: [{ target_id: 5, path: 'vol0:/application_p2.bin' }],
350
+ },
351
+ {
352
+ component: 'coprocessor',
353
+ targets: [{ target_id: 6, path: 'vol0:/coprocessor.bin' }],
354
+ },
355
+ {
356
+ component: 'SE',
357
+ targets: [
358
+ { target_id: 7, path: 'vol0:/se01.bin' },
359
+ { target_id: 8, path: 'vol0:/se02.bin' },
360
+ { target_id: 9, path: 'vol0:/se03.bin' },
361
+ { target_id: 10, path: 'vol0:/se04.bin' },
362
+ ],
363
+ },
364
+ ])(
365
+ 'accepts stable finished BLE status for $component after an actual install disconnect hides in-progress',
366
+ async ({ targets }) => {
367
+ const method = new FirmwareUpdateV4({
368
+ id: 1,
369
+ payload: {
370
+ method: 'firmwareUpdateV4',
371
+ connectId: 'pro2-ble',
372
+ },
373
+ });
374
+ const finishedStatus = {
375
+ type: 'DeviceFirmwareUpdateStatus',
376
+ message: {
377
+ records: targets.map(target => ({
378
+ ...target,
379
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
380
+ })),
381
+ },
382
+ };
383
+ const typedCall = jest
384
+ .fn()
385
+ .mockResolvedValueOnce(finishedStatus)
386
+ .mockImplementationOnce(() => {
387
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
388
+ throw new Error('device was disconnected');
389
+ })
390
+ .mockResolvedValueOnce(finishedStatus)
391
+ .mockImplementationOnce(() => {
392
+ expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
393
+ expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
394
+ return finishedStatus;
395
+ })
396
+ .mockResolvedValueOnce(finishedStatus)
397
+ .mockResolvedValueOnce(finishedStatus)
398
+ .mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
399
+ const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
400
+
401
+ method.device = {
402
+ getCommands: () => ({ typedCall }),
403
+ setCancelableAction: jest.fn(),
404
+ } as unknown as Device;
405
+ method.postProgressMessage = jest.fn();
406
+
407
+ const firmwareUpdate = method as unknown as {
408
+ waitForProtocolV2FirmwareUpdateComplete: (
409
+ value: typeof targets,
410
+ requireCurrentInstallStatus: boolean
411
+ ) => Promise<void>;
412
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
413
+ };
414
+ firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
415
+ (method as any).isBleReconnect = jest.fn(() => true);
416
+
417
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
418
+
419
+ expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
420
+ expect(typedCall).toHaveBeenCalledTimes(6);
421
+ expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
422
+ expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
423
+ expect(method.postProgressMessage).toHaveBeenCalledTimes(2);
424
+ },
425
+ 10_000
426
+ );
427
+
428
+ test('uses a real BLE disconnect while writing the Request as install evidence', async () => {
339
429
  const method = new FirmwareUpdateV4({
340
430
  id: 1,
341
431
  payload: {
@@ -343,49 +433,41 @@ describe('FirmwareUpdateV4 install polling', () => {
343
433
  connectId: 'pro2-ble',
344
434
  },
345
435
  });
346
- const targets = [{ target_id: 6, path: 'vol0:/coprocessor.bin' }];
436
+ const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
347
437
  const finishedStatus = {
348
438
  type: 'DeviceFirmwareUpdateStatus',
349
439
  message: {
350
440
  records: [
351
441
  {
352
- target_id: 6,
442
+ ...targets[0],
353
443
  status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
354
- payload_version: 65_556,
355
- path: 'vol0:/coprocessor.bin',
356
444
  },
357
445
  ],
358
446
  },
359
447
  };
360
448
  const typedCall = jest
361
449
  .fn()
450
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
362
451
  .mockResolvedValueOnce(finishedStatus)
363
- .mockImplementationOnce(() => {
364
- expect(method.postProgressMessage).not.toHaveBeenCalled();
365
- throw new Error('device was disconnected');
366
- })
367
452
  .mockResolvedValueOnce(finishedStatus)
368
- .mockImplementationOnce(() => {
369
- expect(method.postProgressMessage).toHaveBeenCalledTimes(1);
370
- expect(method.postProgressMessage).toHaveBeenCalledWith(1, 'installingFirmware');
371
- return finishedStatus;
372
- })
373
453
  .mockResolvedValueOnce(finishedStatus)
374
454
  .mockResolvedValueOnce(finishedStatus)
375
455
  .mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
456
+ const call = jest.fn().mockRejectedValue(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError));
376
457
  const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
377
458
 
378
- (method as any).params = {
379
- expectedTargetVersions: { coprocessor: '1.0.20' },
380
- };
381
-
382
459
  method.device = {
383
- getCommands: () => ({ typedCall }),
460
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
461
+ createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
462
+ toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-ble' }),
384
463
  setCancelableAction: jest.fn(),
464
+ clearCancelableAction: jest.fn(),
385
465
  } as unknown as Device;
466
+ method.postMessage = jest.fn();
386
467
  method.postProgressMessage = jest.fn();
387
468
 
388
469
  const firmwareUpdate = method as unknown as {
470
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
389
471
  waitForProtocolV2FirmwareUpdateComplete: (
390
472
  value: typeof targets,
391
473
  requireCurrentInstallStatus: boolean
@@ -395,15 +477,234 @@ describe('FirmwareUpdateV4 install polling', () => {
395
477
  firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
396
478
  (method as any).isBleReconnect = jest.fn(() => true);
397
479
 
480
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
398
481
  await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
399
482
 
400
483
  expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
401
- expect(typedCall).toHaveBeenCalledTimes(6);
402
484
  expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
403
485
  expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
404
- expect(method.postProgressMessage).toHaveBeenCalledTimes(2);
486
+ expect(typedCall).toHaveBeenCalledTimes(5);
487
+ }, 10_000);
488
+
489
+ test('does not use a Request response timeout as BLE install evidence', async () => {
490
+ const method = new FirmwareUpdateV4({
491
+ id: 1,
492
+ payload: {
493
+ method: 'firmwareUpdateV4',
494
+ connectId: 'pro2-ble',
495
+ },
496
+ });
497
+ const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
498
+ const finishedStatus = {
499
+ type: 'DeviceFirmwareUpdateStatus',
500
+ message: {
501
+ records: [
502
+ {
503
+ ...targets[0],
504
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
505
+ },
506
+ ],
507
+ },
508
+ };
509
+ const typedCall = jest
510
+ .fn()
511
+ .mockResolvedValueOnce({ type: 'Success', message: {} })
512
+ .mockResolvedValueOnce(finishedStatus)
513
+ .mockResolvedValueOnce(finishedStatus)
514
+ .mockResolvedValueOnce(finishedStatus)
515
+ .mockResolvedValueOnce(finishedStatus)
516
+ .mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
517
+ const call = jest
518
+ .fn()
519
+ .mockRejectedValue(
520
+ ERRORS.TypedError(
521
+ HardwareErrorCode.BleTimeoutError,
522
+ 'device was disconnected after Lowlevel response timeout'
523
+ )
524
+ );
525
+ const reconnectProtocolV2Device = jest.fn().mockResolvedValue(undefined);
526
+
527
+ method.device = {
528
+ getCommands: () => ({ typedCall, call, cancelDevice: jest.fn() }),
529
+ createProtocolV2UiPhaseMetadata: jest.fn().mockReturnValue(undefined),
530
+ toMessageObject: jest.fn().mockReturnValue({ connectId: 'pro2-ble' }),
531
+ setCancelableAction: jest.fn(),
532
+ clearCancelableAction: jest.fn(),
533
+ } as unknown as Device;
534
+ method.postMessage = jest.fn();
535
+ method.postProgressMessage = jest.fn();
536
+
537
+ const firmwareUpdate = method as unknown as {
538
+ protocolV2StartFirmwareUpdate: (params: { targets: typeof targets }) => Promise<void>;
539
+ waitForProtocolV2FirmwareUpdateComplete: (
540
+ value: typeof targets,
541
+ requireCurrentInstallStatus: boolean
542
+ ) => Promise<void>;
543
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
544
+ };
545
+ firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
546
+ (method as any).isBleReconnect = jest.fn(() => true);
547
+
548
+ await firmwareUpdate.protocolV2StartFirmwareUpdate({ targets });
549
+ await expect(
550
+ firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true)
551
+ ).rejects.toMatchObject({
552
+ errorCode: HardwareErrorCode.ActionCancelled,
553
+ });
554
+
555
+ expect(reconnectProtocolV2Device).toHaveBeenCalledWith({ skipProtocolProbe: true });
556
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
405
557
  }, 10_000);
406
558
 
559
+ test('records a real BLE disconnect that occurs during install reconnect', async () => {
560
+ const method = new FirmwareUpdateV4({
561
+ id: 1,
562
+ payload: {
563
+ method: 'firmwareUpdateV4',
564
+ connectId: 'pro2-ble',
565
+ },
566
+ });
567
+ const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
568
+ const finishedStatus = {
569
+ type: 'DeviceFirmwareUpdateStatus',
570
+ message: {
571
+ records: [
572
+ {
573
+ ...targets[0],
574
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
575
+ },
576
+ ],
577
+ },
578
+ };
579
+ const typedCall = jest
580
+ .fn()
581
+ .mockRejectedValueOnce(
582
+ ERRORS.TypedError(
583
+ HardwareErrorCode.BleTimeoutError,
584
+ 'Lowlevel response timeout after 15000ms for DeviceFirmwareUpdateStatusGet'
585
+ )
586
+ )
587
+ .mockResolvedValueOnce(finishedStatus)
588
+ .mockResolvedValueOnce(finishedStatus)
589
+ .mockResolvedValueOnce(finishedStatus)
590
+ .mockResolvedValueOnce(finishedStatus);
591
+ const reconnectProtocolV2Device = jest
592
+ .fn()
593
+ .mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError))
594
+ .mockResolvedValueOnce(undefined);
595
+ const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
596
+ callback: () => void
597
+ ) => {
598
+ callback();
599
+ return 0 as any;
600
+ }) as typeof setTimeout);
601
+
602
+ method.device = {
603
+ getCommands: () => ({ typedCall, cancelDevice: jest.fn() }),
604
+ setCancelableAction: jest.fn(),
605
+ } as unknown as Device;
606
+ method.postProgressMessage = jest.fn();
607
+
608
+ const firmwareUpdate = method as unknown as {
609
+ waitForProtocolV2FirmwareUpdateComplete: (
610
+ value: typeof targets,
611
+ requireCurrentInstallStatus: boolean
612
+ ) => Promise<void>;
613
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
614
+ };
615
+ firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
616
+ (method as any).isBleReconnect = jest.fn(() => true);
617
+
618
+ try {
619
+ await firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true);
620
+ } finally {
621
+ setTimeoutSpy.mockRestore();
622
+ }
623
+
624
+ expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
625
+ expect(method.postProgressMessage).toHaveBeenNthCalledWith(1, 1, 'installingFirmware');
626
+ expect(method.postProgressMessage).toHaveBeenNthCalledWith(2, 100, 'installingFirmware');
627
+ });
628
+
629
+ test('does not use a reconnect response timeout as BLE install evidence', async () => {
630
+ const method = new FirmwareUpdateV4({
631
+ id: 1,
632
+ payload: {
633
+ method: 'firmwareUpdateV4',
634
+ connectId: 'pro2-ble',
635
+ },
636
+ });
637
+ const targets = [{ target_id: 3, path: 'vol0:/bootloader.bin' }];
638
+ const finishedStatus = {
639
+ type: 'DeviceFirmwareUpdateStatus',
640
+ message: {
641
+ records: [
642
+ {
643
+ ...targets[0],
644
+ status: 'FW_MGMT_UPDATER_TASK_STATUS_FINISHED',
645
+ },
646
+ ],
647
+ },
648
+ };
649
+ const typedCall = jest
650
+ .fn()
651
+ .mockRejectedValueOnce(
652
+ ERRORS.TypedError(
653
+ HardwareErrorCode.BleTimeoutError,
654
+ 'Lowlevel response timeout after 15000ms for DeviceFirmwareUpdateStatusGet'
655
+ )
656
+ )
657
+ .mockResolvedValueOnce(finishedStatus)
658
+ .mockResolvedValueOnce(finishedStatus)
659
+ .mockResolvedValueOnce(finishedStatus)
660
+ .mockResolvedValueOnce(finishedStatus)
661
+ .mockRejectedValueOnce(ERRORS.TypedError(HardwareErrorCode.ActionCancelled));
662
+ const reconnectProtocolV2Device = jest
663
+ .fn()
664
+ .mockRejectedValueOnce(
665
+ ERRORS.TypedError(
666
+ HardwareErrorCode.BleTimeoutError,
667
+ 'device was disconnected after Lowlevel response timeout'
668
+ )
669
+ )
670
+ .mockResolvedValueOnce(undefined);
671
+ const setTimeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(((
672
+ callback: () => void
673
+ ) => {
674
+ callback();
675
+ return 0 as any;
676
+ }) as typeof setTimeout);
677
+
678
+ method.device = {
679
+ getCommands: () => ({ typedCall, cancelDevice: jest.fn() }),
680
+ setCancelableAction: jest.fn(),
681
+ } as unknown as Device;
682
+ method.postProgressMessage = jest.fn();
683
+
684
+ const firmwareUpdate = method as unknown as {
685
+ waitForProtocolV2FirmwareUpdateComplete: (
686
+ value: typeof targets,
687
+ requireCurrentInstallStatus: boolean
688
+ ) => Promise<void>;
689
+ reconnectProtocolV2Device: (options: { skipProtocolProbe: boolean }) => Promise<void>;
690
+ };
691
+ firmwareUpdate.reconnectProtocolV2Device = reconnectProtocolV2Device;
692
+ (method as any).isBleReconnect = jest.fn(() => true);
693
+
694
+ try {
695
+ await expect(
696
+ firmwareUpdate.waitForProtocolV2FirmwareUpdateComplete(targets, true)
697
+ ).rejects.toMatchObject({
698
+ errorCode: HardwareErrorCode.ActionCancelled,
699
+ });
700
+ } finally {
701
+ setTimeoutSpy.mockRestore();
702
+ }
703
+
704
+ expect(reconnectProtocolV2Device).toHaveBeenCalledTimes(2);
705
+ expect(method.postProgressMessage).not.toHaveBeenCalled();
706
+ });
707
+
407
708
  test('rejects stable stale finished BLE status after a status response timeout', async () => {
408
709
  const method = new FirmwareUpdateV4({
409
710
  id: 1,
@@ -271,10 +271,12 @@ describe('DeviceUploadWallpaper', () => {
271
271
  payload: {
272
272
  method: 'deviceUploadWallpaper',
273
273
  jpegBase64: createJpegBase64(604, 1024),
274
+ fileName: 'ignored-on-package-firmware.bin',
274
275
  },
275
276
  });
276
277
  const device = stubWallpaperDevice({
277
278
  commands: { typedCall },
279
+ state: { versions: { firmware: '1.0.1' } },
278
280
  getCurrentFirmwareVersionString: jest.fn(() => '1.0.1'),
279
281
  });
280
282
  (method as any).device = device;
@@ -300,6 +302,40 @@ describe('DeviceUploadWallpaper', () => {
300
302
  });
301
303
  });
302
304
 
305
+ test('keeps the legacy wallpaper path on a 1.0.1 prerelease', async () => {
306
+ const typedCall = jest.fn().mockImplementation((request, _response, params) => {
307
+ if (request === 'FilesystemDirMake') return { message: {} };
308
+ if (request === 'FilesystemFileWrite') {
309
+ const file = params.file as { data: Uint8Array; offset: number };
310
+ return { message: { processed_byte: file.offset + file.data.byteLength } };
311
+ }
312
+ if (request === 'DeviceSettingsSet') return { message: {} };
313
+ throw new Error(`Unexpected request: ${request}`);
314
+ });
315
+ const method = new DeviceUploadWallpaper({
316
+ id: 1,
317
+ payload: {
318
+ method: 'deviceUploadWallpaper',
319
+ jpegBase64: createJpegBase64(604, 1024),
320
+ fileName: 'prerelease-wallpaper.bin',
321
+ },
322
+ });
323
+ const device = stubWallpaperDevice({
324
+ commands: { typedCall },
325
+ state: { versions: { firmware: '1.0.1-beta.1' } },
326
+ });
327
+ (method as any).device = device;
328
+
329
+ method.init();
330
+ const result = await method.run();
331
+
332
+ expect(result.path).toBe('vol1:/wallpapers/prerelease-wallpaper.bin');
333
+ const fileWrites = typedCall.mock.calls.filter(call => call[0] === 'FilesystemFileWrite');
334
+ expect(new Set(fileWrites.map(call => call[2].file.path))).toEqual(
335
+ new Set(['vol1:/wallpapers/prerelease-wallpaper.bin'])
336
+ );
337
+ });
338
+
303
339
  test('文件上传失败时不修改 wallpaper_path', async () => {
304
340
  const typedCall = jest.fn().mockImplementation(request => {
305
341
  if (request === 'FilesystemDirMake') return { message: {} };
@@ -5732,6 +5768,7 @@ describe('Protocol V2 firmware update targets', () => {
5732
5768
  );
5733
5769
  expect(typedCall).not.toHaveBeenCalledWith('DeviceFirmwareUpdateRequest', 'Success', {});
5734
5770
  expect((method as any).protocolV2InstallNeedsReconnect).toBe(true);
5771
+ expect((method as any).protocolV2InstallDisconnectObserved).toBe(true);
5735
5772
  });
5736
5773
 
5737
5774
  test.each([
@@ -18,6 +18,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
18
18
  private protocolV2LatestFinalDeviceInfo?;
19
19
  private protocolV2InstallBaselineVersions;
20
20
  private protocolV2InstallNeedsReconnect;
21
+ private protocolV2InstallDisconnectObserved;
21
22
  private protocolV2InstallTerminalSuccessObserved;
22
23
  private protocolV2LastRuntimeProbeFeatures?;
23
24
  private protocolV2LastTransferProgress?;
@@ -1 +1 @@
1
- {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FirmwareUpdateV4.d.ts","sourceRoot":"","sources":["../../src/api/FirmwareUpdateV4.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceUploadNft.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadNft.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAK3C,MAAM,MAAM,qBAAqB,GAAG;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAOF,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,UAAU,CAAC,qBAAqB,CAAC;IAC5E,OAAO,CAAC,MAAM,CAAC,CAAgB;IAE/B,qBAAqB;IAIrB,IAAI;YAgFU,kBAAkB;YAiBlB,qBAAqB;YAwBrB,SAAS;IASjB,GAAG,IAAI,OAAO,CAAC,uBAAuB,CAAC;CA0E9C"}
1
+ {"version":3,"file":"DeviceUploadNft.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadNft.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAK3C,MAAM,MAAM,qBAAqB,GAAG;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IAEjB,SAAS,EAAE,MAAM,CAAC;IAElB,aAAa,EAAE,MAAM,CAAC;IAEtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAOF,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,UAAU,CAAC,qBAAqB,CAAC;IAC5E,OAAO,CAAC,MAAM,CAAC,CAAgB;IAE/B,qBAAqB;IAIrB,IAAI;YAgFU,kBAAkB;YAiBlB,qBAAqB;YAwBrB,SAAS;IASjB,GAAG,IAAI,OAAO,CAAC,uBAAuB,CAAC;CA0E9C"}
@@ -1 +1 @@
1
- {"version":3,"file":"DeviceUploadWallpaper.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadWallpaper.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAO3C,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAMnC,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,wBAAwB,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAqBF,MAAM,CAAC,OAAO,OAAO,qBAAsB,SAAQ,UAAU,CAAC,2BAA2B,CAAC;IACxF,qBAAqB;IAIrB,OAAO,CAAC,OAAO,CAAC,CAA8D;IAE9E,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,IAAI,CAAM;IAElB,IAAI;YA2BU,kBAAkB;YAgBlB,eAAe;YAaf,MAAM;IAsBd,GAAG,IAAI,OAAO,CAAC,6BAA6B,CAAC;CAgCpD"}
1
+ {"version":3,"file":"DeviceUploadWallpaper.d.ts","sourceRoot":"","sources":["../../../src/api/protocol-v2/DeviceUploadWallpaper.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAO3C,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAMnC,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,MAAM,CAAC;IAKnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAK1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,wBAAwB,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAqBF,MAAM,CAAC,OAAO,OAAO,qBAAsB,SAAQ,UAAU,CAAC,2BAA2B,CAAC;IACxF,qBAAqB;IAIrB,OAAO,CAAC,OAAO,CAAC,CAA8D;IAE9E,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,IAAI,CAAM;IAElB,IAAI;YA2BU,kBAAkB;YAgBlB,eAAe;YAaf,MAAM;IAsBd,GAAG,IAAI,OAAO,CAAC,6BAA6B,CAAC;CAgCpD"}
package/dist/index.d.ts CHANGED
@@ -1127,10 +1127,18 @@ declare function encodePro2Wallpaper(options: {
1127
1127
 
1128
1128
  type DeviceUploadWallpaperParams = {
1129
1129
  jpegBase64: string;
1130
+ /**
1131
+ * Legacy firmware uses this name for the uploaded `.bin` file. Firmware
1132
+ * 1.0.1+ always consumes the fixed `wallpaper.okpkg` package path.
1133
+ */
1130
1134
  fileName?: string;
1131
1135
  chunkSize?: number;
1132
1136
  };
1133
1137
  type DeviceUploadWallpaperResponse = {
1138
+ /**
1139
+ * Filesystem path sent to `DeviceSettingsSet`. On firmware 1.0.1+ this is
1140
+ * the temporary package path; firmware extracts and persists wallpaper.bin.
1141
+ */
1134
1142
  path: string;
1135
1143
  size: number;
1136
1144
  colorFormat: Pro2WallpaperColorFormat;
@@ -1149,8 +1157,11 @@ type DeviceUploadNftParams = {
1149
1157
  };
1150
1158
  type DeviceUploadNftResponse = {
1151
1159
  basename: string;
1160
+ /** Final image path after firmware extracts a host-asset package. */
1152
1161
  imagePath: string;
1162
+ /** Final thumbnail path after firmware extracts a host-asset package. */
1153
1163
  thumbnailPath: string;
1164
+ /** Final metadata path after firmware extracts a host-asset package. */
1154
1165
  metadataPath: string;
1155
1166
  totalSize: number;
1156
1167
  nftUpdated: true;
package/dist/index.js CHANGED
@@ -52302,6 +52302,13 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error) => {
52302
52302
  message.includes('message handler not found') ||
52303
52303
  message.includes('unsupported message'));
52304
52304
  };
52305
+ const isProtocolV2InstallResponseTimeout = (error) => {
52306
+ if (!error || typeof error !== 'object')
52307
+ return false;
52308
+ const { code } = error;
52309
+ return (code === 'response-timeout' ||
52310
+ /\bresponse timeout\b/i.test(getProtocolV2UnknownErrorText(error)));
52311
+ };
52305
52312
  const isProtocolV2TerminalInstallStatusError = (error) => {
52306
52313
  var _a;
52307
52314
  return hdShared.isBleStaleBondHardwareError(error) ||
@@ -52429,6 +52436,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
52429
52436
  this.protocolV2CompletedTargetIds = new Set();
52430
52437
  this.protocolV2InstallBaselineVersions = new Map();
52431
52438
  this.protocolV2InstallNeedsReconnect = false;
52439
+ this.protocolV2InstallDisconnectObserved = false;
52432
52440
  this.protocolV2InstallTerminalSuccessObserved = false;
52433
52441
  this.protocolV2LastTransferProgressAt = 0;
52434
52442
  }
@@ -53899,15 +53907,15 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
53899
53907
  return __awaiter(this, void 0, void 0, function* () {
53900
53908
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
53901
53909
  const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
53902
- const isBleCoprocessorInstall = this.isBleReconnect() &&
53903
- expectedTargetIds.has(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR);
53910
+ const isBleInstall = this.isBleReconnect();
53904
53911
  const startTime = Date.now();
53905
53912
  let lastError;
53906
53913
  let shouldReconnect = this.protocolV2InstallNeedsReconnect;
53907
53914
  this.protocolV2InstallNeedsReconnect = false;
53908
53915
  let deviceInfo;
53909
53916
  let bleInstallLinkReady = false;
53910
- let installStatusDisconnectObserved = false;
53917
+ let installStatusDisconnectObserved = isBleInstall && this.protocolV2InstallDisconnectObserved;
53918
+ this.protocolV2InstallDisconnectObserved = false;
53911
53919
  let installReconnectObserved = false;
53912
53920
  let finishedStatusSnapshotKey;
53913
53921
  let finishedStatusSnapshotPolls = 0;
@@ -53915,6 +53923,17 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
53915
53923
  let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
53916
53924
  let currentInstallStatusObserved = false;
53917
53925
  const liveTargetIds = new Set();
53926
+ const recordBleInstallDisconnect = (error) => {
53927
+ if (!isBleInstall ||
53928
+ !isProtocolV2DeviceDisconnectedError(error) ||
53929
+ isProtocolV2InstallResponseTimeout(error)) {
53930
+ return;
53931
+ }
53932
+ installStatusDisconnectObserved = true;
53933
+ installReconnectObserved = false;
53934
+ finishedStatusSnapshotKey = undefined;
53935
+ finishedStatusSnapshotPolls = 0;
53936
+ };
53918
53937
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
53919
53938
  this.throwIfAborted();
53920
53939
  try {
@@ -54101,13 +54120,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
54101
54120
  }
54102
54121
  }
54103
54122
  else {
54104
- if (isBleCoprocessorInstall &&
54105
- isProtocolV2DeviceDisconnectedError(error) &&
54106
- !isProtocolV2ResponseTimeout(error)) {
54107
- installStatusDisconnectObserved = true;
54108
- finishedStatusSnapshotKey = undefined;
54109
- finishedStatusSnapshotPolls = 0;
54110
- }
54123
+ recordBleInstallDisconnect(error);
54111
54124
  shouldReconnect = true;
54112
54125
  deviceInfo = undefined;
54113
54126
  bleInstallLinkReady = false;
@@ -54128,6 +54141,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
54128
54141
  ((_b = error.params) === null || _b === void 0 ? void 0 : _b.firmwareUpdateCode) === 'FirmwareInstallStatusUnavailable') {
54129
54142
  throw error;
54130
54143
  }
54144
+ recordBleInstallDisconnect(error);
54131
54145
  shouldReconnect = true;
54132
54146
  deviceInfo = undefined;
54133
54147
  bleInstallLinkReady = false;
@@ -54377,6 +54391,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
54377
54391
  return __awaiter(this, void 0, void 0, function* () {
54378
54392
  this.protocolV2LastRuntimeProbeFeatures = undefined;
54379
54393
  this.protocolV2InstallNeedsReconnect = false;
54394
+ this.protocolV2InstallDisconnectObserved = false;
54380
54395
  this.protocolV2InstallTerminalSuccessObserved = false;
54381
54396
  const commands = this.device.getCommands();
54382
54397
  yield commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
@@ -54404,6 +54419,7 @@ class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod {
54404
54419
  }
54405
54420
  Log$6.log('[FirmwareUpdateV4] transport released while writing install request; continue status polling');
54406
54421
  this.protocolV2InstallNeedsReconnect = true;
54422
+ this.protocolV2InstallDisconnectObserved = !isProtocolV2InstallResponseTimeout(error);
54407
54423
  }
54408
54424
  });
54409
54425
  }
@@ -54605,6 +54621,7 @@ const CONTAINER_HEADER_MAGIC = 0x50504b4f;
54605
54621
  const CONTAINER_HEADER_VERSION = 1;
54606
54622
  const CONTAINER_RESOURCE_TYPE_MAGIC = 0x43534552;
54607
54623
  const CONTAINER_ED25519_SIGNATURE_ALGORITHM = 0x71717171;
54624
+ const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
54608
54625
  const ARCHIVE_MAGIC = 0x52414b4f;
54609
54626
  const ARCHIVE_VERSION = 1;
54610
54627
  const ARCHIVE_HEADER_SIZE = 42;
@@ -54613,23 +54630,11 @@ const ARCHIVE_ENTRY_NAME_MAX_LENGTH = 255;
54613
54630
  const ARCHIVE_COMPRESS_LZ4_BLOCKED = 1;
54614
54631
  const ARCHIVE_ALIGNMENT = 4;
54615
54632
  const LZ4_BLOCK_SIZE_LOG2 = 12;
54616
- const LZ4_MIN_MATCH = 4;
54617
- const LZ4_LAST_LITERALS = 5;
54618
- const LZ4_MATCH_FIND_LIMIT = 12;
54619
- const LZ4_MAX_OFFSET = 0xffff;
54620
- const LZ4_LENGTH_MASK = 15;
54621
- const LZ4_HASH_LOG = 16;
54622
- const LZ4_HASH_MULTIPLIER = 2654435761;
54623
- const LZ4_SKIP_TRIGGER = 6;
54624
- const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
54625
54633
  function supportsPro2HostAssetPackage(firmwareVersion) {
54626
54634
  return Boolean(firmwareVersion &&
54627
54635
  semver__default["default"].valid(firmwareVersion) &&
54628
54636
  semver__default["default"].gte(firmwareVersion, PRO2_HOST_ASSET_PACKAGE_MIN_VERSION));
54629
54637
  }
54630
- function align(value) {
54631
- return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
54632
- }
54633
54638
  function concatBytes(parts) {
54634
54639
  const output = new Uint8Array(parts.reduce((length, part) => length + part.byteLength, 0));
54635
54640
  let offset = 0;
@@ -54639,6 +54644,14 @@ function concatBytes(parts) {
54639
54644
  }
54640
54645
  return output;
54641
54646
  }
54647
+ const LZ4_MIN_MATCH = 4;
54648
+ const LZ4_LAST_LITERALS = 5;
54649
+ const LZ4_MATCH_FIND_LIMIT = 12;
54650
+ const LZ4_MAX_OFFSET = 0xffff;
54651
+ const LZ4_LENGTH_MASK = 15;
54652
+ const LZ4_HASH_LOG = 16;
54653
+ const LZ4_HASH_MULTIPLIER = 2654435761;
54654
+ const LZ4_SKIP_TRIGGER = 6;
54642
54655
  function writeExtendedLength(output, offset, length) {
54643
54656
  let remaining = length;
54644
54657
  let nextOffset = offset;
@@ -54763,6 +54776,9 @@ function crc32(data) {
54763
54776
  }
54764
54777
  return (value ^ 0xffffffff) >>> 0;
54765
54778
  }
54779
+ function align(value) {
54780
+ return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
54781
+ }
54766
54782
  function buildArchive(entries) {
54767
54783
  const textEncoder = new TextEncoder();
54768
54784
  let dataOffset = ARCHIVE_HEADER_SIZE + entries.length * ARCHIVE_ENTRY_SIZE;
@@ -54923,14 +54939,14 @@ class DeviceUploadWallpaper extends BaseMethod {
54923
54939
  });
54924
54940
  }
54925
54941
  run() {
54926
- var _a;
54942
+ var _a, _b, _c;
54927
54943
  return __awaiter(this, void 0, void 0, function* () {
54928
54944
  const { encoded } = this;
54929
54945
  if (!encoded)
54930
54946
  throw invalidParameter$1('Wallpaper data has not been initialized.');
54931
54947
  yield this.assertCapabilities();
54932
54948
  yield this.ensureDirectory();
54933
- const useHostAssetPackage = supportsPro2HostAssetPackage(this.device.getCurrentFirmwareVersionString());
54949
+ const useHostAssetPackage = supportsPro2HostAssetPackage((_b = (_a = this.device.state) === null || _a === void 0 ? void 0 : _a.versions.firmware) !== null && _b !== void 0 ? _b : undefined);
54934
54950
  const data = useHostAssetPackage
54935
54951
  ? buildPro2HostAssetPackage([{ name: WALLPAPER_PACKAGE_ENTRY, data: encoded.data }])
54936
54952
  : encoded.data;
@@ -54952,7 +54968,7 @@ class DeviceUploadWallpaper extends BaseMethod {
54952
54968
  path: this.path,
54953
54969
  size: data.byteLength,
54954
54970
  colorFormat: encoded.colorFormat,
54955
- message: (_a = response.message) === null || _a === void 0 ? void 0 : _a.message,
54971
+ message: (_c = response.message) === null || _c === void 0 ? void 0 : _c.message,
54956
54972
  };
54957
54973
  });
54958
54974
  }
@@ -55062,12 +55078,12 @@ class DeviceUploadNft extends BaseMethod {
55062
55078
  });
55063
55079
  }
55064
55080
  run() {
55065
- var _a;
55081
+ var _a, _b, _c;
55066
55082
  return __awaiter(this, void 0, void 0, function* () {
55067
55083
  const { bundle } = this;
55068
55084
  if (!bundle)
55069
55085
  throw invalidParameter$1('NFT data has not been initialized.');
55070
- const useHostAssetPackage = supportsPro2HostAssetPackage(this.device.getCurrentFirmwareVersionString());
55086
+ const useHostAssetPackage = supportsPro2HostAssetPackage((_b = (_a = this.device.state) === null || _a === void 0 ? void 0 : _a.versions.firmware) !== null && _b !== void 0 ? _b : undefined);
55071
55087
  yield this.assertCapabilities(useHostAssetPackage);
55072
55088
  this.throwIfAborted();
55073
55089
  if (!useHostAssetPackage)
@@ -55122,7 +55138,7 @@ class DeviceUploadNft extends BaseMethod {
55122
55138
  metadataPath: extractedFiles[2].path,
55123
55139
  totalSize,
55124
55140
  nftUpdated: true,
55125
- message: (_a = response.message) === null || _a === void 0 ? void 0 : _a.message,
55141
+ message: (_c = response.message) === null || _c === void 0 ? void 0 : _c.message,
55126
55142
  };
55127
55143
  });
55128
55144
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pro2HostAssetPackage.d.ts","sourceRoot":"","sources":["../../src/utils/pro2HostAssetPackage.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,mCAAmC,UAAU,CAAC;AA4B3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAQF,wBAAgB,4BAA4B,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMzF;AA6ND,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,yBAAyB,EAAE,GAAG,UAAU,CAuB1F"}
1
+ {"version":3,"file":"pro2HostAssetPackage.d.ts","sourceRoot":"","sources":["../../src/utils/pro2HostAssetPackage.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,mCAAmC,UAAU,CAAC;AAwB3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAQF,wBAAgB,4BAA4B,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMzF;AA2QD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,yBAAyB,EAAE,GAAG,UAAU,CA4B1F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.2-alpha.0",
3
+ "version": "1.2.2-alpha.1",
4
4
  "description": "Core processes and APIs for communicating with OneKey hardware devices.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -25,8 +25,8 @@
25
25
  "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-shared": "1.2.2-alpha.0",
29
- "@onekeyfe/hd-transport": "1.2.2-alpha.0",
28
+ "@onekeyfe/hd-shared": "1.2.2-alpha.1",
29
+ "@onekeyfe/hd-transport": "1.2.2-alpha.1",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "buffer": "^6.0.3",
@@ -46,5 +46,5 @@
46
46
  "@types/w3c-web-usb": "^1.0.10",
47
47
  "@types/web-bluetooth": "^0.0.21"
48
48
  },
49
- "gitHead": "9c18d7073728621179d58252e07fb3385b865226"
49
+ "gitHead": "66f45610d36e06a46ac82cd49a4b0e1a128f19d4"
50
50
  }
@@ -374,6 +374,15 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
374
374
  );
375
375
  };
376
376
 
377
+ const isProtocolV2InstallResponseTimeout = (error: unknown) => {
378
+ if (!error || typeof error !== 'object') return false;
379
+ const { code } = error as { code?: number | string };
380
+ return (
381
+ code === 'response-timeout' ||
382
+ /\bresponse timeout\b/i.test(getProtocolV2UnknownErrorText(error))
383
+ );
384
+ };
385
+
377
386
  const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
378
387
  isBleStaleBondHardwareError(error) ||
379
388
  (error instanceof HardwareError &&
@@ -565,6 +574,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
565
574
 
566
575
  private protocolV2InstallNeedsReconnect = false;
567
576
 
577
+ private protocolV2InstallDisconnectObserved = false;
578
+
568
579
  private protocolV2InstallTerminalSuccessObserved = false;
569
580
 
570
581
  private protocolV2LastRuntimeProbeFeatures?: Features;
@@ -2517,9 +2528,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2517
2528
  ) {
2518
2529
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
2519
2530
  const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
2520
- const isBleCoprocessorInstall =
2521
- this.isBleReconnect() &&
2522
- expectedTargetIds.has(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR);
2531
+ const isBleInstall = this.isBleReconnect();
2523
2532
  const startTime = Date.now();
2524
2533
  let lastError: unknown;
2525
2534
  // The loader may release either USB or BLE as installation starts. Recovery reconnects
@@ -2528,7 +2537,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2528
2537
  this.protocolV2InstallNeedsReconnect = false;
2529
2538
  let deviceInfo: ProtocolV2DeviceInfo | undefined;
2530
2539
  let bleInstallLinkReady = false;
2531
- let installStatusDisconnectObserved = false;
2540
+ let installStatusDisconnectObserved = isBleInstall && this.protocolV2InstallDisconnectObserved;
2541
+ this.protocolV2InstallDisconnectObserved = false;
2532
2542
  let installReconnectObserved = false;
2533
2543
  let finishedStatusSnapshotKey: string | undefined;
2534
2544
  let finishedStatusSnapshotPolls = 0;
@@ -2536,6 +2546,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2536
2546
  let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
2537
2547
  let currentInstallStatusObserved = false;
2538
2548
  const liveTargetIds = new Set<number>();
2549
+ const recordBleInstallDisconnect = (error: unknown) => {
2550
+ if (
2551
+ !isBleInstall ||
2552
+ !isProtocolV2DeviceDisconnectedError(error) ||
2553
+ isProtocolV2InstallResponseTimeout(error)
2554
+ ) {
2555
+ return;
2556
+ }
2557
+ installStatusDisconnectObserved = true;
2558
+ installReconnectObserved = false;
2559
+ finishedStatusSnapshotKey = undefined;
2560
+ finishedStatusSnapshotPolls = 0;
2561
+ };
2539
2562
 
2540
2563
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
2541
2564
  // A transport release caused by an explicit workflow cancellation must not
@@ -2787,15 +2810,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2787
2810
  );
2788
2811
  }
2789
2812
  } else {
2790
- if (
2791
- isBleCoprocessorInstall &&
2792
- isProtocolV2DeviceDisconnectedError(error) &&
2793
- !isProtocolV2ResponseTimeout(error)
2794
- ) {
2795
- installStatusDisconnectObserved = true;
2796
- finishedStatusSnapshotKey = undefined;
2797
- finishedStatusSnapshotPolls = 0;
2798
- }
2813
+ recordBleInstallDisconnect(error);
2799
2814
  shouldReconnect = true;
2800
2815
  deviceInfo = undefined;
2801
2816
  bleInstallLinkReady = false;
@@ -2820,6 +2835,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2820
2835
  ) {
2821
2836
  throw error;
2822
2837
  }
2838
+ recordBleInstallDisconnect(error);
2823
2839
  shouldReconnect = true;
2824
2840
  deviceInfo = undefined;
2825
2841
  bleInstallLinkReady = false;
@@ -3154,6 +3170,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3154
3170
  }) {
3155
3171
  this.protocolV2LastRuntimeProbeFeatures = undefined;
3156
3172
  this.protocolV2InstallNeedsReconnect = false;
3173
+ this.protocolV2InstallDisconnectObserved = false;
3157
3174
  this.protocolV2InstallTerminalSuccessObserved = false;
3158
3175
  const commands = this.device.getCommands();
3159
3176
  await commands.typedCall('DeviceFirmwareUpdateStage', 'Success', { targets });
@@ -3196,6 +3213,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3196
3213
  '[FirmwareUpdateV4] transport released while writing install request; continue status polling'
3197
3214
  );
3198
3215
  this.protocolV2InstallNeedsReconnect = true;
3216
+ this.protocolV2InstallDisconnectObserved = !isProtocolV2InstallResponseTimeout(error);
3199
3217
  }
3200
3218
  }
3201
3219
 
@@ -42,8 +42,11 @@ export type DeviceUploadNftParams = {
42
42
 
43
43
  export type DeviceUploadNftResponse = {
44
44
  basename: string;
45
+ /** Final image path after firmware extracts a host-asset package. */
45
46
  imagePath: string;
47
+ /** Final thumbnail path after firmware extracts a host-asset package. */
46
48
  thumbnailPath: string;
49
+ /** Final metadata path after firmware extracts a host-asset package. */
47
50
  metadataPath: string;
48
51
  totalSize: number;
49
52
  nftUpdated: true;
@@ -197,7 +200,7 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
197
200
  if (!bundle) throw invalidParameter('NFT data has not been initialized.');
198
201
 
199
202
  const useHostAssetPackage = supportsPro2HostAssetPackage(
200
- this.device.getCurrentFirmwareVersionString()
203
+ this.device.state?.versions.firmware ?? undefined
201
204
  );
202
205
  await this.assertCapabilities(useHostAssetPackage);
203
206
  this.throwIfAborted();
@@ -23,11 +23,19 @@ import {
23
23
 
24
24
  export type DeviceUploadWallpaperParams = {
25
25
  jpegBase64: string;
26
+ /**
27
+ * Legacy firmware uses this name for the uploaded `.bin` file. Firmware
28
+ * 1.0.1+ always consumes the fixed `wallpaper.okpkg` package path.
29
+ */
26
30
  fileName?: string;
27
31
  chunkSize?: number;
28
32
  };
29
33
 
30
34
  export type DeviceUploadWallpaperResponse = {
35
+ /**
36
+ * Filesystem path sent to `DeviceSettingsSet`. On firmware 1.0.1+ this is
37
+ * the temporary package path; firmware extracts and persists wallpaper.bin.
38
+ */
31
39
  path: string;
32
40
  size: number;
33
41
  colorFormat: Pro2WallpaperColorFormat;
@@ -150,7 +158,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
150
158
  await this.assertCapabilities();
151
159
  await this.ensureDirectory();
152
160
  const useHostAssetPackage = supportsPro2HostAssetPackage(
153
- this.device.getCurrentFirmwareVersionString()
161
+ this.device.state?.versions.firmware ?? undefined
154
162
  );
155
163
  const data = useHostAssetPackage
156
164
  ? buildPro2HostAssetPackage([{ name: WALLPAPER_PACKAGE_ENTRY, data: encoded.data }])
@@ -1,30 +1,24 @@
1
1
  import { sha3_512 } from '@noble/hashes/sha3';
2
2
  import semver from 'semver';
3
3
 
4
- // The raw LZ4 block encoder below is adapted from lz4-lite 1.1.2.
5
- //
6
- // MIT License
7
- // Copyright (c) 2026 Alexander Vukov
8
- // Permission is hereby granted, free of charge, to any person obtaining a copy
9
- // of this software and associated documentation files (the "Software"), to deal
10
- // in the Software without restriction, including without limitation the rights
11
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- // copies of the Software, and to permit persons to whom the Software is
13
- // furnished to do so, subject to the following conditions:
14
- // The above copyright notice and this permission notice shall be included in all
15
- // copies or substantial portions of the Software.
16
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- // SOFTWARE.
4
+ /**
5
+ * Builds the unsigned host-asset package consumed by Pro2 firmware:
6
+ *
7
+ * OKPP RESOURCE container
8
+ * └── OKAR archive
9
+ * └── independently compressed raw LZ4 blocks
10
+ *
11
+ * OKPP and OKAR are OneKey formats. Their constants mirror firmware-pro2's
12
+ * payload_package headers; the LZ4 bytes inside each block follow the standard
13
+ * raw block format and deliberately do not use an LZ4 frame or size prefix.
14
+ */
23
15
 
24
16
  /* eslint-disable no-bitwise */
25
17
 
26
18
  export const PRO2_HOST_ASSET_PACKAGE_MIN_VERSION = '1.0.1';
27
19
 
20
+ // OKPP container layout. The fixed header has seven empty signature slots even
21
+ // for a host-generated unsigned package.
28
22
  const CONTAINER_HEADER_SIZE = 0x5f90;
29
23
  const CONTAINER_HEADER_HASH_INPUT_LENGTH = 0x240;
30
24
  const CONTAINER_HASH_SECTION_OFFSET = 0x200;
@@ -33,6 +27,9 @@ const CONTAINER_HEADER_MAGIC = 0x50504b4f;
33
27
  const CONTAINER_HEADER_VERSION = 1;
34
28
  const CONTAINER_RESOURCE_TYPE_MAGIC = 0x43534552;
35
29
  const CONTAINER_ED25519_SIGNATURE_ALGORITHM = 0x71717171;
30
+ const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
31
+
32
+ // OKAR archive layout.
36
33
  const ARCHIVE_MAGIC = 0x52414b4f;
37
34
  const ARCHIVE_VERSION = 1;
38
35
  const ARCHIVE_HEADER_SIZE = 42;
@@ -41,15 +38,6 @@ const ARCHIVE_ENTRY_NAME_MAX_LENGTH = 255;
41
38
  const ARCHIVE_COMPRESS_LZ4_BLOCKED = 1;
42
39
  const ARCHIVE_ALIGNMENT = 4;
43
40
  const LZ4_BLOCK_SIZE_LOG2 = 12;
44
- const LZ4_MIN_MATCH = 4;
45
- const LZ4_LAST_LITERALS = 5;
46
- const LZ4_MATCH_FIND_LIMIT = 12;
47
- const LZ4_MAX_OFFSET = 0xffff;
48
- const LZ4_LENGTH_MASK = 15;
49
- const LZ4_HASH_LOG = 16;
50
- const LZ4_HASH_MULTIPLIER = 2654435761;
51
- const LZ4_SKIP_TRIGGER = 6;
52
- const HOST_ASSET_PACKAGE_MAX_SIZE = 4 * 1024 * 1024;
53
41
 
54
42
  export type Pro2HostAssetPackageEntry = {
55
43
  name: string;
@@ -70,10 +58,6 @@ export function supportsPro2HostAssetPackage(firmwareVersion: string | undefined
70
58
  );
71
59
  }
72
60
 
73
- function align(value: number): number {
74
- return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
75
- }
76
-
77
61
  function concatBytes(parts: Uint8Array[]): Uint8Array {
78
62
  const output = new Uint8Array(parts.reduce((length, part) => length + part.byteLength, 0));
79
63
  let offset = 0;
@@ -84,6 +68,41 @@ function concatBytes(parts: Uint8Array[]): Uint8Array {
84
68
  return output;
85
69
  }
86
70
 
71
+ // Raw LZ4 block encoder
72
+ // ---------------------
73
+ //
74
+ // This encoder is adapted from lz4-lite 1.1.2 and intentionally kept local so
75
+ // every SDK runtime uses the same dependency-free implementation. Keep this
76
+ // section isolated from the OneKey package writer below. Firmware requires raw
77
+ // blocks here; replacing it with an LZ4 frame encoder is not compatible.
78
+ //
79
+ // MIT License
80
+ // Copyright (c) 2026 Alexander Vukov
81
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
82
+ // of this software and associated documentation files (the "Software"), to deal
83
+ // in the Software without restriction, including without limitation the rights
84
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
85
+ // copies of the Software, and to permit persons to whom the Software is
86
+ // furnished to do so, subject to the following conditions:
87
+ // The above copyright notice and this permission notice shall be included in all
88
+ // copies or substantial portions of the Software.
89
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
90
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
92
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
94
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
95
+ // SOFTWARE.
96
+
97
+ const LZ4_MIN_MATCH = 4;
98
+ const LZ4_LAST_LITERALS = 5;
99
+ const LZ4_MATCH_FIND_LIMIT = 12;
100
+ const LZ4_MAX_OFFSET = 0xffff;
101
+ const LZ4_LENGTH_MASK = 15;
102
+ const LZ4_HASH_LOG = 16;
103
+ const LZ4_HASH_MULTIPLIER = 2654435761;
104
+ const LZ4_SKIP_TRIGGER = 6;
105
+
87
106
  function writeExtendedLength(output: Uint8Array, offset: number, length: number): number {
88
107
  let remaining = length;
89
108
  let nextOffset = offset;
@@ -210,6 +229,11 @@ function compressRawLz4Block(input: Uint8Array, hashTable: Uint32Array): Uint8Ar
210
229
  return output.slice(0, outputOffset);
211
230
  }
212
231
 
232
+ // OneKey LZ4-blocked wrapper
233
+ // --------------------------
234
+ // The archive stores an 8-byte descriptor, one compressed-size value per
235
+ // block, then the concatenated raw blocks. Blocks are independent so firmware
236
+ // can validate and decompress them with bounded memory.
213
237
  function encodeLz4Blocked(data: Uint8Array): Uint8Array {
214
238
  const blockSize = 1 << LZ4_BLOCK_SIZE_LOG2;
215
239
  const blockCount = Math.ceil(data.byteLength / blockSize);
@@ -231,6 +255,8 @@ function encodeLz4Blocked(data: Uint8Array): Uint8Array {
231
255
  return concatBytes([header, ...blocks]);
232
256
  }
233
257
 
258
+ // OKAR integrity helpers
259
+ // ----------------------
234
260
  const CRC32_TABLE = (() => {
235
261
  const table = new Uint32Array(256);
236
262
  for (let index = 0; index < table.length; index += 1) {
@@ -251,6 +277,12 @@ function crc32(data: Uint8Array): number {
251
277
  return (value ^ 0xffffffff) >>> 0;
252
278
  }
253
279
 
280
+ function align(value: number): number {
281
+ return (value + ARCHIVE_ALIGNMENT - 1) & ~(ARCHIVE_ALIGNMENT - 1);
282
+ }
283
+
284
+ // OKAR archive writer
285
+ // -------------------
254
286
  function buildArchive(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
255
287
  const textEncoder = new TextEncoder();
256
288
  let dataOffset = ARCHIVE_HEADER_SIZE + entries.length * ARCHIVE_ENTRY_SIZE;
@@ -289,6 +321,8 @@ function buildArchive(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
289
321
  return archive;
290
322
  }
291
323
 
324
+ // OKPP container writer
325
+ // ---------------------
292
326
  export function buildPro2HostAssetPackage(entries: Pro2HostAssetPackageEntry[]): Uint8Array {
293
327
  if (entries.length === 0 || new Set(entries.map(entry => entry.name)).size !== entries.length) {
294
328
  throw new Error('Pro2 host asset package entries must have unique, non-empty names.');
@@ -303,6 +337,11 @@ export function buildPro2HostAssetPackage(entries: Pro2HostAssetPackageEntry[]):
303
337
  view.setUint32(0x0c, CONTAINER_HEADER_SIZE, true);
304
338
  view.setUint32(0x10, 1, true);
305
339
  view.setUint32(0x14, payload.byteLength, true);
340
+
341
+ // Host asset packages are unsigned, but firmware still requires a valid
342
+ // payload hash, header hash, and the Ed25519 algorithm discriminator. The
343
+ // zero-initialized header intentionally leaves sig_used_count and every
344
+ // signature slot empty.
306
345
  header.set(sha3_512(payload), CONTAINER_HASH_SECTION_OFFSET);
307
346
  header.set(sha3_512(header.subarray(0, CONTAINER_HEADER_HASH_INPUT_LENGTH)), 0x240);
308
347
  view.setUint32(CONTAINER_SIGNATURE_ALGORITHM_OFFSET, CONTAINER_ED25519_SIGNATURE_ALGORITHM, true);