react-native-update 10.43.3 → 10.45.0

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.
Files changed (41) hide show
  1. package/android/jni/Android.mk +0 -1
  2. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  3. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  4. package/android/lib/x86/librnupdate.so +0 -0
  5. package/android/lib/x86_64/librnupdate.so +0 -0
  6. package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +24 -12
  7. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +33 -9
  8. package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +9 -1
  9. package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +64 -0
  10. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +8 -4
  11. package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +13 -1
  12. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +8 -8
  13. package/cpp/patch_core/archive_patch_core.cpp +16 -0
  14. package/cpp/patch_core/archive_patch_core.h +5 -0
  15. package/cpp/patch_core/jni_util.h +56 -0
  16. package/cpp/patch_core/patch_core.cpp +16 -1
  17. package/cpp/patch_core/patch_core.h +6 -0
  18. package/cpp/patch_core/patch_core_android.cpp +4 -37
  19. package/cpp/patch_core/state_ops.h +24 -0
  20. package/cpp/patch_core/tests/patch_core_test.cpp +109 -2
  21. package/cpp/patch_core/update_core_android.cpp +43 -69
  22. package/harmony/pushy/src/main/cpp/pushy.cpp +163 -128
  23. package/harmony/pushy/src/main/ets/DownloadTask.ts +72 -19
  24. package/harmony/pushy/src/main/ets/NativePatchCore.ts +2 -6
  25. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +10 -10
  26. package/harmony/pushy/src/main/ets/UpdateContext.ts +26 -9
  27. package/harmony/pushy.har +0 -0
  28. package/ios/RCTPushy/RCTPushy.mm +142 -111
  29. package/ios/RCTPushy/RCTPushyDownloader.mm +41 -2
  30. package/package.json +7 -3
  31. package/src/client.ts +161 -101
  32. package/src/context.ts +21 -7
  33. package/src/core.ts +5 -1
  34. package/src/index.ts +7 -1
  35. package/src/provider.tsx +91 -56
  36. package/src/utils.ts +58 -18
  37. package/android/jni/DownloadTask.c +0 -56
  38. package/android/jni/cn_reactnative_modules_update_DownloadTask.h +0 -21
  39. package/harmony/pushy/src/main/cpp/pushy.c +0 -117
  40. package/harmony/pushy/src/main/cpp/pushy.h +0 -8
  41. package/src/__tests__/setup.ts +0 -44
@@ -175,6 +175,81 @@ void TestApplyPatchFromFileSourceMergesAndCopies() {
175
175
  Expect(!Exists(JoinPath(target, "assets/delete.txt")), "deleted asset should not be copied");
176
176
  }
177
177
 
