@onekeyfe/react-native-split-bundle-loader 6.7.0-1 → 6.7.0-10

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.
@@ -114,6 +114,7 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
114
114
  // assets get extracted fresh on first load.
115
115
  private const val PREFS_NAME = "split_bundle_loader"
116
116
  private const val KEY_LAST_INSTALL_STAMP = "last_install_stamp"
117
+ private const val BUILTIN_BUNDLE_SENTINEL = "__BUILTIN__"
117
118
  // Double-checked locking: loser threads must BLOCK until the wipe
118
119
  // finishes, not just skip. AtomicBoolean.compareAndSet would let
119
120
  // losers race ahead and read half-nuked state.
@@ -121,6 +122,19 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
121
122
  private val wipeLock = Any()
122
123
  }
123
124
 
125
+ // Pin the OTA bundle path that this React runtime instance booted with so
126
+ // mid-session SharedPreferences mutations (installBundle / switchBundle /
127
+ // resetToBuiltInBundle during the 2500ms pre-restart window) cannot cause
128
+ // subsequent lazy segment loads to jump to a different bundle root.
129
+ @Volatile
130
+ private var pinnedOtaBundlePath: String? = null
131
+ private val pinnedOtaBundleLock = Any()
132
+
133
+ override fun initialize() {
134
+ super.initialize()
135
+ getOtaBundlePath()
136
+ }
137
+
124
138
  override fun getName(): String = NAME
125
139
 
126
140
  // -----------------------------------------------------------------------
@@ -458,6 +472,8 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
458
472
  } else {
459
473
  SBLLogger.info("[resolveSeg] rel=$relativePath otaBundlePath=$otaBundlePath parentFile=null")
460
474
  }
475
+ SBLLogger.warn("[resolveSeg] rel=$relativePath missing in active OTA bundle root — refusing builtin fallback")
476
+ return null
461
477
  } else {
462
478
  SBLLogger.info("[resolveSeg] rel=$relativePath otaBundlePath=(empty) — skipping OTA")
463
479
  }
@@ -636,6 +652,22 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
636
652
  }
637
653
 
