react-native-update 10.59.1 → 10.60.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 (125) hide show
  1. package/README-CN.md +7 -7
  2. package/README.md +7 -6
  3. package/android/build.gradle +25 -33
  4. package/android/jni/Android.mk +16 -24
  5. package/android/lib/arm64-v8a/librnpushy.so +0 -0
  6. package/android/lib/armeabi-v7a/librnpushy.so +0 -0
  7. package/android/lib/x86/librnpushy.so +0 -0
  8. package/android/lib/x86_64/librnpushy.so +0 -0
  9. package/android/proguard.pro +9 -7
  10. package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +11 -12
  11. package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +23 -6
  12. package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +50 -51
  13. package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +12 -13
  14. package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +3 -0
  15. package/android/src/main/java/cn/reactnative/modules/update/CrashHold.java +22 -30
  16. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +181 -332
  17. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -1
  18. package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +27 -0
  19. package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +1 -1
  20. package/android/src/main/java/cn/reactnative/modules/update/FlowBridge.java +32 -8
  21. package/android/src/main/java/cn/reactnative/modules/update/HttpClients.java +63 -0
  22. package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +0 -11
  23. package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +66 -116
  24. package/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +9 -1
  25. package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +1 -1
  26. package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +32 -60
  27. package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +8 -23
  28. package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +42 -80
  29. package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +22 -39
  30. package/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +176 -299
  31. package/android/src/main/java/cn/reactnative/modules/update/Threads.java +35 -0
  32. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +242 -174
  33. package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +1 -1
  34. package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +75 -17
  35. package/android/src/main/java/cn/reactnative/modules/update/{UpdateModuleImpl.java → UpdateModule.java} +113 -95
  36. package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +16 -19
  37. package/android/src/oldarch/cn/reactnative/modules/update/NativePushySpec.java +69 -0
  38. package/cpp/android_jni/archive_limits_jni.cpp +83 -0
  39. package/cpp/android_jni/install_record_jni.cpp +89 -0
  40. package/cpp/android_jni/jni_log.h +31 -0
  41. package/cpp/android_jni/jni_natives.h +40 -0
  42. package/cpp/android_jni/jni_registration.cpp +133 -0
  43. package/cpp/android_jni/jni_util.h +160 -0
  44. package/cpp/android_jni/update_core_android.cpp +450 -0
  45. package/cpp/android_jni/update_flow_jni.cpp +133 -0
  46. package/{android/jni → cpp/hpatch}/hpatch.c +5 -5
  47. package/{android/jni → cpp/hpatch}/hpatch.h +4 -1
  48. package/cpp/patch_core/archive_limits.cpp +194 -0
  49. package/cpp/patch_core/archive_limits.h +99 -10
  50. package/cpp/patch_core/archive_patch_core.cpp +96 -12
  51. package/cpp/patch_core/archive_patch_core.h +49 -0
  52. package/cpp/patch_core/digest.h +5 -4
  53. package/cpp/patch_core/error_codes.h +5 -3
  54. package/cpp/patch_core/install_record.cpp +430 -0
  55. package/cpp/patch_core/install_record.h +163 -9
  56. package/cpp/patch_core/patch_core.cpp +62 -16
  57. package/cpp/patch_core/patch_core.h +27 -7
  58. package/cpp/patch_core/patch_manifest.cpp +201 -0
  59. package/cpp/patch_core/patch_manifest.h +74 -0
  60. package/cpp/patch_core/state_ops.h +3 -3
  61. package/cpp/update_flow_core/flow_json.cpp +82 -8
  62. package/cpp/update_flow_core/flow_json.h +21 -0
  63. package/cpp/update_flow_core/native_config.cpp +456 -0
  64. package/cpp/update_flow_core/native_config.h +84 -0
  65. package/cpp/update_flow_core/native_round.cpp +326 -0
  66. package/cpp/update_flow_core/native_round.h +90 -0
  67. package/cpp/update_flow_core/update_flow_core.h +2 -2
  68. package/harmony/hvigor-plugin.ts +11 -19
  69. package/harmony/pushy/src/main/cpp/CMakeLists.txt +19 -26
  70. package/harmony/pushy/src/main/cpp/PushyPackage.h +2 -2
  71. package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +55 -94
  72. package/harmony/pushy/src/main/cpp/pushy.cpp +694 -517
  73. package/harmony/pushy/src/main/ets/ArchiveLimits.ts +48 -72
  74. package/harmony/pushy/src/main/ets/DownloadTask.ts +164 -404
  75. package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +1 -3
  76. package/harmony/pushy/src/main/ets/ErrorCodes.ts +1 -14
  77. package/harmony/pushy/src/main/ets/EventHub.ts +1 -1
  78. package/harmony/pushy/src/main/ets/InstallRecord.ts +52 -151
  79. package/harmony/pushy/src/main/ets/Logger.ts +20 -34
  80. package/harmony/pushy/src/main/ets/NativePatchCore.ts +116 -42
  81. package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -85
  82. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -25
  83. package/harmony/pushy/src/main/ets/SaveFile.ts +99 -18
  84. package/harmony/pushy/src/main/ets/SyncCoordinator.ts +136 -335
  85. package/harmony/pushy/src/main/ets/Texts.ts +0 -4
  86. package/harmony/pushy/src/main/ets/UpdateContext.ts +106 -119
  87. package/harmony/pushy.har +0 -0
  88. package/ios/RCTPushy/RCTPushy+Launch.mm +185 -0
  89. package/ios/RCTPushy/RCTPushy.h +12 -0
  90. package/ios/RCTPushy/RCTPushy.mm +87 -2628
  91. package/ios/RCTPushy/RCTPushyCommon.h +34 -0
  92. package/ios/RCTPushy/RCTPushyConfiguration.h +0 -8
  93. package/ios/RCTPushy/RCTPushyConfiguration.mm +40 -121
  94. package/ios/RCTPushy/RCTPushyDownloader.mm +40 -37
  95. package/ios/RCTPushy/RCTPushyInstallRecord.mm +61 -0
  96. package/ios/RCTPushy/RCTPushyInstaller.mm +689 -0
  97. package/ios/RCTPushy/RCTPushyInternal.h +144 -0
  98. package/ios/RCTPushy/RCTPushyOrchestrator.mm +1001 -0
  99. package/ios/RCTPushy/RCTPushyState.mm +314 -0
  100. package/package.json +25 -23
  101. package/react-native-update.podspec +74 -83
  102. package/src/client.ts +361 -451
  103. package/src/context.ts +1 -1
  104. package/src/core.ts +35 -8
  105. package/src/endpoint.ts +0 -2
  106. package/src/error.ts +13 -32
  107. package/src/i18n.ts +18 -83
  108. package/src/locales/en.ts +0 -4
  109. package/src/locales/zh.ts +0 -4
  110. package/src/provider.tsx +60 -53
  111. package/src/telemetry.ts +0 -13
  112. package/src/type.ts +0 -15
  113. package/src/updateFlowCore.ts +116 -10
  114. package/src/utils.ts +49 -42
  115. package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +0 -6
  116. package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +0 -104
  117. package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +0 -37
  118. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +0 -121
  119. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +0 -125
  120. package/cpp/patch_core/jni_natives.h +0 -27
  121. package/cpp/patch_core/jni_registration.cpp +0 -103
  122. package/cpp/patch_core/jni_util.h +0 -60
  123. package/cpp/patch_core/patch_core_android.cpp +0 -97
  124. package/cpp/patch_core/update_core_android.cpp +0 -485
  125. package/cpp/update_flow_core/update_flow_jni.cpp +0 -150
