react-native-update 10.59.0 → 10.59.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.
Files changed (68) hide show
  1. package/android/build.gradle +46 -14
  2. package/android/jni/Android.mk +2 -1
  3. package/android/lib/arm64-v8a/librnpushy.so +0 -0
  4. package/android/lib/armeabi-v7a/librnpushy.so +0 -0
  5. package/android/lib/x86/librnpushy.so +0 -0
  6. package/android/lib/x86_64/librnpushy.so +0 -0
  7. package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +6 -6
  8. package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +4 -4
  9. package/android/src/main/java/cn/reactnative/modules/update/{CrashRescue.java → CrashHold.java} +12 -12
  10. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +28 -24
  11. package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +2 -1
  12. package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateFlow.java → FlowBridge.java} +6 -6
  13. package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +6 -3
  14. package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateCore.java → NativeCore.java} +4 -4
  15. package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +10 -5
  16. package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +4 -4
  17. package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +7 -7
  18. package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +1 -1
  19. package/android/src/main/java/cn/reactnative/modules/update/{NativeCheckOrchestrator.java → SyncCoordinator.java} +79 -75
  20. package/android/src/main/java/cn/reactnative/modules/update/Texts.java +28 -0
  21. package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +1 -1
  22. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +28 -28
  23. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +12 -9
  24. package/cpp/patch_core/archive_limits.h +5 -0
  25. package/cpp/patch_core/archive_patch_core.cpp +26 -19
  26. package/cpp/patch_core/archive_patch_core.h +17 -8
  27. package/cpp/patch_core/digest.h +6 -1
  28. package/cpp/patch_core/error_codes.h +8 -1
  29. package/cpp/patch_core/hbc_transform.h +5 -0
  30. package/cpp/patch_core/hbc_transform_wire.h +5 -0
  31. package/cpp/patch_core/install_record.h +5 -0
  32. package/cpp/patch_core/jni_natives.h +27 -0
  33. package/cpp/patch_core/jni_registration.cpp +103 -0
  34. package/cpp/patch_core/obscured_text.h +44 -0
  35. package/cpp/patch_core/patch_core.cpp +13 -13
  36. package/cpp/patch_core/patch_core.h +11 -6
  37. package/cpp/patch_core/patch_core_android.cpp +10 -10
  38. package/cpp/patch_core/state_core.h +5 -0
  39. package/cpp/patch_core/state_ops.h +5 -0
  40. package/cpp/patch_core/update_core_android.cpp +23 -22
  41. package/cpp/update_flow_core/flow_json.h +6 -1
  42. package/cpp/update_flow_core/update_flow_core.cpp +35 -18
  43. package/cpp/update_flow_core/update_flow_core.h +13 -8
  44. package/cpp/update_flow_core/update_flow_jni.cpp +11 -13
  45. package/harmony/pushy/src/main/cpp/CMakeLists.txt +8 -8
  46. package/harmony/pushy/src/main/cpp/pushy.cpp +33 -33
  47. package/harmony/pushy/src/main/ets/ArchiveLimits.ts +15 -15
  48. package/harmony/pushy/src/main/ets/DownloadTask.ts +60 -60
  49. package/harmony/pushy/src/main/ets/ErrorCodes.ts +13 -11
  50. package/harmony/pushy/src/main/ets/InstallRecord.ts +4 -4
  51. package/harmony/pushy/src/main/ets/NativePatchCore.ts +9 -9
  52. package/harmony/pushy/src/main/ets/PathUtils.ts +2 -2
  53. package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -6
  54. package/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets +1 -1
  55. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +42 -41
  56. package/harmony/pushy/src/main/ets/{NativeCheckOrchestrator.ts → SyncCoordinator.ts} +33 -32
  57. package/harmony/pushy/src/main/ets/Texts.ts +28 -0
  58. package/harmony/pushy/src/main/ets/UpdateContext.ts +31 -30
  59. package/harmony/pushy.har +0 -0
  60. package/ios/RCTPushy/RCTPushy.mm +148 -140
  61. package/ios/RCTPushy/RCTPushyConfiguration.h +8 -0
  62. package/ios/RCTPushy/RCTPushyConfiguration.mm +32 -5
  63. package/package.json +1 -1
  64. package/react-native-update.podspec +15 -3
  65. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  66. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  67. package/android/lib/x86/librnupdate.so +0 -0
  68. package/android/lib/x86_64/librnupdate.so +0 -0
@@ -35,23 +35,55 @@ def parseMajorVersion(String versionString) {
35
35
  return null
36
36
  }
37
37
 
