@onekeyfe/hd-core 1.2.0-alpha.40 → 1.2.0-alpha.42

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.
@@ -2,7 +2,6 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
2
2
  import {
3
3
  DeviceRebootType,
4
4
  PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
5
- PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
6
5
  PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
7
6
  } from '@onekeyfe/hd-transport';
8
7
  import { sha256 } from '@noble/hashes/sha256';
@@ -27,6 +26,12 @@ import {
27
26
  ProtocolV2FirmwareTargetType,
28
27
  } from '../protocols/protocol-v2';
29
28
  import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
29
+ import {
30
+ PROTOCOL_V2_RESOURCE_DEVICE_PATHS,
31
+ buildProtocolV2ResourceUpdatePlan,
32
+ isProtocolV2ResourceFileValid,
33
+ requestProtocolV2ResourceInventory,
34
+ } from '../protocols/protocol-v2/resources';
30
35
  import {
31
36
  getProtocolV2UnknownErrorText,
32
37
  isProtocolV2DeviceDisconnectedError,
@@ -41,6 +46,7 @@ import type {
41
46
  Features,
42
47
  IFirmwareReleaseInfo,
43
48
  IProtocolV2FirmwareComponent,
49
+ IProtocolV2Resource,
44
50
  IVersionArray,
45
51
  } from '../types';
46
52
 
@@ -120,11 +126,6 @@ type ProtocolV2ResourceBundleBinary = {
120
126
  name: string;
121
127
  binary: ArrayBuffer;
122
128
  devicePath: string;
123
- /** Download URL for remote-config mode; omitted in manual mode. */
124
- url?: string;
125
- version?: IVersionArray;
126
- payloadHash?: string;
127
- headerHash?: string;
128
129
  };
129
130
 
130
131
  type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
@@ -276,18 +277,6 @@ const normalizeProtocolV2TargetStatus = (
276
277
 
277
278
  const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
278
279
 
279
- const versionArrayToNumber = (version?: IVersionArray) => {
280
- if (!version) return undefined;
281
- return version[0] * 0x10000 + version[1] * 0x100 + version[2];
282
- };
283
-
284
- const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
285
- const currentNumber = versionArrayToNumber(current);
286
- const targetNumber = versionArrayToNumber(target);
287
- if (currentNumber === undefined || targetNumber === undefined) return undefined;
288
- return currentNumber - targetNumber;
289
- };
290
-
291
280
  const bytesToHex = (bytes: Uint8Array) =>
292
281
  Array.from(bytes)
293
282
  .map(byte => byte.toString(16).padStart(2, '0'))
@@ -468,16 +457,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
468
457
  };
469
458
  }
470
459
 