@@ -39,7 +39,7 @@ final class UpdateEventEmitter {
39
39
  // emit (this SDK itself triggers reloads); sendEvent runs on the
40
40
  // main thread, so a throw here would crash the app for a lost
41
41
  // progress tick.
42
- Log.w("pushy", "sendEvent " + eventName + " failed: " + e.getMessage());
42
+ Log.w(Texts.LOG_TAG, "sendEvent " + eventName + " failed: " + e.getMessage());
43
43
  }
44
44
  }
45
45
 
@@ -1,13 +1,18 @@
1
1
  package cn.reactnative.modules.update;
2
2
 
3
+ import java.io.ByteArrayOutputStream;
3
4
  import java.io.File;
4
5
  import java.io.FileInputStream;
5
6
  import java.io.FileOutputStream;
6
7
  import java.io.IOException;
7
8
  import java.io.InputStream;
9
+ import java.io.OutputStream;
10
+ import java.nio.charset.StandardCharsets;
11
+ import java.security.MessageDigest;
8
12
 
9
13
  final class UpdateFileUtils {
10
- private static final int BUFFER_SIZE = 8192;
14
+ /** The one IO block size: stream copies, digests, downloads, unzip. */
15
+ static final int BUFFER_SIZE = 64 * 1024;
11
16
 
12
17
  private UpdateFileUtils() {
13
18
  }
@@ -57,27 +62,53 @@ final class UpdateFileUtils {
57
62
  }
58
63
  }
59
64
 