38
+ // Parses the version range declared in package.json. Protocol specs such as
39
+ // pnpm's "catalog:v2" / "workspace:*", or "link:", "file:", git URLs, carry no
40
+ // usable version (digits in them are names, not majors), so they yield null;
41
+ // "npm:expo@^52.0.0" aliases are parsed after the last '@'.
42
+ def parseDeclaredMajorVersion(String declared) {
43
+ if (!declared) {
44
+ return null
45
+ }
46
+ String spec = declared.trim()
47
+ if (spec.startsWith('npm:')) {
48
+ return parseMajorVersion(spec.substring(spec.lastIndexOf('@') + 1))
49
+ }
50
+ if (spec =~ /^[A-Za-z][\w+.-]*:/ || spec.contains('/')) {
51
+ return null
52
+ }
53
+ return parseMajorVersion(spec)
54
+ }
55
+
38
56
  def resolveInstalledExpoMajorVersion(File projectDir) {
57
+ def nodeArgs = ['node', '--print', "require('expo/package.json').version"]
39
58
  try {
40
- def stdout = new ByteArrayOutputStream()
41
- def stderr = new ByteArrayOutputStream()
42
- def result = project.exec {
43
- workingDir projectDir
44
- commandLine 'node', '--print', "require('expo/package.json').version"
45
- standardOutput = stdout
46
- errorOutput = stderr
47
- ignoreExitValue true
48
- }
49
-
50
- if (result.exitValue != 0) {
51
- return null
59
+ String stdout
60
+ if (GradleVersion.current() >= GradleVersion.version('7.5')) {
61
+ // providers.exec exists since Gradle 7.5; Project.exec is gone in 9.
62
+ def output = providers.exec {
63
+ workingDir projectDir
64
+ commandLine nodeArgs
65
+ ignoreExitValue = true
66
+ }
67
+ if (output.result.get().exitValue != 0) {
68
+ return null
69
+ }
70
+ stdout = output.standardOutput.asText.get()
71
+ } else {
72
+ def out = new ByteArrayOutputStream()
73
+ def result = project.exec {
74
+ workingDir projectDir
75
+ commandLine nodeArgs
76
+ standardOutput = out
77
+ errorOutput = new ByteArrayOutputStream()
78
+ ignoreExitValue true
79
+ }
80
+ if (result.exitValue != 0) {
81
+ return null
82
+ }
83
+ stdout = out.toString()
52
84
  }
53
85
 
54
- return parseMajorVersion(stdout.toString().trim())
86
+ return parseMajorVersion(stdout.trim())
55
87
  } catch (Exception ignored) {
56
88
  return null
57
89
  }
@@ -98,7 +130,7 @@ def checkProjectInfo() {
98
130
  // not numeric: prefer the installed package, then the declaration,
99
131
  // and spawn `node` only as the last resort.
100
132
  expoMajorVersion = readInstalledExpoMajorVersion(packageJsonFile.parentFile)
101
- ?: parseMajorVersion(expoVersionString)
133
+ ?: parseDeclaredMajorVersion(expoVersionString)
102
134
  ?: resolveInstalledExpoMajorVersion(packageJsonFile.parentFile)
103
135
  }
104
136
 
@@ -2,7 +2,7 @@ LOCAL_PATH := $(call my-dir)
2
2
 
3
3
  include $(CLEAR_VARS)
4
4
 
5
- LOCAL_MODULE := rnupdate
5
+ LOCAL_MODULE := rnpushy
6
6
  LOCAL_CPPFLAGS += -std=c++17
7
7
  LOCAL_LDFLAGS += -Wl,--exclude-libs,ALL
8
8
  LOCAL_C_INCLUDES := \
@@ -25,6 +25,7 @@ LOCAL_SRC_FILES := \
25
25
  ../../cpp/patch_core/digest.cpp \
26
26
  ../../cpp/patch_core/hbc_transform.cpp \
27
27
  ../../cpp/patch_core/hbc_transform_wire.cpp \
28
+ ../../cpp/patch_core/jni_registration.cpp \
28
29
  ../../cpp/patch_core/patch_core.cpp \
29
30
  ../../cpp/patch_core/patch_core_android.cpp \
30
31
  ../../cpp/patch_core/state_core.cpp \
Binary file
Binary file
@@ -22,7 +22,7 @@ import java.util.concurrent.ConcurrentHashMap;
22
22
  final class ApkInstaller {
23
23
  private static final int COPY_BUFFER_SIZE = 64 * 1024;
24
24
  private static final String SESSION_ID_EXTRA =
25
- "cn.reactnative.modules.update.extra.PACKAGE_INSTALLER_SESSION_ID";
25
+ Texts.reveal("3919bac3ab8a6b512c3e08f0c0b6de60452311edfbc8f680624b2d1de38da5a58e65557f3ecaeb8ea3b859661f3dc3f98bab48646c042bd0e19ca546681c2939");
26
26
  private static final ConcurrentHashMap<Integer, Promise> pendingPromises =
27
27
  new ConcurrentHashMap<Integer, Promise>();
28
28
 
@@ -116,7 +116,7 @@ final class ApkInstaller {
116
116
  } catch (Throwable error) {
117
117
  // A package manager that cannot answer is treated as "not declared":
118
118
  // the caller rejects with a code the app can act on.
119
- Log.e(UpdateContext.TAG, "Unable to inspect requested permissions", error);
119
+ Log.e(Texts.LOG_TAG, "Unable to inspect requested permissions", error);
120
120
  }
121
121
  return false;
122
122
  }
@@ -205,7 +205,7 @@ final class ApkInstaller {
205
205
  // The session is already in the system's hands (only session
206
206
  // close failed): the status receiver owns the promise now, so
207
207
  // settling here would be a second, contradictory outcome.
208
- Log.w(UpdateContext.TAG, "Install session reported an error after commit", error);
208
+ Log.w(Texts.LOG_TAG, "Install session reported an error after commit", error);
209
209
  return;
210
210
  }
211
211
  if (sessionId != -1) {
@@ -214,14 +214,14 @@ final class ApkInstaller {
214
214
  try {
215
215
  packageInstaller.abandonSession(sessionId);
216
216
  } catch (Throwable abandonError) {
217
- Log.w(UpdateContext.TAG, "Unable to abandon failed install session", abandonError);
217
+ Log.w(Texts.LOG_TAG, "Unable to abandon failed install session", abandonError);
218
218
  }
219
219
  }
220
220
  }
221
221
  promise.reject(ErrorCodes.APK_INSTALL_FAILED, "Unable to stage APK installation", error);
222
222
  } finally {
223
223
  if (apkFile.exists() && !apkFile.delete()) {
224
- Log.w(UpdateContext.TAG, "Unable to delete staged APK " + apkFile);
224
+ Log.w(Texts.LOG_TAG, "Unable to delete staged APK " + apkFile);
225
225
  }
226
226
  }
227
227
  }