471
- private getProtocolV2FirmwareChunkSize(direction: 'read' | 'write' = 'write') {
460
+ private getProtocolV2FirmwareChunkSize() {
472
461
  const payloadChunkSize = Number(this.params?.chunkSize);
473
462
  const env = DataManager.getSettings('env');
474
463
  const isBle = this.params?.platform === 'native' || (env && DataManager.isBleConnect(env));
475
464
  let maxChunkSize = PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE;
476
465
  if (isBle) {
477
- maxChunkSize =
478
- direction === 'read'
479
- ? PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE
480
- : PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
466
+ maxChunkSize = PROTOCOL_V2_BLE_FILE_CHUNK_SIZE;
481
467
  }
482
468
  if (!Number.isFinite(payloadChunkSize) || payloadChunkSize <= 0) {
483
469
  return maxChunkSize;
@@ -498,6 +484,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
498
484
  const deviceFeatures = await this.getProtocolV2DeviceFeatures();
499
485
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
500
486
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
487
+ const resourceRecoveryMode = Boolean(
488
+ this.isProtocolV2BootloaderMode() || this.isProtocolV2RomloaderMode()
489
+ );
501
490
 
502
491
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
503
492
  let bootloaderBinary: ArrayBuffer | null = null;
@@ -507,7 +496,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
507
496
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
508
497
  fwBinaryMap = this.collectExplicitTargetBinaries();
509
498
  bootloaderBinary = this.prepareBootloaderBinary();
510
- if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
499
+ const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
500
+ const needsRemoteResources =
501
+ !this.params.resourceBundleFiles?.length &&
502
+ !!this.params.targetsToUpdate?.includes('resource');
503
+ if (needsRemoteFirmware || needsRemoteResources) {
504
+ // Remote updates must use a freshly fetched config before any reboot or file write.
505
+ await DataManager.forceReloadData();
506
+ }
507
+ if (needsRemoteFirmware) {
511
508
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
512
509
  firmwareType,
513
510
  deviceFeatures
@@ -516,16 +513,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
516
513
  fwBinaryMap = remoteBinaries.fwBinaryMap;
517
514
  installItems = remoteBinaries.installItems;
518
515
  }
519
- resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
520
- if (resourceBundles?.length) {
521
- resourceBundles = await this.prefetchProtocolV2ResourceBundles(resourceBundles);
522
- }
516
+ resourceBundles = await this.prepareProtocolV2ResourceBundles(resourceRecoveryMode);
523
517
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
524
518
  } catch (err) {
525
519
  throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
526
520
  }
527
521
 
528
522
  if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
523
+ if (resourceBundles !== undefined) {
524
+ this.postTipMessage(FirmwareUpdateTipMessage.FirmwareUpdateCompleted);
525
+ return this.getProtocolV2VersionResult(deviceFeatures);
526
+ }
529
527
  throw ERRORS.TypedError(
530
528
  HardwareErrorCode.FirmwareUpdateDownloadFailed,
531
529
  'No firmware to update'
@@ -662,57 +660,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
662
660
  return target;
663
661
  }
664
662
 
665
- private getProtocolV2ResourceFilePath(path: string) {
666
- if (path.startsWith('vol')) return path;
667
- if (path.startsWith('/')) return `vol0:${path}`;
668
- return `vol0:/${path}`;
669
- }
670
-
671
- private async readProtocolV2DeviceFileHeader(path: string) {
672
- const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
673
- const filePath = this.getProtocolV2ResourceFilePath(path);
674
- const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
675
- path: filePath,
676
- });
677
- const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
678
- if (
679
- !pathInfoRes.message?.exist ||
680
- pathInfoRes.message?.directory ||
681
- fileSize === undefined ||
682
- fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
683
- ) {
684
- return null;
685
- }
686
-
687
- const chunkSize = this.getProtocolV2FirmwareChunkSize('read');
688
- const chunks: Uint8Array[] = [];
689
- let offset = 0;
690
- while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
691
- const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
692
- const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
693
- file: {
694
- path: filePath,
695
- offset,
696
- total_size: 0,
697
- },
698
- chunk_len: readLen,
699
- ui_percentage: undefined,
700
- });
701
- const data = toProtocolV2Bytes(res.message?.data);
702
- if (data.byteLength === 0) return null;
703
- chunks.push(data);
704
- offset += data.byteLength;
705
- }
706
-
707
- const headerBytes = new Uint8Array(offset);
708
- let cursor = 0;
709
- chunks.forEach(chunk => {
710
- headerBytes.set(chunk, cursor);
711
- cursor += chunk.byteLength;
712
- });
713
- return parseProtocolV2OkppHeader(headerBytes);
714
- }
715
-
716
663
  private async downloadRemoteProtocolV2Component(
717
664
  key: string,
718
665
  component: IProtocolV2FirmwareComponent
@@ -801,23 +748,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
801
748
  };
802
749
  }
803
750
 