65
+ static MessageDigest newSha256() throws IOException {
66
+ try {
67
+ return MessageDigest.getInstance("SHA-256");
68
+ } catch (java.security.NoSuchAlgorithmException e) {
69
+ throw new IOException("SHA-256 unavailable", e);
70
+ }
71
+ }
72
+
73
+ /** Feeds the whole file into digest (streaming, bounded memory). */
74
+ static void updateDigest(MessageDigest digest, File file) throws IOException {
75
+ try (InputStream in = new FileInputStream(file)) {
76
+ updateDigest(digest, in);
77
+ }
78
+ }
79
+
80
+ /** Feeds the rest of the stream into digest; the caller closes it. */
81
+ static void updateDigest(MessageDigest digest, InputStream in) throws IOException {
82
+ byte[] buffer = new byte[BUFFER_SIZE];
83
+ int read;
84
+ while ((read = in.read(buffer)) != -1) {
85
+ digest.update(buffer, 0, read);
86
+ }
87
+ }
88
+
89
+ static String toHex(byte[] digest) {
90
+ StringBuilder hex = new StringBuilder(digest.length * 2);
91
+ for (byte b : digest) {
92
+ hex.append(Character.forDigit((b >> 4) & 0xf, 16));
93
+ hex.append(Character.forDigit(b & 0xf, 16));
94
+ }
95
+ return hex.toString();
96
+ }
97
+
60
98
  /** Streaming SHA-256 of a file, lowercase hex. */
61
99
  static String sha256Hex(File file) throws IOException {
62
100
  try (InputStream in = new FileInputStream(file)) {
63
- java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
64
- byte[] buffer = new byte[64 * 1024];
65
- int read;
66
- while ((read = in.read(buffer)) != -1) {
67
- md.update(buffer, 0, read);
68
- }
69
- byte[] digest = md.digest();
70
- StringBuilder hex = new StringBuilder(digest.length * 2);
71
- for (byte b : digest) {
72
- hex.append(Character.forDigit((b >> 4) & 0xf, 16));
73
- hex.append(Character.forDigit(b & 0xf, 16));
74
- }
75
- return hex.toString();
76
- } catch (java.security.NoSuchAlgorithmException e) {
77
- throw new IOException("SHA-256 unavailable", e);
101
+ return sha256Hex(in);
78
102
  }
79
103
  }
80
104
 