178
+ void TestApplyPatchMergeHardLinksUnchangedFiles() {
179
+ TempDir temp;
180
+ const std::string source = JoinPath(temp.path, "origin");
181
+ const std::string target = JoinPath(temp.path, "target");
182
+ const std::string patch = JoinPath(temp.path, "bundle.patch");
183
+
184
+ WriteFile(JoinPath(source, "index.bundlejs"), "old bundle");
185
+ WriteFile(JoinPath(source, "assets/keep.txt"), "keep");
186
+ WriteFile(patch, "unused patch");
187
+
188
+ FakeBundlePatcher patcher("patched bundle");
189
+ FileSourcePatchOptions options;
190
+ options.source_root = source;
191
+ options.target_root = target;
192
+ options.origin_bundle_path = JoinPath(source, "index.bundlejs");
193
+ options.bundle_patch_path = patch;
194
+ options.bundle_output_path = JoinPath(target, "index.bundlejs");
195
+ options.manifest.copies.push_back(CopyOperation{"assets/keep.txt", "assets/copied.txt"});
196
+
197
+ Status status = ApplyPatchFromFileSource(options, patcher);
198
+ Expect(status.ok, status.message);
199
+
200
+ // Unchanged files must share the source inode (hard link) instead of being
201
+ // byte-copied: same filesystem, so the fast path has to kick in.
202
+ struct stat source_stat;
203
+ struct stat merged_stat;
204
+ struct stat copied_stat;
205
+ Expect(stat(JoinPath(source, "assets/keep.txt").c_str(), &source_stat) == 0, "stat source");
206
+ Expect(stat(JoinPath(target, "assets/keep.txt").c_str(), &merged_stat) == 0, "stat merged");
207
+ Expect(stat(JoinPath(target, "assets/copied.txt").c_str(), &copied_stat) == 0, "stat copied");
208
+ Expect(merged_stat.st_ino == source_stat.st_ino, "merged file should hard-link the source");
209
+ Expect(copied_stat.st_ino == source_stat.st_ino, "manifest copy should hard-link the source");
210
+ Expect(source_stat.st_nlink >= 3, "source should have three names");
211
+ ExpectEq(ReadFile(JoinPath(target, "assets/keep.txt")), "keep", "merged content mismatch");
212
+
213
+ // The patched bundle must be a fresh file, never a link into the source dir.
214
+ struct stat bundle_stat;
215
+ struct stat origin_stat;
216
+ Expect(stat(JoinPath(target, "index.bundlejs").c_str(), &bundle_stat) == 0, "stat bundle");
217
+ Expect(stat(JoinPath(source, "index.bundlejs").c_str(), &origin_stat) == 0, "stat origin bundle");
218
+ Expect(bundle_stat.st_ino != origin_stat.st_ino, "patched bundle must not link the origin");
219
+ }
220
+
221
+ void TestApplyPatchMergeFallsBackToByteCopy() {
222
+ TempDir temp;
223
+ const std::string source = JoinPath(temp.path, "origin");
224
+ const std::string target = JoinPath(temp.path, "target");
225
+ const std::string patch = JoinPath(temp.path, "bundle.patch");
226
+
227
+ WriteFile(JoinPath(source, "index.bundlejs"), "old bundle");
228
+ WriteFile(JoinPath(source, "assets/keep.txt"), "keep");
229
+ WriteFile(patch, "unused patch");
230
+
231
+ FakeBundlePatcher patcher("patched bundle");
232
+ FileSourcePatchOptions options;
233
+ options.source_root = source;
234
+ options.target_root = target;
235
+ options.origin_bundle_path = JoinPath(source, "index.bundlejs");
236
+ options.bundle_patch_path = patch;
237
+ options.bundle_output_path = JoinPath(target, "index.bundlejs");
238
+
239
+ pushy::patch::internal::g_disable_hard_links = true;
240
+ Status status = ApplyPatchFromFileSource(options, patcher);
241
+ pushy::patch::internal::g_disable_hard_links = false;
242
+ Expect(status.ok, status.message);
243
+
244
+ struct stat source_stat;
245
+ struct stat merged_stat;
246
+ Expect(stat(JoinPath(source, "assets/keep.txt").c_str(), &source_stat) == 0, "stat source");
247
+ Expect(stat(JoinPath(target, "assets/keep.txt").c_str(), &merged_stat) == 0, "stat merged");
248
+ Expect(merged_stat.st_ino != source_stat.st_ino, "fallback should produce an independent copy");
249
+ Expect(merged_stat.st_nlink == 1, "fallback copy should have a single name");
250
+ ExpectEq(ReadFile(JoinPath(target, "assets/keep.txt")), "keep", "fallback content mismatch");
251
+ }
252
+
178
253
  void TestApplyPatchFromFileSourceCanLimitMergeSubdir() {
179
254
  TempDir temp;
180
255
  const std::string source = JoinPath(temp.path, "origin");
@@ -426,6 +501,28 @@ void TestArchivePatchCoreRejectsMissingEntries() {
426
501
  "manifest entry should be skipped");
427
502
  }
428
503
 