804
- // ============================================================
805
- // Incremental RESC bundle sync through FileRead and FilesystemFileWrite
806
- // ============================================================
807
-
808
- /**
809
- * Prepare the RESC bundle list.
810
- *
811
- * Two modes, matching FirmwareUpdateV3 binary-versus-version behavior:
812
- * - resourceBundleFiles supplied: use binaries directly without version checks.
813
- * - Otherwise load release.resourceBundles from remote config.json; the binaries
814
- * are prefetched before reboot and synchronization skips matching device headers.
815
- */
816
- private prepareProtocolV2ResourceBundles(
817
- firmwareType: EFirmwareType,
818
- features: Features
819
- ): ProtocolV2ResourceBundleBinary[] | undefined {
820
- // Manual binaries are installed directly without comparison.
751
+ private async prepareProtocolV2ResourceBundles(
752
+ recoveryMode: boolean
753
+ ): Promise<ProtocolV2ResourceBundleBinary[] | undefined> {
821
754
  if (this.params.resourceBundleFiles?.length) {
822
755
  return this.params.resourceBundleFiles.map((file, index) => {
823
756
  const devicePath = validateProtocolV2FilesystemPath(
@@ -836,70 +769,51 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
836
769
  return undefined;
837
770
  }
838
771
 
839
- // Remote-config bundles are downloaded and compared later.
840
- const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
841
- if (!release?.resourceBundles?.length) return undefined;
842
-
843
- return release.resourceBundles.map((bundle, index) => ({
844
- name: bundle.name,
845
- binary: new ArrayBuffer(0),
846
- devicePath: validateProtocolV2FilesystemPath(
847
- bundle.devicePath,
848
- `resourceBundles[${index}].devicePath`
849
- ),
850
- url: bundle.url,
851
- version: bundle.version,
852
- payloadHash: bundle.payloadHash,
853
- headerHash: bundle.headerHash,
854
- })) as ProtocolV2ResourceBundleBinary[];
772
+ const resources = DataManager.getProtocolV2Resources();
773
+ if (!resources?.length) {
774
+ throw new Error('Missing Pro2 stable resource configuration');
775
+ }
776
+
777
+ const inventory = recoveryMode
778
+ ? undefined
779
+ : await requestProtocolV2ResourceInventory({
780
+ commands: this.device.getCommands(),
781
+ timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
782
+ });
783
+ const plan = buildProtocolV2ResourceUpdatePlan({
784
+ resources,
785
+ inventory,
786
+ mode: recoveryMode ? 'bootloader-recovery' : 'application',
787
+ forced: this.params.forcedUpdateRes,
788
+ });
789
+ Log.log(
790
+ `[FirmwareUpdateV4] Pro2 resource plan mode=${
791
+ recoveryMode ? 'bootloader-recovery' : 'application'
792
+ } status=${plan.status} count=${plan.resources.length}`
793
+ );
794
+
795
+ const bundles: ProtocolV2ResourceBundleBinary[] = [];
796
+ for (const resource of plan.resources) {
797
+ bundles.push(await this.downloadProtocolV2Resource(resource));
798
+ }
799
+ return bundles;
855
800
  }
856
801
 
857
- private async prefetchProtocolV2ResourceBundles(
858
- bundles: ProtocolV2ResourceBundleBinary[]
859
- ): Promise<ProtocolV2ResourceBundleBinary[]> {
860
- const prefetched: ProtocolV2ResourceBundleBinary[] = [];
861
- for (const bundle of bundles) {
862
- let { binary } = bundle;
863
- let downloadedFromRemote = false;
864
- if (binary.byteLength === 0) {
865
- if (!bundle.url) {
866
- throw new Error(`Missing Protocol V2 RESC bundle binary: ${bundle.name}`);
867
- }
868
- Log.log(`[FirmwareUpdateV4] downloading remote RESC bundle ${bundle.name}`);
869
- ({ binary } = await getSysResourceBinary(bundle.url));
870
- downloadedFromRemote = true;
871
- }
872
- if (binary.byteLength === 0) {
873
- throw new Error(`Protocol V2 RESC bundle is empty: ${bundle.name}`);
874
- }
875
- if (downloadedFromRemote) {
876
- const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
877
- if (!header || header.type !== 'RESC') {
878
- throw new Error(`Invalid Protocol V2 RESC bundle header: ${bundle.name}`);
879
- }
880
- if (bundle.version && compareProtocolV2Versions(header.version, bundle.version) !== 0) {
881
- throw new Error(`Protocol V2 RESC bundle version mismatch: ${bundle.name}`);
882
- }
883
- const expectedPayloadHash = normalizeProtocolV2Hex(bundle.payloadHash);
884
- if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) {
885
- throw new Error(`Protocol V2 RESC bundle payload hash mismatch: ${bundle.name}`);
886
- }
887
- const expectedHeaderHash = normalizeProtocolV2Hex(bundle.headerHash);
888
- if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) {
889
- throw new Error(`Protocol V2 RESC bundle header hash mismatch: ${bundle.name}`);
890
- }
891
- }
892
- prefetched.push({ ...bundle, binary });
802
+ private async downloadProtocolV2Resource(
803
+ resource: IProtocolV2Resource
804
+ ): Promise<ProtocolV2ResourceBundleBinary> {
805
+ Log.log(`[FirmwareUpdateV4] downloading Pro2 resource ${resource.type}`);
806
+ const { binary } = await getSysResourceBinary(resource.url);
807
+ if (!isProtocolV2ResourceFileValid(binary, resource)) {
808
+ throw new Error(`Pro2 resource file verification failed: ${resource.type}`);
893
809
  }
894
- return prefetched;
810
+ return {
811
+ name: `${resource.type}.okpkg`,
812
+ binary,
813
+ devicePath: PROTOCOL_V2_RESOURCE_DEVICE_PATHS[resource.type],
814
+ };
895
815
  }
896
816
 
897
- /**
898
- * Synchronize RESC bundles to the device.
899
- *
900
- * Manual mode writes supplied binaries directly.
901
- * Remote-config mode uses prefetched binaries and skips matching bundles after FileRead.
902
- */
903
817
  private async syncProtocolV2ResourceBundles(
904
818
  bundles: ProtocolV2ResourceBundleBinary[],
905
819
  firmwareSize: number
@@ -907,37 +821,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
907
821
  const transferStartTime = Date.now();
908
822
  const transferTransport = this.getProtocolV2FirmwareTransferTransport();
909
823
 
910
- const isRemoteMode = bundles.some(bundle => !!bundle.url);
911
- let bundlesToSync = bundles;
912
-
913
- // Remote binaries were prefetched before bootloader entry. Only device-local
914
- // header comparison and transfer are allowed in this phase.
915
- if (isRemoteMode) {
916
- const filtered: ProtocolV2ResourceBundleBinary[] = [];
917
- for (const bundle of bundles) {
918
- // Compare the existing device header.
919
- const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
920
- if (upToDate) {
921
- Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
922
- } else {
923
- filtered.push(bundle);
924
- }
925
- }
926
- bundlesToSync = filtered;
927
- }
928
-
929
- if (bundlesToSync.length === 0) {
930
- Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
931
- return { processedSize: 0, totalSize: firmwareSize };
932
- }
933
-
934
824
  // Write directly to the device through FileWrite.
935
825
  let totalSize = 0;
936
- for (const b of bundlesToSync) totalSize += b.binary.byteLength;
826
+ for (const b of bundles) totalSize += b.binary.byteLength;
937
827
 
938
828
  const transferTotalSize = totalSize + firmwareSize;
939
829
  let processedSize = 0;
940
- for (const bundle of bundlesToSync) {
830
+ for (const bundle of bundles) {
941
831
  Log.log(
942
832
  `[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
943
833
  );
@@ -958,41 +848,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
958
848
  return { processedSize, totalSize: transferTotalSize };
959
849
  }
960
850
 
961
- /**
962
- * Compare the existing okpkg OKPP header in remote-config mode.
963
- */
964
- private async isProtocolV2ResourceBundleUpToDate(
965
- bundle: ProtocolV2ResourceBundleBinary
966
- ): Promise<boolean> {
967
- if (this.params.forcedUpdateRes) return false;
968
- if (!bundle.version && !bundle.payloadHash) return false;
969
-
970
- try {
971
- const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
972
- if (!header) return false;
973
-
974
- if (bundle.version) {
975
- const cmp = compareProtocolV2Versions(header.version, bundle.version);
976
- if (cmp === undefined || cmp !== 0) return false;
977
- }
978
- if (bundle.payloadHash) {
979
- const expected = normalizeProtocolV2Hex(bundle.payloadHash);
980
- if (expected && header.payloadHash !== expected) return false;
981
- }
982
- if (bundle.headerHash) {
983
- const expected = normalizeProtocolV2Hex(bundle.headerHash);
984
- if (expected && header.headerHash !== expected) return false;
985
- }
986
- return true;
987
- } catch (error) {
988
- Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
989
- return false;
990
- }
991
- }
992
-
993
851
  private isProtocolV2BootloaderMode() {
994
852
  if (typeof this.device.isBootloader === 'function') {
995
- return this.device.isBootloader();
853
+ return !!this.device.isBootloader();
996
854
  }
997
855
  return (
998
856
  this.device.features?.mode === 'bootloader' ||
@@ -1406,6 +1264,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1406
1264
  PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT
1407
1265
  );
1408
1266
 
1267
+ return this.getProtocolV2VersionResult(features);
1268
+ }
1269
+
1270
+ private getProtocolV2VersionResult(features: Features) {
1409
1271
  const bootloaderVersion = getDeviceBootloaderVersion(features).join('.');
1410
1272
  const bleVersion = getDeviceBLEFirmwareVersion(features).join('.');
1411
1273
  const firmwareVersion = getDeviceFirmwareVersion(features).join('.');
@@ -450,6 +450,8 @@
450
450
  "MessageType_DeviceInfo": 60601,
451
451
  "MessageType_DeviceStatusGet": 60602,
452
452
  "MessageType_DeviceStatus": 60603,
453
+ "MessageType_ResourceInventoryGet": 60604,
454
+ "MessageType_ResourceInventory": 60605,
453
455
  "MessageType_FilesystemPermissionFix": 60800,
454
456
  "MessageType_FilesystemPathInfo": 60801,
455
457
  "MessageType_FilesystemPathInfoQuery": 60802,
@@ -11994,6 +11996,16 @@
11994
11996
  "APP": 85
11995
11997
  }
11996
11998
  },
11999
+ "ResourceBundleType": {
12000
+ "values": {
12001
+ "IMAGES": 0,
12002
+ "ANIMATION": 1,
12003
+ "WALLPAPER": 2,
12004
+ "TRANSLATIONS": 3,
12005
+ "ROOBERT": 4,
12006
+ "NOTO": 5
12007
+ }
12008
+ },
11997
12009
  "DeviceFirmwareImageInfo": {
11998
12010
  "fields": {
11999
12011
  "version": {
@@ -12191,6 +12203,37 @@
12191
12203
  }
12192
12204
  }
12193
12205
  },
12206
+ "ResourceInventoryGet": {
12207
+ "fields": {}
12208
+ },
12209
+ "ResourceInventoryItem": {
12210
+ "fields": {
12211
+ "type": {
12212
+ "rule": "required",
12213
+ "type": "ResourceBundleType",
12214
+ "id": 1
12215
+ },
12216
+ "size": {
12217
+ "rule": "required",
12218
+ "type": "uint32",
12219
+ "id": 2
12220
+ },
12221
+ "header_hash": {
12222
+ "rule": "required",
12223
+ "type": "bytes",
12224
+ "id": 3
12225
+ }
12226
+ }
12227
+ },
12228
+ "ResourceInventory": {
12229
+ "fields": {
12230
+ "items": {
12231
+ "rule": "repeated",
12232
+ "type": "ResourceInventoryItem",
12233
+ "id": 1
12234
+ }
12235
+ }
12236
+ },
12194
12237
  "DeviceSessionErrorCode": {
12195
12238
  "values": {
12196
12239
  "DeviceSessionError_None": 0,
@@ -17,6 +17,7 @@ import {
17
17
  } from '../utils';
18
18
  import { DeviceModelToTypes } from '../types';
19
19
  import { findLatestRelease, getReleaseChangelog, getReleaseStatus } from '../utils/release';
20
+ import { parseProtocolV2Resources } from '../protocols/protocol-v2/resources';
20
21
 
21
22
  import type {
22
23
  AssetsMap,
@@ -397,10 +398,10 @@ export default class DataManager {
397
398
  return enrichedData;
398
399
  }
399
400
 
400
- static async load(settings: ConnectSettings) {
401
+ static async load(settings: ConnectSettings): Promise<boolean> {
401
402
  this.settings = settings;
402
403
  if (!settings.fetchConfig) {
403
- return;
404
+ return false;
404
405
  }
405
406
 
406
407
  const url = settings.preRelease
@@ -445,6 +446,9 @@ export default class DataManager {
445
446
 
446
447
  // 3. Apply config if available
447
448
  if (data) {
449
+ const pro2Resources = parseProtocolV2Resources(
450
+ (data.pro2 as { resources?: unknown } | undefined)?.resources
451
+ );
448
452
  Log.log(`[DataConfig] Config loaded successfully via [${fetchMethod}]`);
449
453
  this.deviceMap = {
450
454
  [EDeviceType.Classic]: this.enrichFirmwareReleaseInfo(data.classic),
@@ -453,14 +457,18 @@ export default class DataManager {
453
457
  [EDeviceType.Mini]: this.enrichFirmwareReleaseInfo(data.mini),
454
458
  [EDeviceType.Touch]: this.enrichFirmwareReleaseInfo(data.touch),
455
459
  [EDeviceType.Pro]: this.enrichFirmwareReleaseInfo(data.pro),
456
- [EDeviceType.Pro2]: this.enrichFirmwareReleaseInfo(data.pro2),
460
+ [EDeviceType.Pro2]: {
461
+ ...this.enrichFirmwareReleaseInfo(data.pro2),
462
+ ...(pro2Resources ? { resources: pro2Resources } : undefined),
463
+ },
457
464
  };
458
465
  this.assets = {
459
466
  bridge: data.bridge,
460
467
  };
461
- } else {
462
- Log.warn('[DataConfig] All fetch methods failed, using built-in default config');
468
+ return true;
463
469
  }
470
+ Log.warn('[DataConfig] All fetch methods failed, using built-in default config');
471
+ return false;
464
472
  }
465
473
 
466
474
  static updateEnv(newEnv: ConnectSettings['env']) {
@@ -478,10 +486,27 @@ export default class DataManager {
478
486
 
479
487
  static async checkAndReloadData() {
480
488
  if (getTimeStamp() - this.lastCheckTimestamp > 1000 * 60 * 60 * 3) {
481
- await this.load(this.settings).then(() => {
489
+ const loaded = await this.load(this.settings);
490
+ if (loaded) {
482
491
  this.lastCheckTimestamp = getTimeStamp();
483
- });
492
+ }
493
+ }
494
+ }
495
+
496
+ /** Force a fresh remote config before an update is allowed to mutate the device. */
497
+ static async forceReloadData(): Promise<void> {
498
+ if (!this.settings) {
499
+ throw new Error('Remote config settings are not initialized');
500
+ }
501
+ const loaded = await this.load(this.settings);
502
+ if (!loaded) {
503
+ throw new Error('Unable to refresh the latest remote config');
484
504
  }
505
+ this.lastCheckTimestamp = getTimeStamp();
506
+ }
507
+
508
+ static getProtocolV2Resources() {
509
+ return this.deviceMap[EDeviceType.Pro2]?.resources?.stable;
485
510
  }
486
511
 
487
512
  static getProtobufMessages(schema: ProtobufMessageSchema = 'v1CurrentSchema'): JSON {