105
+ /** Streaming SHA-256 of the rest of the stream, lowercase hex; the caller closes it. */
106
+ static String sha256Hex(InputStream in) throws IOException {
107
+ MessageDigest md = newSha256();
108
+ updateDigest(md, in);
109
+ return toHex(md.digest());
110
+ }
111
+
81
112
  static void copyFile(File from, File to) throws IOException {
82
113
  ensureParentDirectory(to);
83
114
  try (
@@ -95,11 +126,38 @@ final class UpdateFileUtils {
95
126
  }
96
127
  }
97
128
 
98
- private static void copy(InputStream in, FileOutputStream out) throws IOException {
129
+ /** Copies the rest of the stream; the caller closes both ends. */
130
+ static void copy(InputStream in, OutputStream out) throws IOException {
99
131
  byte[] buffer = new byte[BUFFER_SIZE];
100
132
  int count;
101
133
  while ((count = in.read(buffer)) != -1) {
102
134
  out.write(buffer, 0, count);
103
135
  }
104
136
  }
137
+
138
+ /**
139
+ * Reads the whole stream (and closes it), failing once it exceeds
140
+ * maxBytes: small server- or archive-provided documents must not be
141
+ * able to exhaust memory.
142
+ */
143
+ static byte[] readBytes(InputStream input, long maxBytes) throws IOException {
144
+ try (InputStream in = input; ByteArrayOutputStream out = new ByteArrayOutputStream()) {
145
+ byte[] buffer = new byte[BUFFER_SIZE];
146
+ int count;
147
+ long total = 0;
148
+ while ((count = in.read(buffer)) != -1) {
149
+ total += count;
150
+ if (total > maxBytes) {
151
+ throw new IOException("content exceeds " + maxBytes + " bytes");
152
+ }
153
+ out.write(buffer, 0, count);
154
+ }
155
+ return out.toByteArray();
156
+ }
157
+ }
158
+
159
+ /** A small UTF-8 text file (a record, a sidecar), at most maxBytes long. */
160
+ static String readSmallUtf8(File file, long maxBytes) throws IOException {
161
+ return new String(readBytes(new FileInputStream(file), maxBytes), StandardCharsets.UTF_8);
162
+ }
105
163
  }
@@ -1,9 +1,13 @@
1
1
  package cn.reactnative.modules.update;
2
2
 
3
+ import static cn.reactnative.modules.update.StateSerialRunner.STATE_SERIAL;
4
+ import static cn.reactnative.modules.update.StateSerialRunner.UI_THREAD;
5
+
3
6
  import android.util.Log;
4
7
  import androidx.annotation.Nullable;
5
8
  import com.facebook.react.bridge.Promise;
6
9
  import com.facebook.react.bridge.ReactApplicationContext;
10
+ import com.facebook.react.bridge.ReactMethod;
7
11
  import com.facebook.react.bridge.ReadableMap;
8
12
  import java.util.HashMap;
9
13
  import java.util.Map;
@@ -12,32 +16,33 @@ import org.json.JSONObject;
12
16
  import org.json.JSONTokener;
13
17
 
14
18
  /**
15
- * Everything the "Pushy" native module does, independent of the bridge it is
16
- * registered with. The old-architecture and new-architecture UpdateModule
17
- * classes are thin wrappers that only carry their annotations / spec
18
- * overrides and forward here, so the two cannot drift apart.
19
+ * The "Pushy" native module for both architectures. NativePushySpec is the
20
+ * codegen spec under the new architecture and the hand-written stand-in in
21
+ * src/oldarch otherwise; the old bridge discovers methods through the
22
+ * @ReactMethod annotations here, so every spec method carries one. Each
23
+ * state-changing method takes a trailing Promise, so a failed persistence
24
+ * rejects instead of resolving early (CODE_AUDIT 2.3).
19
25
  */
20
- public class UpdateModuleImpl {
26
+ public class UpdateModule extends NativePushySpec {
21
27
  // Download URL option key, stored encoded.
22
28
  private static final String OPTION_URL = Texts.reveal("2f07f0d0ba8e5d572e");
23
29
 
24
-
25
- public static final String NAME = "Pushy";
26
-
27
30
  private final ReactApplicationContext reactContext;
28
31
  private final UpdateContext updateContext;
29
32
 
30
- public UpdateModuleImpl(ReactApplicationContext reactContext) {
33
+ public UpdateModule(ReactApplicationContext reactContext) {
31
34
  this(reactContext, UpdateContext.getInstance(reactContext));
32
35
  }
33
36
 
34
- public UpdateModuleImpl(ReactApplicationContext reactContext, UpdateContext updateContext) {
37
+ public UpdateModule(ReactApplicationContext reactContext, UpdateContext updateContext) {
38
+ super(reactContext);
35
39
  this.reactContext = reactContext;
36
40
  this.updateContext = updateContext;
37
41
  UpdateEventEmitter.register(reactContext);
38
42
  }
39
43
 
40
- public Map<String, Object> getConstants() {
44
+ @Override
45
+ protected Map<String, Object> getTypedExportedConstants() {
41
46
  final Map<String, Object> constants = new HashMap<String, Object>();
42
47
  constants.put("downloadRootDir", updateContext.getRootDir());
43
48
  constants.put("packageVersion", updateContext.getPackageVersion());
@@ -45,6 +50,8 @@ public class UpdateModuleImpl {
45
50
  String currentVersion = updateContext.getCurrentVersion();
46
51
  constants.put("currentVersion", currentVersion);
47
52
  constants.put("currentVersionInfo", updateContext.getKv("hash_" + currentVersion));
53
+ // Served from the value cached at launch resolution (no file read on
54
+ // the JS thread) when currentVersion is the launched version.
48
55
  constants.put("currentBundleSha256", updateContext.currentBundleSha256(currentVersion));
49
56
  constants.put("buildTime", updateContext.getBuildTime());
50
57
  constants.put("isUsingBundleUrl", updateContext.getIsUsingBundleUrl());
@@ -63,7 +70,7 @@ public class UpdateModuleImpl {
63
70
  // A mismatched librnpushy.so (stale manual copy / build cache)
64
71
  // must not crash startup via getConstants; 0 simply means "no v2
65
72
  // diff track" and the server degrades gracefully.
66
- Log.e("pushy", "supportedDiffVersion missing from the native library", e);
73
+ Log.e(Texts.LOG_TAG, "supportedDiffVersion missing from the native library", e);
67
74
  }
68
75
  constants.put("supportedDiffVersion", supportedDiffVersion);
69
76
  return constants;
@@ -127,26 +134,13 @@ public class UpdateModuleImpl {
127
134
 
128
135
  @Override
129
136
  public void onDownloadFailed(Throwable error) {
130
- promise.reject(downloadErrorCode(error), error);
137
+ promise.reject(ErrorCodes.forDownloadFailure(error), error);
131
138
  }
132
139
  };
133
140
  }
134
141
 
135
- // Post-download failures (unzip / hdiff / resource copy, incl. copiesCrc
136
- // verification) reject as PATCH_FAILED so JS-side telemetry can separate
137
- // patch health from network health; a local refusal (reinstalling the
138
- // running version in place) as FILE_OPERATION_FAILED. DownloadTask
139
- // classifies them.
140
- private static String downloadErrorCode(Throwable error) {
141
- if (error instanceof PatchFailedException) {
142
- return ErrorCodes.DELTA_FAILED;
143
- }
144
- if (error instanceof FileOperationException) {
145
- return ErrorCodes.FILE_OPERATION_FAILED;
146
- }
147
- return ErrorCodes.DOWNLOAD_FAILED;
148
- }
149
-
142
+ @ReactMethod
143
+ @Override
150
144
  public void downloadFullUpdate(final ReadableMap options, final Promise promise) {
151
145
  String url = readRequiredString(options, OPTION_URL, promise);
152
146
  if (url == null) {
@@ -156,9 +150,11 @@ public class UpdateModuleImpl {
156
150
  if (hash == null) {
157
151
  return;
158
152
  }
159
- updateContext.downloadFullUpdate(url, hash, downloadListener(promise));
153
+ updateContext.downloadFullUpdate(url, hash, downloadListener(promise), 0);
160
154
  }
161
155
 
156
+ @ReactMethod
157
+ @Override
162
158
  public void downloadAndInstallApk(final ReadableMap options, final Promise promise) {
163
159
  // Every failure of this path — a missing REQUEST_INSTALL_PACKAGES
164
160
  // declaration above all — has to come back as a promise rejection,
@@ -203,6 +199,8 @@ public class UpdateModuleImpl {
203
199
  }
204
200
  }
205
201
 
202
+ @ReactMethod
203
+ @Override
206
204
  public void downloadPatchFromPackage(final ReadableMap options, final Promise promise) {
207
205
  String url = readRequiredString(options, OPTION_URL, promise);
208
206
  if (url == null) {
@@ -212,9 +210,11 @@ public class UpdateModuleImpl {
212
210
  if (hash == null) {
213
211
  return;
214
212
  }
215
- updateContext.downloadPatchFromApk(url, hash, downloadListener(promise));
213
+ updateContext.downloadPatchFromApk(url, hash, downloadListener(promise), 0);
216
214
  }
217
215
 
216
+ @ReactMethod
217
+ @Override
218
218
  public void downloadPatchFromPpk(final ReadableMap options, final Promise promise) {
219
219
  String url = readRequiredString(options, OPTION_URL, promise);
220
220
  if (url == null) {
@@ -228,79 +228,80 @@ public class UpdateModuleImpl {
228
228
  if (originHash == null) {
229
229
  return;
230
230
  }
231
- updateContext.downloadPatchFromPpk(url, hash, originHash, downloadListener(promise));
231
+ updateContext.downloadPatchFromPpk(url, hash, originHash, downloadListener(promise), 0);
232
232
  }
233
233
 
234
+ @ReactMethod
235
+ @Override
234
236
  public void reloadUpdate(final ReadableMap options, final Promise promise) {
235
237
  String hash = readRequiredString(options, "hash", promise);
236
238
  if (hash == null) {
237
239
  return;
238
240
  }
239
- restartApp(hash, promise);
241
+ restart(hash, promise);
240
242
  }
241
243
 
242
- public void restartApp(@Nullable final String hash, final Promise promise) {
244
+ @ReactMethod
245
+ @Override
246
+ public void restartApp(Promise promise) {
247
+ restart(null, promise);
248
+ }
249
+
250
+ private void restart(@Nullable final String hash, final Promise promise) {
243
251
  // Two hops (CODE_AUDIT 2.2): the switch (bundle digest + commit) and
244
252
  // the launch resolution run on the state serial thread like every
245
- // other state write; only the React reload itself is posted to the
246
- // UI thread. A failure at either hop rejects with RESTART_FAILED.
247
- StateSerialRunner.run(promise, ErrorCodes.RESTART_FAILED, "restartApp", new StateSerialRunner.Operation() {
248
- @Override
249
- public void run() throws Throwable {
250
- final String bundlePath = ReactReloadManager.prepareRestart(updateContext, hash);
251
- UiThreadRunner.run(promise, ErrorCodes.RESTART_FAILED, "restartApp", new UiThreadRunner.Operation() {
252
- @Override
253
- public void run() throws Throwable {
254
- ReactReloadManager.reload(updateContext, reactContext, bundlePath);
255
- promise.resolve(true);
256
- }
257
- });
253
+ // other state write — nothing there touches React; only the React
254
+ // reload itself is posted to the UI thread. A failure at either hop
255
+ // rejects with RESTART_FAILED.
256
+ StateSerialRunner.run(STATE_SERIAL, promise, ErrorCodes.RESTART_FAILED, "restartApp", () -> {
257
+ if (hash != null) {
258
+ updateContext.switchVersion(hash);
258
259
  }
260
+ // The bundle to load; null for the packaged one.
261
+ final String bundlePath = updateContext.getBundleUrl();
262
+ StateSerialRunner.run(UI_THREAD, promise, ErrorCodes.RESTART_FAILED, "restartApp", () -> {
263
+ ReactReloadManager.reload(updateContext, reactContext, bundlePath);
264
+ promise.resolve(true);
265
+ });
259
266
  });
260
267
  }
261
268
 
269
+ @ReactMethod
270
+ @Override
262
271
  public void setNeedUpdate(final ReadableMap options, final Promise promise) {
263
272
  final String hash = readRequiredString(options, "hash", promise);
264
273
  if (hash == null) {
265
274
  return;
266
275
  }
267
- StateSerialRunner.run(promise, ErrorCodes.SWITCH_VERSION_FAILED, "switchVersionLater", new StateSerialRunner.Operation() {
268
- @Override
269
- public void run() {
270
- updateContext.switchVersion(hash);
271
- promise.resolve(true);
272
- }
276
+ StateSerialRunner.run(STATE_SERIAL, promise, ErrorCodes.SWITCH_VERSION_FAILED, "switchVersionLater", () -> {
277
+ updateContext.switchVersion(hash);
278
+ promise.resolve(true);
273
279
  });
274
280
  }
275
281
 
282
+ @ReactMethod
283
+ @Override
276
284
  public void markSuccess(final Promise promise) {
277
- StateSerialRunner.run(promise, ErrorCodes.MARK_SUCCESS_FAILED, "markSuccess", new StateSerialRunner.Operation() {
278
- @Override
279
- public void run() {
280
- updateContext.markSuccess();
281
- promise.resolve(true);
282
- }
285
+ StateSerialRunner.run(STATE_SERIAL, promise, ErrorCodes.MARK_SUCCESS_FAILED, "markSuccess", () -> {
286
+ updateContext.markSuccess();
287
+ promise.resolve(true);
283
288
  });
284
289
  }
285
290
 
291
+ @ReactMethod
292
+ @Override
286
293
  public void getBundleHash(final Promise promise) {
287
- // Threading lives in UpdateContext (download executor); resolve is
294
+ // Threading lives in UpdateContext (its bundle-hash thread); resolve is
288
295
  // thread-safe. Never rejects — empty string means "unknown".
289
- updateContext.getBundleHash(new UpdateContext.BundleHashListener() {
290
- @Override
291
- public void onBundleHash(String hash) {
292
- promise.resolve(hash);
293
- }
294
- });
296
+ updateContext.getBundleHash(promise::resolve);
295
297
  }
296
298
 
299
+ @ReactMethod
300
+ @Override
297
301
  public void resetToPackagedBundle(final Promise promise) {
298
- StateSerialRunner.run(promise, ErrorCodes.RESET_FAILED, "resetToPackagedBundle", new StateSerialRunner.Operation() {
299
- @Override
300
- public void run() {
301
- updateContext.resetToPackagedBundle();
302
- promise.resolve(true);
303
- }
302
+ StateSerialRunner.run(STATE_SERIAL, promise, ErrorCodes.RESET_FAILED, "resetToPackagedBundle", () -> {
303
+ updateContext.resetToPackagedBundle();
304
+ promise.resolve(true);
304
305
  });
305
306
  }
306
307
 
@@ -309,18 +310,19 @@ public class UpdateModuleImpl {
309
310
  * reuse instead of re-checking (§10.3). Empty string when absent; never
310
311
  * rejects.
311
312
  */
313
+ @ReactMethod
314
+ @Override
312
315
  public void getNativeCheckCache(final Promise promise) {
313
- String cached = updateContext.getKv(SyncCoordinator.KEY_RESP_CACHE);
316
+ String cached = updateContext.getNativeCheckCache();
314
317
  promise.resolve(cached == null ? "" : cached);
315
318
  }
316
319
 
317
320
  /**
318
- * Provisioning for the native cold-start update check
319
- * (NATIVE_CHECKUPDATE_DESIGN §10.1): the raw JSON persists as-is and is
320
- * parsed on read by the orchestrator; absent config = check disabled.
321
- * Validated at write time — a corrupt config would otherwise silently
322
- * disable the native check forever with no signal.
321
+ * JS obtained a valid check response for this config in this process; the
322
+ * delayed native round then skips its duplicate request (§10.3).
323
323
  */
324
+ @ReactMethod
325
+ @Override
324
326
  public void markJsCheckCompleted(final String config, final Promise promise) {
325
327
  if (requireNonEmpty(config, "config", promise) == null) {
326
328
  return;
@@ -329,6 +331,15 @@ public class UpdateModuleImpl {
329
331
  promise.resolve(true);
330
332
  }
331
333
 
334
+ /**
335
+ * Provisioning for the native cold-start update check
336
+ * (NATIVE_CHECKUPDATE_DESIGN §10.1): the raw JSON persists as-is and is
337
+ * parsed on read by the orchestrator; absent config = check disabled.
338
+ * Validated at write time — a corrupt config would otherwise silently
339
+ * disable the native check forever with no signal.
340
+ */
341
+ @ReactMethod
342
+ @Override
332
343
  public void syncNativeConfig(final String config, final Promise promise) {
333
344
  if (requireNonEmpty(config, "config", promise) == null) {
334
345
  return;
@@ -339,28 +350,26 @@ public class UpdateModuleImpl {
339
350
  promise.reject(ErrorCodes.INVALID_OPTIONS, "config must be a JSON object string", e);
340
351
  return;
341
352
  }
342
- StateSerialRunner.run(promise, ErrorCodes.FILE_OPERATION_FAILED, "syncNativeConfig", new StateSerialRunner.Operation() {
343
- @Override
344
- public void run() {
345
- updateContext.setNativeConfig(config);
346
- promise.resolve(true);
347
- }
353
+ StateSerialRunner.run(STATE_SERIAL, promise, ErrorCodes.FILE_OPERATION_FAILED, "syncNativeConfig", () -> {
354
+ updateContext.setNativeConfig(config);
355
+ promise.resolve(true);
348
356
  });
349
357
  }
350
358
 
359
+ @ReactMethod
360
+ @Override
351
361
  public void setUuid(final String uuid, final Promise promise) {
352
362
  if (requireNonEmpty(uuid, "uuid", promise) == null) {
353
363
  return;
354
364
  }
355
- StateSerialRunner.run(promise, ErrorCodes.FILE_OPERATION_FAILED, "setUuid", new StateSerialRunner.Operation() {
356
- @Override
357
- public void run() {
358
- updateContext.setKv("uuid", uuid);
359
- promise.resolve(true);
360
- }
365
+ StateSerialRunner.run(STATE_SERIAL, promise, ErrorCodes.FILE_OPERATION_FAILED, "setUuid", () -> {
366
+ updateContext.setKv("uuid", uuid);
367
+ promise.resolve(true);
361
368
  });
362
369
  }
363
370
 
371
+ @ReactMethod
372
+ @Override
364
373
  public void setLocalHashInfo(final String hash, final String info, final Promise promise) {
365
374
  if (requireNonEmpty(hash, "hash", promise) == null) {
366
375
  return;
@@ -371,15 +380,14 @@ public class UpdateModuleImpl {
371
380
  }
372
381
  // A state write that did not reach disk (SharedPreferences commit
373
382
  // false) is FILE_OPERATION_FAILED, distinct from malformed input.
374
- StateSerialRunner.run(promise, ErrorCodes.FILE_OPERATION_FAILED, "setLocalHashInfo", new StateSerialRunner.Operation() {
375
- @Override
376
- public void run() {
377
- updateContext.setKv("hash_" + hash, info);
378
- promise.resolve(true);
379
- }
383
+ StateSerialRunner.run(STATE_SERIAL, promise, ErrorCodes.FILE_OPERATION_FAILED, "setLocalHashInfo", () -> {
384
+ updateContext.setKv("hash_" + hash, info);
385
+ promise.resolve(true);
380
386
  });
381
387
  }
382
388
 
389
+ @ReactMethod
390
+ @Override
383
391
  public void getLocalHashInfo(final String hash, final Promise promise) {
384
392
  if (requireNonEmpty(hash, "hash", promise) == null) {
385
393
  return;
@@ -398,4 +406,14 @@ public class UpdateModuleImpl {
398
406
 
399
407
  promise.resolve(value);
400
408
  }
409
+
410
+ @ReactMethod
411
+ @Override
412
+ public void addListener(String eventName) {
413
+ }
414
+
415
+ @ReactMethod
416
+ @Override
417
+ public void removeListeners(double count) {
418
+ }
401
419
  }
@@ -18,29 +18,26 @@ public class UpdatePackage extends TurboReactPackage {
18
18
  @Nullable
19
19
  @Override
20
20
  public NativeModule getModule(String name, ReactApplicationContext reactContext) {
21
- return name.equals(UpdateModuleImpl.NAME) ? new UpdateModule(reactContext) : null;
21
+ return name.equals(UpdateModule.NAME) ? new UpdateModule(reactContext) : null;
22
22
  }
23
23
 
24
24
  @Override
25
25
  public ReactModuleInfoProvider getReactModuleInfoProvider() {
26
- return new ReactModuleInfoProvider() {
27
- @Override
28
- public Map<String, ReactModuleInfo> getReactModuleInfos() {
29
- final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
30
- boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
31
- moduleInfos.put(
32
- UpdateModuleImpl.NAME,
33
- new ReactModuleInfo(
34
- UpdateModuleImpl.NAME,
35
- UpdateModuleImpl.NAME,
36
- false, // canOverrideExistingModule
37
- false, // needsEagerInit
38
- true, // hasConstants
39
- false, // isCxxModule
40
- isTurboModule // isTurboModule
41
- ));
42
- return moduleInfos;
43
- }
26
+ return () -> {
27
+ final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
28
+ boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
29
+ moduleInfos.put(
30
+ UpdateModule.NAME,
31
+ new ReactModuleInfo(
32
+ UpdateModule.NAME,
33
+ UpdateModule.NAME,
34
+ false, // canOverrideExistingModule
35
+ false, // needsEagerInit
36
+ true, // hasConstants
37
+ false, // isCxxModule
38
+ isTurboModule // isTurboModule
39
+ ));
40
+ return moduleInfos;
44
41
  };
45
42
  }
46
43
  }
@@ -0,0 +1,69 @@
1
+ package cn.reactnative.modules.update;
2
+
3
+ import com.facebook.react.bridge.Promise;
4
+ import com.facebook.react.bridge.ReactApplicationContext;
5
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
6
+ import com.facebook.react.bridge.ReadableMap;
7
+ import java.util.Map;
8
+
9
+ /**
10
+ * Old-architecture stand-in for the NativePushySpec that codegen generates
11
+ * from src/NativePushy.ts under the new architecture: same name, methods and
12
+ * constants hook, so a single UpdateModule serves both. The bridge reads the
13
+ * @ReactMethod annotations from UpdateModule itself.
14
+ */
15
+ public abstract class NativePushySpec extends ReactContextBaseJavaModule {
16
+ public static final String NAME = "Pushy";
17
+
18
+ public NativePushySpec(ReactApplicationContext reactContext) {
19
+ super(reactContext);
20
+ }
21
+
22
+ @Override
23
+ public String getName() {
24
+ return NAME;
25
+ }
26
+
27
+ protected abstract Map<String, Object> getTypedExportedConstants();
28
+
29
+ @Override
30
+ public final Map<String, Object> getConstants() {
31
+ return getTypedExportedConstants();
32
+ }
33
+
34
+ public abstract void setLocalHashInfo(String hash, String info, Promise promise);
35
+
36
+ public abstract void getLocalHashInfo(String hash, Promise promise);
37
+
38
+ public abstract void setUuid(String uuid, Promise promise);
39
+
40
+ public abstract void syncNativeConfig(String config, Promise promise);
41
+
42
+ public abstract void getNativeCheckCache(Promise promise);
43
+
44
+ public abstract void markJsCheckCompleted(String config, Promise promise);
45
+
46
+ public abstract void reloadUpdate(ReadableMap options, Promise promise);
47
+
48
+ public abstract void restartApp(Promise promise);
49
+
50
+ public abstract void setNeedUpdate(ReadableMap options, Promise promise);
51
+
52
+ public abstract void markSuccess(Promise promise);
53
+
54
+ public abstract void getBundleHash(Promise promise);
55
+
56
+ public abstract void resetToPackagedBundle(Promise promise);
57
+
58
+ public abstract void downloadPatchFromPpk(ReadableMap options, Promise promise);
59
+
60
+ public abstract void downloadPatchFromPackage(ReadableMap options, Promise promise);
61
+
62
+ public abstract void downloadFullUpdate(ReadableMap options, Promise promise);
63
+
64
+ public abstract void downloadAndInstallApk(ReadableMap options, Promise promise);
65
+
66
+ public abstract void addListener(String eventName);
67
+
68
+ public abstract void removeListeners(double count);
69
+ }