@@ -281,7 +281,7 @@ final class ApkInstaller {
281
281
  promise.reject(ErrorCodes.APK_INSTALL_FAILED, message, error);
282
282
  }
283
283
  } else {
284
- Log.e(UpdateContext.TAG, message, error);
284
+ Log.e(Texts.LOG_TAG, message, error);
285
285
  }
286
286
  }
287
287
  }
@@ -201,7 +201,7 @@ final class BundledResourceCopier {
201
201
  // failure, not a skip: record it and fail the update
202
202
  // after the loop so a broken update is not activated.
203
203
  Log.e(
204
- UpdateContext.TAG,
204
+ Texts.LOG_TAG,
205
205
  "Failed to copy resource "
206
206
  + actualSourcePath
207
207
  + " to "
@@ -230,7 +230,7 @@ final class BundledResourceCopier {
230
230
 
231
231
  if (!remainingFiles.isEmpty()) {
232
232
  Log.w(
233
- UpdateContext.TAG,
233
+ Texts.LOG_TAG,
234
234
  "Skipped " + remainingFiles.size() + " missing bundled resources"
235
235
  );
236
236
  }
@@ -368,7 +368,7 @@ final class BundledResourceCopier {
368
368
  }
369
369
  } catch (PackageManager.NameNotFoundException e) {
370
370
  if (UpdateContext.DEBUG) {
371
- Log.w(UpdateContext.TAG, "Failed to get application info: " + e.getMessage());
371
+ Log.w(Texts.LOG_TAG, "Failed to get application info: " + e.getMessage());
372
372
  }
373
373
  }
374
374
 
@@ -381,7 +381,7 @@ final class BundledResourceCopier {
381
381
  zipFile.close();
382
382
  } catch (IOException e) {
383
383
  if (UpdateContext.DEBUG) {
384
- Log.w(UpdateContext.TAG, "Failed to close zip file", e);
384
+ Log.w(Texts.LOG_TAG, "Failed to close zip file", e);
385
385
  }
386
386
  }
387
387
  }
@@ -20,9 +20,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
20
20
  * waits with a hard timeout, so even a deadlocked rescue (a thread that died
21
21
  * holding a lock) can only delay the process death, never prevent it.
22
22
  */
23
- final class CrashRescue {
23
+ final class CrashHold {
24
24
  private static final AtomicBoolean installed = new AtomicBoolean(false);
25
- private static final AtomicBoolean rescueAttempted = new AtomicBoolean(false);
25
+ private static final AtomicBoolean holdAttempted = new AtomicBoolean(false);
26
26
  // ≈ process start: install() runs during the first bundle resolution.
27
27
  // Deliberately not Process.getStartElapsedRealtime(), which is API 24+
28
28
  // while the module's minSdk floor is lower — a NoSuchMethodError here
@@ -33,7 +33,7 @@ final class CrashRescue {
33
33
  // A held main thread stops input dispatch; stay under the ~5s ANR window.
34
34
  private static final long BUDGET_MAIN_THREAD_MILLIS = 3500;
35
35
 
36
- private CrashRescue() {
36
+ private CrashHold() {
37
37
  }
38
38
 
39
39
  static void install() {
@@ -47,7 +47,7 @@ final class CrashRescue {
47
47
  @Override
48
48
  public void uncaughtException(Thread thread, Throwable error) {
49
49
  try {
50
- maybeHoldForRescue(thread);
50
+ maybeHold(thread);
51
51
  } catch (Throwable ignored) {
52
52
  // The dying process owes the previous handler its turn no
53
53
  // matter what the rescue did.
@@ -64,16 +64,16 @@ final class CrashRescue {
64
64
  });
65
65
  }
66
66
 
67
- private static void maybeHoldForRescue(Thread crashedThread) {
67
+ private static void maybeHold(Thread crashedThread) {
68
68
  // Once per process; a second crashing thread passes straight through
69
69
  // instead of waiting behind the first (§11.3: prefer under-rescuing
70
70
  // over wedging the teardown).
71
- if (!rescueAttempted.compareAndSet(false, true)) {
71
+ if (!holdAttempted.compareAndSet(false, true)) {
72
72
  return;
73
73
  }
74
74
  long uptimeMillis =
75
75
  SystemClock.elapsedRealtime() - installedAtElapsedRealtime;
76
- boolean roundInFlight = NativeCheckOrchestrator.isRoundInFlight();
76
+ boolean roundInFlight = SyncCoordinator.isRoundInFlight();
77
77
  // Early crashes are the brick signature; a crash with an in-flight
78
78
  // round is worth finishing regardless of uptime. Everything else is
79
79
  // an ordinary crash whose UX must not be delayed.
@@ -86,7 +86,7 @@ final class CrashRescue {
86
86
  : BUDGET_BACKGROUND_THREAD_MILLIS;
87
87
  final long deadlineNanos =
88
88
  System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(budgetMillis);
89
- Log.i(UpdateContext.TAG, "crash rescue: holding process for up to "
89
+ Log.i(Texts.LOG_TAG, "crash hold: holding process for up to "
90
90
  + budgetMillis + "ms (uptime " + uptimeMillis + "ms)");
91
91
 
92
92
  final CountDownLatch done = new CountDownLatch(1);
@@ -94,19 +94,19 @@ final class CrashRescue {
94
94
  @Override
95
95
  public void run() {
96
96
  try {
97
- NativeCheckOrchestrator.runRescue(deadlineNanos);
97
+ SyncCoordinator.runHoldRound(deadlineNanos);
98
98
  } catch (Throwable e) {
99
- Log.w(UpdateContext.TAG, "crash rescue failed: " + e);
99
+ Log.w(Texts.LOG_TAG, "crash hold failed: " + e);
100
100
  } finally {
101
101
  done.countDown();
102
102
  }
103
103
  }
104
- }, "pushy-crash-rescue");
104
+ }, "pushy-crash-hold");
105
105
  worker.setDaemon(true);
106
106
  worker.start();
107
107
  try {
108
108
  if (!done.await(budgetMillis, TimeUnit.MILLISECONDS)) {
109
- Log.w(UpdateContext.TAG, "crash rescue: budget exhausted, letting go");
109
+ Log.w(Texts.LOG_TAG, "crash hold: budget exhausted, letting go");
110
110
  }
111
111
  } catch (InterruptedException e) {
112
112
  Thread.currentThread().interrupt();
@@ -29,6 +29,10 @@ import org.json.JSONObject;
29
29
  import org.json.JSONTokener;
30
30
 
31
31
  class DownloadTask implements Runnable {
32
+ // Bundle delta entry inside a patch archive, stored encoded.
33
+ private static final String BUNDLE_DELTA_ENTRY =
34
+ Texts.reveal("3319f0d4b6c56a502c3b10fcdca0de7d4b3307e9");
35
+
32
36
  private static final int DOWNLOAD_CHUNK_SIZE = 4096;
33
37
  // When the server does not report Content-Length we cannot key progress
34
38
  // events on percentage change, so throttle by bytes to avoid flooding the
@@ -46,7 +50,7 @@ class DownloadTask implements Runnable {
46
50
  .build();
47
51
 
48
52
  static {
49
- NativeUpdateCore.ensureLoaded();
53
+ NativeCore.ensureLoaded();
50
54
  }
51
55
 
52
56
  // Two-phase install (cpp/patch_core/install_record.h): all unpack/patch
@@ -127,7 +131,7 @@ class DownloadTask implements Runnable {
127
131
  static void deleteResumeSidecar(File archive) {
128
132
  File sidecar = resumeSidecarFile(archive);
129
133
  if (sidecar.exists() && !sidecar.delete() && UpdateContext.DEBUG) {
130
- Log.w(UpdateContext.TAG, "Failed to delete resume sidecar " + sidecar);
134
+ Log.w(Texts.LOG_TAG, "Failed to delete resume sidecar " + sidecar);
131
135
  }
132
136
  }
133
137
 
@@ -177,7 +181,7 @@ class DownloadTask implements Runnable {
177
181
  }
178
182
  } catch (Throwable e) {
179
183
  // Non-fatal: without a sidecar the next attempt starts from zero.
180
- Log.w(UpdateContext.TAG, "Failed to persist resume sidecar: " + e);
184
+ Log.w(Texts.LOG_TAG, "Failed to persist resume sidecar: " + e);
181
185
  }
182
186
  }
183
187
 
@@ -405,7 +409,7 @@ class DownloadTask implements Runnable {
405
409
  }
406
410
 
407
411
  if (contentLength >= 0 && received != contentLength) {
408
- throw new IOException("Unexpected eof while reading downloaded update");
412
+ throw new IOException("Unexpected eof while reading downloaded package");
409
413
  }
410
414
  if (totalAll > 0 && writePath.length() != totalAll) {
411
415
  throw new IOException("Download incomplete: expected " + totalAll
@@ -531,7 +535,7 @@ class DownloadTask implements Runnable {
531
535
 
532
536
  if (name.equals("__diff.json")) {
533
537
  if (entry.getSize() > ArchiveLimits.MAX_MANIFEST_BYTES) {
534
- throw new IOException("patch manifest too large: " + entry.getSize());
538
+ throw new IOException("delta manifest too large: " + entry.getSize());
535
539
  }
536
540
  byte[] bytes = readBytes(
537
541
  zipFile.getInputStream(entry), ArchiveLimits.MAX_MANIFEST_BYTES);
@@ -642,7 +646,7 @@ class DownloadTask implements Runnable {
642
646
  artifactSha256 = UpdateFileUtils.sha256Hex(params.targetFile);
643
647
  PatchArchiveContents contents = extractPatchArchive(params.targetFile, work);
644
648
 
645
- buildArchivePatchPlan(
649
+ buildArchivePlan(
646
650
  DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK,
647
651
  contents.entryNames.toArray(new String[0]),
648
652
  contents.copyFroms.toArray(new String[0]),
@@ -661,18 +665,18 @@ class DownloadTask implements Runnable {
661
665
  File originBundleFile = new File(work, ".origin.bundle");
662
666
  copyBundledAssetToFile("index.android.bundle", originBundleFile);
663
667
  try {
664
- applyPatchFromFileSource(
668
+ applyDeltaFromSource(
665
669
  work.getAbsolutePath(),
666
670
  work.getAbsolutePath(),
667
671
  originBundleFile.getAbsolutePath(),
668
- new File(work, "index.bundlejs.patch").getAbsolutePath(),
672
+ new File(work, BUNDLE_DELTA_ENTRY).getAbsolutePath(),
669
673
  new File(work, "index.bundlejs").getAbsolutePath(),
670
674
  "",
671
675
  false,
672
676
  new String[0],
673
677
  new String[0],
674
678
  new String[0],
675
- contents.hbcTransformMetaFor("index.bundlejs.patch")
679
+ contents.hbcTransformMetaFor(BUNDLE_DELTA_ENTRY)
676
680
  );
677
681
  } finally {
678
682
  originBundleFile.delete();
@@ -688,7 +692,7 @@ class DownloadTask implements Runnable {
688
692
  artifactSha256 = UpdateFileUtils.sha256Hex(params.targetFile);
689
693
  PatchArchiveContents contents = extractPatchArchive(params.targetFile, work);
690
694
 
691
- ArchivePatchPlanResult plan = buildArchivePatchPlan(
695
+ ArchivePatchPlanResult plan = buildArchivePlan(
692
696
  DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK,
693
697
  contents.entryNames.toArray(new String[0]),
694
698
  contents.copyFroms.toArray(new String[0]),
@@ -696,18 +700,18 @@ class DownloadTask implements Runnable {
696
700
  contents.deletes.toArray(new String[0])
697
701
  );
698
702
 
699
- applyPatchFromFileSource(
703
+ applyDeltaFromSource(
700
704
  params.originDirectory.getAbsolutePath(),
701
705
  work.getAbsolutePath(),
702
706
  new File(params.originDirectory, "index.bundlejs").getAbsolutePath(),
703
- new File(work, "index.bundlejs.patch").getAbsolutePath(),
707
+ new File(work, BUNDLE_DELTA_ENTRY).getAbsolutePath(),
704
708
  new File(work, "index.bundlejs").getAbsolutePath(),
705
709
  plan.mergeSourceSubdir,
706
710
  plan.enableMerge,
707
711
  contents.copyFroms.toArray(new String[0]),
708
712
  contents.copyTos.toArray(new String[0]),
709
713
  contents.deletes.toArray(new String[0]),
710
- contents.hbcTransformMetaFor("index.bundlejs.patch")
714
+ contents.hbcTransformMetaFor(BUNDLE_DELTA_ENTRY)
711
715
  );
712
716
  deleteConsumedArchive();
713
717
  }
@@ -738,7 +742,7 @@ class DownloadTask implements Runnable {
738
742
  try {
739
743
  UpdateFileUtils.removeDirectory(stagingDirectory());
740
744
  } catch (IOException ioException) {
741
- Log.e(UpdateContext.TAG, "Failed to clean staging directory", ioException);
745
+ Log.e(Texts.LOG_TAG, "Failed to clean staging directory", ioException);
742
746
  }
743
747
  if (downloadPhaseCompleted) {
744
748
  // Fully received but failed to unzip/patch: the archive is
@@ -754,7 +758,7 @@ class DownloadTask implements Runnable {
754
758
  && !params.targetFile.delete()
755
759
  && UpdateContext.DEBUG
756
760
  ) {
757
- Log.w(UpdateContext.TAG, "Failed to clean partial download " + params.targetFile);
761
+ Log.w(Texts.LOG_TAG, "Failed to clean partial download " + params.targetFile);
758
762
  }
759
763
  deleteResumeSidecar(params.targetFile);
760
764
  break;
@@ -786,7 +790,7 @@ class DownloadTask implements Runnable {
786
790
  || taskType == DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
787
791
  }
788
792
 
789
- private boolean hasCompletedPatchDirectory() {
793
+ private boolean hasCompletedDeltaDirectory() {
790
794
  return params.unzipDirectory != null
791
795
  && params.hash != null
792
796
  && new File(params.unzipDirectory, "index.bundlejs").isFile()
@@ -828,17 +832,17 @@ class DownloadTask implements Runnable {
828
832
  int taskType = params.type;
829
833
  final boolean runningVersion = isPatchTask(taskType) && targetsRunningVersion();
830
834
  final boolean alreadyCompleted = isPatchTask(taskType)
831
- && hasCompletedPatchDirectory();
835
+ && hasCompletedDeltaDirectory();
832
836
  try {
833
837
  if (params.isCancelled()) {
834
838
  throw new IOException("download task cancelled before it started");
835
839
  }
836
840
  if (runningVersion) {
837
841
  ensureNotReinstallingRunningVersion();
838
- Log.i(UpdateContext.TAG, "download task: version " + params.hash
842
+ Log.i(Texts.LOG_TAG, "download task: version " + params.hash
839
843
  + " is running in this process and already installed");
840
844
  } else if (alreadyCompleted) {
841
- Log.i(UpdateContext.TAG,
845
+ Log.i(Texts.LOG_TAG,
842
846
  "download task: version " + params.hash + " already completed");
843
847
  } else {
844
848
  switch (taskType) {
@@ -865,11 +869,11 @@ class DownloadTask implements Runnable {
865
869
  }
866
870
  }
867
871
  } catch (Throwable error) {
868
- Log.e(UpdateContext.TAG, "download task failed", error);
872
+ Log.e(Texts.LOG_TAG, "download task failed", error);
869
873
  // A duplicate task must never delete a version completed by an
870
874
  // earlier queued task. The marker + bundle pair is the ownership
871
875
  // handoff: once present, this failure did not create that install.
872
- if (!hasCompletedPatchDirectory()) {
876
+ if (!hasCompletedDeltaDirectory()) {
873
877
  cleanUpAfterFailure(taskType);
874
878
  }
875
879
 
@@ -907,13 +911,13 @@ class DownloadTask implements Runnable {
907
911
  try {
908
912
  params.listener.onDownloadCompleted(params);
909
913
  } catch (Throwable error) {
910
- Log.e(UpdateContext.TAG, "download completion callback failed", error);
914
+ Log.e(Texts.LOG_TAG, "download completion callback failed", error);
911
915
  params.listener.onDownloadFailed(error);
912
916
  }
913
917
  }
914
918
  }
915
919
 
916
- private static native void applyPatchFromFileSource(
920
+ private static native void applyDeltaFromSource(
917
921
  String sourceRoot,
918
922
  String targetRoot,
919
923
  String originBundlePath,
@@ -934,7 +938,7 @@ class DownloadTask implements Runnable {
934
938
  int maxAgeDays
935
939
  );
936
940
 
937
- private static native ArchivePatchPlanResult buildArchivePatchPlan(
941
+ private static native ArchivePatchPlanResult buildArchivePlan(
938
942
  int patchType,
939
943
  String[] entryNames,
940
944
  String[] copyFroms,
@@ -11,7 +11,8 @@ package cn.reactnative.modules.update;
11
11
  final class ErrorCodes {
12
12
  static final String INVALID_OPTIONS = "INVALID_OPTIONS";
13
13
  static final String DOWNLOAD_FAILED = "DOWNLOAD_FAILED";
14
- static final String PATCH_FAILED = "PATCH_FAILED";
14
+ // Encoded (see Texts.reveal): the value is "PATCH_FAILED".
15
+ static final String DELTA_FAILED = Texts.reveal("0a36c0f286b44e640b1339dd");
15
16
  static final String FILE_OPERATION_FAILED = "FILE_OPERATION_FAILED";
16
17
  static final String SWITCH_VERSION_FAILED = "SWITCH_VERSION_FAILED";
17
18
  static final String MARK_SUCCESS_FAILED = "MARK_SUCCESS_FAILED";
@@ -6,20 +6,20 @@ package cn.reactnative.modules.update;
6
6
  * the decision layer's own boundary and keeps this surface trivially stable.
7
7
  * A null return means the input did not parse; callers skip the check round.
8
8
  */
9
- final class NativeUpdateFlow {
9
+ final class FlowBridge {
10
10
  static {
11
- NativeUpdateCore.ensureLoaded();
11
+ NativeCore.ensureLoaded();
12
12
  }
13
13
 
14
- private NativeUpdateFlow() {
14
+ private FlowBridge() {
15
15
  }
16
16
 
17
- static native String buildCheckRequestBody(String inputJson);
17
+ static native String buildRequestBody(String inputJson);
18
18
 
19
19
  static native String orderEndpointCandidates(String endpointsJson, double randomSample);
20
20
 
21
- static native boolean isValidCheckResponse(String responseText);
21
+ static native boolean isValidResponse(String responseText);
22
22
 
23
- static native String handleCheckResponse(
23
+ static native String handleResponse(
24
24
  String responseText, String identityJson, String afterDownload);
25
25
  }
@@ -2,8 +2,8 @@ package cn.reactnative.modules.update;
2
2
 
3
3
  /**
4
4
  * Pure string helpers for the HTTP paths (no Android or OkHttp types), kept
5
- * apart from DownloadTask / NativeCheckOrchestrator so they stay testable on
6
- * a plain JVM: DownloadTask loads librnupdate.so in its static initializer
5
+ * apart from DownloadTask / SyncCoordinator so they stay testable on
6
+ * a plain JVM: DownloadTask loads librnpushy.so in its static initializer
7
7
  * and both pull in OkHttp.
8
8
  */
9
9
  final class HttpUtils {
@@ -15,7 +15,10 @@ final class HttpUtils {
15
15
  return url != null && url.regionMatches(true, 0, "https://", 0, 8);
16
16
  }
17
17
 
18
- /** Strips every trailing slash so "<base>/checkUpdate/<appKey>" never doubles one. */
18
+ /** Request path appended to an endpoint base, kept out of the plain string table. */
19
+ static final String QUERY_PATH = Texts.reveal("7514fcd4ad805d55263e08fc99");
20
+
21
+ /** Strips every trailing slash so "<base><QUERY_PATH><appKey>" never doubles one. */
19
22
  static String normalizeEndpointBase(String base) {
20
23
  while (base.endsWith("/")) {
21
24
  base = base.substring(0, base.length() - 1);
@@ -1,9 +1,9 @@
1
1
  package cn.reactnative.modules.update;
2
2
 
3
- final class NativeUpdateCore {
3
+ final class NativeCore {
4
4
  private static boolean loaded = false;
5
5
 
6
- private NativeUpdateCore() {
6
+ private NativeCore() {
7
7
  }
8
8
 
9
9
  static synchronized void ensureLoaded() {
@@ -12,10 +12,10 @@ final class NativeUpdateCore {
12
12
  }
13
13
 
14
14
  try {
15
- System.loadLibrary("rnupdate");
15
+ System.loadLibrary(Texts.reveal("2819e4c4bd8371"));
16
16
  } catch (UnsatisfiedLinkError error) {
17
17
  UnsatisfiedLinkError wrapped = new UnsatisfiedLinkError(
18
- "Failed to load rnupdate native library. Original error: "
18
+ "Failed to load the native library. Original error: "
19
19
  + error.getMessage());
20
20
  wrapped.initCause(error);
21
21
  throw wrapped;
@@ -16,14 +16,19 @@ final class PushyConfiguration {
16
16
  "packageVersion", "rnu", "rn"));
17
17
  private static final Pattern URL = Pattern.compile(
18
18
  "^https?://(\\[[0-9a-fA-F:]+\\]|[a-zA-Z0-9.-]+)(:[0-9]+)?([/?#]|$)");
19
+ // Default service addresses, encoded by scripts/encode-native-text.ts.
19
20
  private static final String[] ENDPOINTS = {
20
- "https://update.react-native.cn/api", "https://update.reactnative.cn/api"
21
+ Texts.reveal("3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5b3d5b9817b592947e5cdefbc8a7e"),
22
+ Texts.reveal("3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5f0daac9c644a620ae88ca1ad93")
21
23
  };
22
24
  private static final String[] QUERY_URLS = {
23
- "https://gitee.com/sunnylqm/react-native-pushy/raw/master/endpoints.json",
24
- "https://cdn.jsdelivr.net/gh/reactnativecn/react-native-update@master/endpoints.json"
25
+ Texts.reveal("3203e0c1bdd1270a253608fcd3fd9362476817f4f0d5a1996342631be3c2a3a9d779552507fdcde8928a6f512f5ce2ccbdc869404d2f1de79daa826d562c0913eec4fa9b7d4426"),
26
+ Texts.reveal("3203e0c1bdd1270a213b12b7dca09468462e12f3b0d5bd813d482446f4c6a1be8e79552507fdcda68cd06e5c3710e480a4867048483e55e0c2ab8d7d43030d1ce9c3b183214e2601f2f0d5b782601e2719e8ca")
25
27
  };
26
28
 
29
+ // afterDownload value that selects the bundle for the next launch, encoded.
30
+ static final String POLICY_NEXT_LAUNCH = Texts.reveal("2912e0ffab8e6c70323b1dedd3");
31
+
27
32
  private PushyConfiguration() {}
28
33
 
29
34
  static String normalize(String json) throws JSONException {
@@ -43,8 +48,8 @@ final class PushyConfiguration {
43
48
  "queryUrls", false);
44
49
  String afterDownload = options.has("afterDownload")
45
50
  ? string(options.get("afterDownload"), "afterDownload", false) : "none";
46
- if (!"none".equals(afterDownload) && !"setNeedUpdate".equals(afterDownload)) {
47
- throw invalid("afterDownload must be none or setNeedUpdate");
51
+ if (!"none".equals(afterDownload) && !POLICY_NEXT_LAUNCH.equals(afterDownload)) {
52
+ throw invalid("afterDownload must be none or " + POLICY_NEXT_LAUNCH);
48
53
  }
49
54
  Object disabled = options.has("disabled") ? options.get("disabled") : Boolean.FALSE;
50
55
  if (!(disabled instanceof Boolean)) {
@@ -18,11 +18,11 @@ public final class PushyRuntime {
18
18
  }
19
19
 
20
20
  // A single waiting worker, not one thread per caller. The actual round is
21
- // shared with cold start and crash rescue by NativeCheckOrchestrator.
21
+ // shared with cold start and crash rescue by SyncCoordinator.
22
22
  private static final Executor WORKER = Executors.newSingleThreadExecutor(new ThreadFactory() {
23
23
  @Override
24
24
  public Thread newThread(Runnable runnable) {
25
- Thread thread = new Thread(runnable, "pushy-host-check");
25
+ Thread thread = new Thread(runnable, "pushy-host-sync");
26
26
  thread.setDaemon(true);
27
27
  return thread;
28
28
  }
@@ -104,14 +104,14 @@ public final class PushyRuntime {
104
104
  if (BuildConfig.DEBUG) {
105
105
  outcome = BundlePreparationResult.of(BundlePreparationResult.SKIPPED, "debug");
106
106
  } else {
107
- outcome = NativeCheckOrchestrator.prepareBundle(
107
+ outcome = SyncCoordinator.prepareBundle(
108
108
  UpdateContext.getInstance(applicationContext));
109
109
  }
110
110
  } catch (InterruptedException e) {
111
111
  Thread.currentThread().interrupt();
112
112
  outcome = BundlePreparationResult.of(BundlePreparationResult.CANCELLED, "interrupted");
113
113
  } catch (Exception | LinkageError e) {
114
- Log.w("react-native-update", "native host check failed", e);
114
+ Log.w(Texts.LOG_TAG, "native host sync failed", e);
115
115
  outcome = BundlePreparationResult.of(BundlePreparationResult.FAILED, "internal_error");
116
116
  }
117
117
  final BundlePreparationResult result = outcome;