638
654
  private fun getOtaBundlePath(): String? {
655
+ val existing = pinnedOtaBundlePath
656
+ if (existing != null) {
657
+ return if (existing == BUILTIN_BUNDLE_SENTINEL) null else existing
658
+ }
659
+ synchronized(pinnedOtaBundleLock) {
660
+ val rechecked = pinnedOtaBundlePath
661
+ if (rechecked != null) {
662
+ return if (rechecked == BUILTIN_BUNDLE_SENTINEL) null else rechecked
663
+ }
664
+ val resolved = queryCurrentOtaBundlePath()
665
+ pinnedOtaBundlePath = if (resolved.isNullOrEmpty()) BUILTIN_BUNDLE_SENTINEL else resolved
666
+ return resolved
667
+ }
668
+ }
669
+
670
+ private fun queryCurrentOtaBundlePath(): String? {
639
671
  return try {
640
672
  val bundleUpdateStore = Class.forName(
641
673
  "com.margelo.nitro.reactnativebundleupdate.BundleUpdateStoreAndroid"
@@ -697,7 +729,9 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
697
729
  */
698
730
  private fun verifySha256(path: String, expected: String): Boolean {
699
731
  if (expected.isEmpty()) {
700
- return !currentBundleRequiresPerSegmentHash()
732
+ val otaPath = getOtaBundlePath()
733
+ val otaActive = !otaPath.isNullOrEmpty() && pinnedOtaBundlePath != BUILTIN_BUNDLE_SENTINEL
734
+ return otaActive && !currentBundleRequiresPerSegmentHash()
701
735
  }
702
736
  return try {
703
737
  val md = MessageDigest.getInstance("SHA-256")
@@ -416,7 +416,33 @@ typedef NS_ENUM(NSInteger, ESegmentEvalError) {
416
416
  ESegmentEvalErrorTimeout = 6,
417
417
  };
418
418
 
419
- @implementation SplitBundleLoader
419
+ static NSString *const kSBLBuiltinBundleSentinel = @"__BUILTIN__";
420
+
421
+ @implementation SplitBundleLoader {
422
+ os_unfair_lock _pinnedOtaLock;
423
+ NSString *_Nullable _pinnedOtaBundlePath;
424
+ }
425
+
426
+ - (instancetype)init
427
+ {
428
+ if (self = [super init]) {
429
+ _pinnedOtaLock = OS_UNFAIR_LOCK_INIT;
430
+ }
431
+ return self;
432
+ }
433
+
434
+ - (nullable NSString *)pinnedOtaBundlePath
435
+ {
436
+ os_unfair_lock_lock(&_pinnedOtaLock);
437
+ NSString *cached = _pinnedOtaBundlePath;
438
+ if (!cached) {
439
+ NSString *resolved = [SplitBundleLoader otaBundlePath];
440
+ cached = (resolved.length > 0) ? [resolved copy] : kSBLBuiltinBundleSentinel;
441
+ _pinnedOtaBundlePath = cached;
442
+ }
443
+ os_unfair_lock_unlock(&_pinnedOtaLock);
444
+ return [cached isEqualToString:kSBLBuiltinBundleSentinel] ? nil : cached;
445
+ }
420
446
 
421
447
  // Reflectively asks bundle-update whether the current bundle's manifest is
422
448
  // required to ship per-segment SHA-256s (three-bundle / split-thread format).
@@ -451,11 +477,21 @@ typedef NS_ENUM(NSInteger, ESegmentEvalError) {
451
477
  // mismatch or read failure. Streams the file with a fixed-size buffer so a
452
478
  // large segment doesn't pull the whole bundle into memory just to hash it.
453
479
  + (BOOL)verifySha256OfFile:(NSString *)path expected:(NSString *)expected
480
+ {
481
+ return [self verifySha256OfFile:path expected:expected otaBundlePath:[self otaBundlePath]];
482
+ }
483
+
484
+ + (BOOL)verifySha256OfFile:(NSString *)path
485
+ expected:(NSString *)expected
486
+ otaBundlePath:(nullable NSString *)otaPath
454
487
  {
455
488
  if (expected.length == 0) {
456
- // Defer to bundle-update: three-bundle format manifests must ship
457
- // per-segment hashes (fail closed); older formats are allowed to omit
458
- // them (back-compat).
489
+ // Only allow skipping SHA-256 verification when an OTA bundle IS active
490
+ // AND its manifest is an older format that does not require per-segment hashes.
491
+ // Built-in bundles and three-bundle manifests must always supply a non-empty hash.
492
+ if (otaPath.length == 0 || [otaPath isEqualToString:kSBLBuiltinBundleSentinel]) {
493
+ return NO;
494
+ }
459
495
  return ![SplitBundleLoader currentBundleRequiresPerSegmentHash];
460
496
  }
461
497
  NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:path];
@@ -486,6 +522,7 @@ typedef NS_ENUM(NSInteger, ESegmentEvalError) {
486
522
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
487
523
  (const facebook::react::ObjCTurboModule::InitParams &)params
488
524
  {
525
+ [self pinnedOtaBundlePath];
489
526
  [SBLLogger info:@"SplitBundleLoader module initialized"];
490
527
  return std::make_shared<facebook::react::NativeSplitBundleLoaderSpecJSI>(params);
491
528
  }
@@ -761,7 +798,7 @@ typedef NS_ENUM(NSInteger, ESegmentEvalError) {
761
798
  NSString *nativeVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"] ?: @"";
762
799
  NSString *bundleVersion = @"";
763
800
 
764
- NSString *otaPath = [SplitBundleLoader otaBundlePath];
801
+ NSString *otaPath = [self pinnedOtaBundlePath];
765
802
  if (otaPath && [[NSFileManager defaultManager] fileExistsAtPath:otaPath]) {
766
803
  sourceKind = @"ota";
767
804
  bundleRoot = [otaPath stringByDeletingLastPathComponent];
@@ -788,28 +825,38 @@ typedef NS_ENUM(NSInteger, ESegmentEvalError) {
788
825
  /// Resolves a relative segment path to an absolute path, checking OTA then builtin.
789
826
  /// Returns nil if the segment file is not found.
790
827
  + (nullable NSString *)resolveAbsolutePath:(NSString *)relativePath
828
+ {
829
+ return [self resolveAbsolutePath:relativePath
830
+ otaBundlePath:[self otaBundlePath]];
831
+ }
832
+
833
+ + (nullable NSString *)resolveAbsolutePath:(NSString *)relativePath
834
+ otaBundlePath:(nullable NSString *)otaPath
791
835
  {
792
836
  // 1. Try OTA bundle root first
793
- NSString *otaPath = [SplitBundleLoader otaBundlePath];
794
837
  if (otaPath) {
795
- // Standardize root so hasPrefix matches candidate (iOS resolves /private/var → /var).
796
- NSString *otaRoot = [[otaPath stringByDeletingLastPathComponent] stringByStandardizingPath];
797
- NSString *candidate = [[otaRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
798
- BOOL otaPrefixOk = [candidate hasPrefix:otaRoot];
838
+ // Resolve symlinks and enforce trailing slash boundary so sibling-prefix directories cannot match.
839
+ NSString *otaRoot = [[[otaPath stringByDeletingLastPathComponent] stringByResolvingSymlinksInPath] stringByStandardizingPath];
840
+ NSString *otaRootWithSlash = [otaRoot hasSuffix:@"/"] ? otaRoot : [otaRoot stringByAppendingString:@"/"];
841
+ NSString *candidate = [[[otaRoot stringByAppendingPathComponent:relativePath] stringByResolvingSymlinksInPath] stringByStandardizingPath];
842
+ BOOL otaPrefixOk = [candidate hasPrefix:otaRootWithSlash];
799
843
  BOOL otaExists = [[NSFileManager defaultManager] fileExistsAtPath:candidate];
800
844
  [SBLLogger info:[NSString stringWithFormat:@"[resolveAbs] rel=%@ ota root=%@ cand=%@ prefixOk=%d exists=%d",
801
845
  relativePath, otaRoot, candidate, otaPrefixOk, otaExists]];
802
846
  if (otaPrefixOk && otaExists) {
803
847
  return candidate;
804
848
  }
849
+ [SBLLogger warn:[NSString stringWithFormat:@"[resolveAbs] rel=%@ missing in active OTA bundle root — refusing builtin fallback", relativePath]];
850
+ return nil;
805
851
  } else {
806
852
  [SBLLogger info:[NSString stringWithFormat:@"[resolveAbs] rel=%@ otaPath=(nil) — skipping OTA", relativePath]];
807
853
  }
808
854
 
809
855
  // 2. Fallback to builtin resource path
810
- NSString *builtinRoot = [[[NSBundle mainBundle] resourcePath] stringByStandardizingPath];
811
- NSString *candidate = [[builtinRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
812
- BOOL builtinPrefixOk = [candidate hasPrefix:builtinRoot];
856
+ NSString *builtinRoot = [[[[NSBundle mainBundle] resourcePath] stringByResolvingSymlinksInPath] stringByStandardizingPath];
857
+ NSString *builtinRootWithSlash = [builtinRoot hasSuffix:@"/"] ? builtinRoot : [builtinRoot stringByAppendingString:@"/"];
858
+ NSString *candidate = [[[builtinRoot stringByAppendingPathComponent:relativePath] stringByResolvingSymlinksInPath] stringByStandardizingPath];
859
+ BOOL builtinPrefixOk = [candidate hasPrefix:builtinRootWithSlash];
813
860
  BOOL builtinExists = [[NSFileManager defaultManager] fileExistsAtPath:candidate];
814
861
  [SBLLogger info:[NSString stringWithFormat:@"[resolveAbs] rel=%@ builtin root=%@ cand=%@ prefixOk=%d exists=%d",
815
862
  relativePath, builtinRoot, candidate, builtinPrefixOk, builtinExists]];
@@ -836,14 +883,16 @@ typedef NS_ENUM(NSInteger, ESegmentEvalError) {
836
883
  return;
837
884
  }
838
885
 
839
- NSString *absolutePath = [SplitBundleLoader resolveAbsolutePath:relativePath];
886
+ NSString *otaPath = [self pinnedOtaBundlePath];
887
+ NSString *absolutePath = [SplitBundleLoader resolveAbsolutePath:relativePath
888
+ otaBundlePath:otaPath];
840
889
  if (!absolutePath) {
841
890
  reject(@"SPLIT_BUNDLE_NOT_FOUND",
842
891
  [NSString stringWithFormat:@"Segment file not found: %@", relativePath],
843
892
  nil);
844
893
  return;
845
894
  }
846
- if (![SplitBundleLoader verifySha256OfFile:absolutePath expected:sha256]) {
895
+ if (![SplitBundleLoader verifySha256OfFile:absolutePath expected:sha256 otaBundlePath:otaPath]) {
847
896
  reject(@"SPLIT_BUNDLE_SHA256_MISMATCH",
848
897
  [NSString stringWithFormat:@"Segment SHA-256 mismatch: %@", relativePath],
849
898
  nil);
@@ -1068,7 +1117,9 @@ typedef NS_ENUM(NSInteger, ESegmentEvalError) {
1068
1117
  return;
1069
1118
  }
1070
1119
 
1071
- NSString *absolutePath = [SplitBundleLoader resolveAbsolutePath:relativePath];
1120
+ NSString *otaPath = [self pinnedOtaBundlePath];
1121
+ NSString *absolutePath = [SplitBundleLoader resolveAbsolutePath:relativePath
1122
+ otaBundlePath:otaPath];
1072
1123
 
1073
1124
  if (!absolutePath) {
1074
1125
  reject(@"SPLIT_BUNDLE_NOT_FOUND",
@@ -1077,7 +1128,7 @@ typedef NS_ENUM(NSInteger, ESegmentEvalError) {
1077
1128
  return;
1078
1129
  }
1079
1130
 
1080
- if (![SplitBundleLoader verifySha256OfFile:absolutePath expected:sha256]) {
1131
+ if (![SplitBundleLoader verifySha256OfFile:absolutePath expected:sha256 otaBundlePath:otaPath]) {
1081
1132
  reject(@"SPLIT_BUNDLE_SHA256_MISMATCH",
1082
1133
  [NSString stringWithFormat:@"Segment SHA-256 mismatch: %@ (key=%@)", relativePath, segmentKey],
1083
1134
  nil);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-split-bundle-loader",
3
- "version": "6.7.0-1",
3
+ "version": "6.7.0-10",
4
4
  "description": "Native split JavaScript bundle context and segment loading for OneKey",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",