504
+ void TestTryParseArchivePatchType() {
505
+ pushy::archive_patch::ArchivePatchType type;
506
+ Expect(
507
+ pushy::archive_patch::TryParseArchivePatchType(1, &type) &&
508
+ type == pushy::archive_patch::ArchivePatchType::kFull,
509
+ "1 should parse to kFull");
510
+ Expect(
511
+ pushy::archive_patch::TryParseArchivePatchType(2, &type) &&
512
+ type == pushy::archive_patch::ArchivePatchType::kPatchFromPackage,
513
+ "2 should parse to kPatchFromPackage");
514
+ Expect(
515
+ pushy::archive_patch::TryParseArchivePatchType(3, &type) &&
516
+ type == pushy::archive_patch::ArchivePatchType::kPatchFromPpk,
517
+ "3 should parse to kPatchFromPpk");
518
+ Expect(
519
+ !pushy::archive_patch::TryParseArchivePatchType(0, &type),
520
+ "0 should be rejected");
521
+ Expect(
522
+ !pushy::archive_patch::TryParseArchivePatchType(4, &type),
523
+ "unknown type should be rejected, not silently coerced to kFull");
524
+ }
525
+
429
526
  void TestArchivePatchCoreSupportsCustomBundlePatchEntry() {
430
527
  PatchManifest manifest;
431
528
  manifest.copies.push_back(CopyOperation{"assets/a.png", "assets/a.png"});
@@ -515,6 +612,8 @@ void TestStateCoreSwitchToSameVersion() {
515
612
  int main() {
516
613
  const std::vector<std::pair<std::string, void (*)()>> tests = {
517
614
  {"ApplyPatchFromFileSourceMergesAndCopies", TestApplyPatchFromFileSourceMergesAndCopies},
615
+ {"ApplyPatchMergeHardLinksUnchangedFiles", TestApplyPatchMergeHardLinksUnchangedFiles},
616
+ {"ApplyPatchMergeFallsBackToByteCopy", TestApplyPatchMergeFallsBackToByteCopy},
518
617
  {"ApplyPatchFromFileSourceCanLimitMergeSubdir", TestApplyPatchFromFileSourceCanLimitMergeSubdir},
519
618
  {"ApplyPatchFromFileSourceRejectsUnsafePaths", TestApplyPatchFromFileSourceRejectsUnsafePaths},
520
619
  {"CleanupOldEntriesRemovesOnlyExpiredPaths", TestCleanupOldEntriesRemovesOnlyExpiredPaths},
@@ -524,6 +623,7 @@ int main() {
524
623
  {"StateCoreCanClearMarkers", TestStateCoreCanClearMarkers},
525
624
  {"ArchivePatchCoreBuildPlanAndCopyGroups", TestArchivePatchCoreBuildPlanAndCopyGroups},
526
625
  {"ArchivePatchCoreRejectsMissingEntries", TestArchivePatchCoreRejectsMissingEntries},
626
+ {"TryParseArchivePatchType", TestTryParseArchivePatchType},
527
627
  {"ArchivePatchCoreSupportsCustomBundlePatchEntry", TestArchivePatchCoreSupportsCustomBundlePatchEntry},
528
628
  {"ArchivePatchCoreHarmonyBundlePatchFromPackage", TestArchivePatchCoreHarmonyBundlePatchFromPackage},
529
629
  {"StateCoreRollbackToEmptyVersion", TestStateCoreRollbackToEmptyVersion},
@@ -531,15 +631,22 @@ int main() {
531
631
  {"StateCoreSwitchToSameVersion", TestStateCoreSwitchToSameVersion},
532
632
  };
533
633
 
634
+ int failures = 0;
534
635
  for (const auto& test : tests) {
535
636
  try {
536
637
  test.second();
537
638
  std::fprintf(stdout, "[PASS] %s\n", test.first.c_str());
538
639
  } catch (const std::exception& error) {
539
640
  std::fprintf(stderr, "[FAIL] %s: %s\n", test.first.c_str(), error.what());
540
- return 1;
641
+ ++failures;
541
642
  }
542
643
  }
543
644
 
544
- return 0;
645
+ std::fprintf(
646
+ stdout,
647
+ "\n%zu tests, %d passed, %d failed\n",
648
+ tests.size(),
649
+ static_cast<int>(tests.size()) - failures,
650
+ failures);
651
+ return failures == 0 ? 0 : 1;
545
652
  }
@@ -4,56 +4,16 @@
4
4
  #include <vector>
5
5
 
6
6
  #include "archive_patch_core.h"
7
+ #include "jni_util.h"
7
8
  #include "state_core.h"
9
+ #include "state_ops.h"
8
10
 
9
11
  namespace {
10
12
 
11
- enum class StateOperation {
12
- kSwitchVersion = 1,
13
- kMarkSuccess = 2,
14
- kRollback = 3,
15
- kClearFirstTime = 4,
16
- kClearRollbackMark = 5,
17
- kResolveLaunch = 6,
18
- };
19
-
20
- std::string JStringToString(JNIEnv* env, jstring value) {
21
- if (value == nullptr) {
22
- return std::string();
23
- }
24
-
25
- const char* chars = env->GetStringUTFChars(value, nullptr);
26
- if (chars == nullptr) {
27
- return std::string();
28
- }
29
-
30
- std::string result(chars);
31
- env->ReleaseStringUTFChars(value, chars);
32
- return result;
33
- }
34
-
35
- std::vector<std::string> JArrayToVector(JNIEnv* env, jobjectArray values) {
36
- std::vector<std::string> result;
37
- if (values == nullptr) {
38
- return result;
39
- }
40
-
41
- const jsize size = env->GetArrayLength(values);
42
- result.reserve(static_cast<size_t>(size));
43
- for (jsize index = 0; index < size; ++index) {
44
- auto* item = static_cast<jstring>(env->GetObjectArrayElement(values, index));
45
- result.push_back(JStringToString(env, item));
46
- env->DeleteLocalRef(item);
47
- }
48
- return result;
49
- }
50
-
51
- void ThrowRuntimeException(JNIEnv* env, const std::string& message) {
52
- jclass exception = env->FindClass("java/lang/RuntimeException");
53
- if (exception != nullptr) {
54
- env->ThrowNew(exception, message.c_str());
55
- }
56
- }
13
+ using pushy::jni_util::JArrayToVector;
14
+ using pushy::jni_util::JStringToString;
15
+ using pushy::jni_util::ThrowRuntimeException;
16
+ using pushy::state_ops::StateOperation;
57
17
 
58
18
  void SetStringField(
59
19
  JNIEnv* env,
@@ -64,6 +24,9 @@ void SetStringField(
64
24
  jfieldID field =
65
25
  env->GetFieldID(target_class, field_name, "Ljava/lang/String;");
66
26
  if (field == nullptr) {
27
+ // Clear the pending NoSuchFieldError so subsequent JNI calls are not
28
+ // executed with an exception pending (undefined behavior / CheckJNI abort).
29
+ env->ExceptionClear();
67
30
  return;
68
31
  }
69
32
  if (value.empty()) {
@@ -83,9 +46,11 @@ void SetBooleanField(
83
46
  const char* field_name,
84
47
  bool value) {
85
48
  jfieldID field = env->GetFieldID(target_class, field_name, "Z");
86
- if (field != nullptr) {
87
- env->SetBooleanField(target, field, value ? JNI_TRUE : JNI_FALSE);
49
+ if (field == nullptr) {
50
+ env->ExceptionClear();
51
+ return;
88
52
  }
53
+ env->SetBooleanField(target, field, value ? JNI_TRUE : JNI_FALSE);
89
54
  }
90
55
 
91
56
  std::string GetStringField(
@@ -96,6 +61,7 @@ std::string GetStringField(
96
61
  jfieldID field =
97
62
  env->GetFieldID(target_class, field_name, "Ljava/lang/String;");
98
63
  if (field == nullptr) {
64
+ env->ExceptionClear();
99
65
  return std::string();
100
66
  }
101
67
  auto* value = static_cast<jstring>(env->GetObjectField(target, field));
@@ -112,7 +78,11 @@ bool GetBooleanField(
112
78
  jclass target_class,
113
79
  const char* field_name) {
114
80
  jfieldID field = env->GetFieldID(target_class, field_name, "Z");
115
- return field != nullptr && env->GetBooleanField(target, field) == JNI_TRUE;
81
+ if (field == nullptr) {
82
+ env->ExceptionClear();
83
+ return false;
84
+ }
85
+ return env->GetBooleanField(target, field) == JNI_TRUE;
116
86
  }
117
87
 
118
88
  pushy::state::State ReadState(
@@ -241,13 +211,9 @@ jobject NewArchivePatchPlanResult(
241
211
 
242
212
  jobject NewCopyGroupResult(
243
213
  JNIEnv* env,
214
+ jclass result_class,
215
+ jclass string_class,
244
216
  const pushy::archive_patch::CopyGroup& group) {
245
- jclass result_class =
246
- env->FindClass("cn/reactnative/modules/update/CopyGroupResult");
247
- if (result_class == nullptr) {
248
- return nullptr;
249
- }
250
-
251
217
  jmethodID constructor = env->GetMethodID(result_class, "<init>", "()V");
252
218
  if (constructor == nullptr) {
253
219
  return nullptr;
@@ -262,7 +228,6 @@ jobject NewCopyGroupResult(
262
228
  jfieldID to_paths_field =
263
229
  env->GetFieldID(result_class, "toPaths", "[Ljava/lang/String;");
264
230
  if (to_paths_field != nullptr) {
265
- jclass string_class = env->FindClass("java/lang/String");
266
231
  jobjectArray to_paths = env->NewObjectArray(
267
232
  static_cast<jsize>(group.to_paths.size()), string_class, nullptr);
268
233
  if (to_paths != nullptr) {
@@ -275,22 +240,20 @@ jobject NewCopyGroupResult(
275
240
  env->SetObjectField(result, to_paths_field, to_paths);
276
241
  env->DeleteLocalRef(to_paths);
277
242
  }
243
+ } else {
244
+ env->ExceptionClear();
278
245
  }
279
246
 
280
247
  return result;
281
248
  }
282
249
 
283
- pushy::archive_patch::ArchivePatchType ToArchivePatchType(jint patch_type) {
284
- switch (patch_type) {
285
- case 1:
286
- return pushy::archive_patch::ArchivePatchType::kFull;
287
- case 2:
288
- return pushy::archive_patch::ArchivePatchType::kPatchFromPackage;
289
- case 3:
290
- return pushy::archive_patch::ArchivePatchType::kPatchFromPpk;
291
- default:
292
- return pushy::archive_patch::ArchivePatchType::kFull;
293
- }
250
+ bool ToArchivePatchType(
251
+ jint patch_type,
252
+ pushy::archive_patch::ArchivePatchType* out) {
253
+ // Delegate to the shared parser so Android and HarmonyOS agree on which
254
+ // integer values are valid and neither silently falls back to kFull.
255
+ return pushy::archive_patch::TryParseArchivePatchType(
256
+ static_cast<int>(patch_type), out);
294
257
  }
295
258
 
296
259
  pushy::patch::PatchManifest BuildManifest(
@@ -411,9 +374,14 @@ Java_cn_reactnative_modules_update_DownloadTask_buildArchivePatchPlan(
411
374
 
412
375
  pushy::patch::PatchManifest manifest =
413
376
  BuildManifest(from_values, to_values, JArrayToVector(env, deletes));
377
+ pushy::archive_patch::ArchivePatchType archive_type;
378
+ if (!ToArchivePatchType(patch_type, &archive_type)) {
379
+ ThrowRuntimeException(env, "Unknown archive patch type");
380
+ return nullptr;
381
+ }
414
382
  pushy::archive_patch::ArchivePatchPlan plan;
415
383
  pushy::patch::Status status = pushy::archive_patch::BuildArchivePatchPlan(
416
- ToArchivePatchType(patch_type),
384
+ archive_type,
417
385
  manifest,
418
386
  JArrayToVector(env, entry_names),
419
387
  &plan);
@@ -454,6 +422,11 @@ Java_cn_reactnative_modules_update_DownloadTask_buildCopyGroups(
454
422
  return nullptr;
455
423
  }
456
424
 
425
+ jclass string_class = env->FindClass("java/lang/String");
426
+ if (string_class == nullptr) {
427
+ return nullptr;
428
+ }
429
+
457
430
  jobjectArray result = env->NewObjectArray(
458
431
  static_cast<jsize>(groups.size()), result_class, nullptr);
459
432
  if (result == nullptr) {
@@ -461,9 +434,10 @@ Java_cn_reactnative_modules_update_DownloadTask_buildCopyGroups(
461
434
  }
462
435
 
463
436
  for (jsize index = 0; index < static_cast<jsize>(groups.size()); ++index) {
464
- jobject group = NewCopyGroupResult(env, groups[index]);
437
+ jobject group = NewCopyGroupResult(env, result_class, string_class, groups[index]);
465
438
  env->SetObjectArrayElement(result, index, group);
466
439
  env->DeleteLocalRef(group);
467
440
  }
441
+ env->DeleteLocalRef(string_class);
468
442
  return result;
469
443
  }