react-native-update 10.55.1 → 10.56.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.
- package/README-CN.md +35 -0
- package/README.md +37 -0
- package/android/build.gradle +53 -27
- package/android/jni/Application.mk +6 -1
- package/android/jni/hpatch.c +59 -4
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/proguard.pro +70 -14
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +15 -8
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +1 -0
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +1 -2
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +25 -0
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -34
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +29 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +15 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +59 -0
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +27 -1
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +57 -16
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +38 -46
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +279 -183
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +14 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +222 -215
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +5 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +23 -25
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +29 -29
- package/cpp/patch_core/archive_limits.h +6 -0
- package/cpp/patch_core/jni_util.h +6 -2
- package/cpp/patch_core/patch_core.cpp +237 -84
- package/cpp/patch_core/patch_core.h +11 -0
- package/cpp/patch_core/patch_core_android.cpp +49 -33
- package/cpp/patch_core/update_core_android.cpp +134 -103
- package/cpp/update_flow_core/flow_json.cpp +150 -17
- package/cpp/update_flow_core/flow_json.h +13 -1
- package/cpp/update_flow_core/update_flow_core.cpp +78 -8
- package/cpp/update_flow_core/update_flow_core.h +9 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +44 -25
- package/harmony/hvigor-plugin.ts +168 -21
- package/harmony/pushy/src/README.md +74 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +13 -3
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +11 -5
- package/harmony/pushy/src/main/cpp/pushy.cpp +103 -18
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +61 -5
- package/harmony/pushy/src/main/ets/DownloadTask.ts +255 -166
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +75 -0
- package/harmony/pushy/src/main/ets/EventHub.ts +22 -20
- package/harmony/pushy/src/main/ets/InstallRecord.ts +79 -21
- package/harmony/pushy/src/main/ets/Logger.ts +42 -16
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +98 -17
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +10 -3
- package/harmony/pushy/src/main/ets/PathUtils.ts +9 -3
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +139 -53
- package/harmony/pushy/src/main/ets/UpdateContext.ts +269 -159
- package/harmony/pushy/src/main/module.json5 +5 -1
- package/harmony/pushy.har +0 -0
- package/ios/PrivacyInfo.xcprivacy +79 -0
- package/ios/RCTPushy/RCTPushy.mm +399 -170
- package/ios/RCTPushy/RCTPushyDownloader.h +14 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +136 -11
- package/package.json +14 -24
- package/react-native-update.podspec +43 -3
- package/src/client.ts +236 -82
- package/src/context.ts +40 -27
- package/src/core.ts +41 -16
- package/src/endpoint.ts +2 -1
- package/src/error.ts +49 -11
- package/src/index.ts +14 -0
- package/src/locales/en.ts +1 -23
- package/src/locales/zh.ts +1 -22
- package/src/provider.tsx +159 -111
- package/src/type.ts +0 -2
- package/src/updateFlowCore.ts +10 -2
- package/src/utils.ts +61 -4
- package/android/src/main/java/cn/reactnative/modules/update/ReactNativeHostHandler.java +0 -13
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +0 -45
|
@@ -12,6 +12,20 @@ final class UpdateFileUtils {
|
|
|
12
12
|
private UpdateFileUtils() {
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// Server-provided identifiers (hash/originHash/fileName) become child
|
|
16
|
+
// names under the update root; anything that could resolve outside of it
|
|
17
|
+
// (path separators, "..", ".") must be rejected before touching the
|
|
18
|
+
// filesystem.
|
|
19
|
+
static boolean isSafePathComponent(String name) {
|
|
20
|
+
return name != null
|
|
21
|
+
&& !name.isEmpty()
|
|
22
|
+
&& !name.equals(".")
|
|
23
|
+
&& !name.equals("..")
|
|
24
|
+
&& !name.contains("/")
|
|
25
|
+
&& !name.contains("\\")
|
|
26
|
+
&& name.indexOf('\0') < 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
static void ensureDirectory(File directory) throws IOException {
|
|
16
30
|
if (!directory.exists() && !directory.mkdirs() && !directory.exists()) {
|
|
17
31
|
throw new IOException("Failed to create directory: " + directory);
|
|
@@ -1,18 +1,107 @@
|
|
|
1
1
|
package cn.reactnative.modules.update;
|
|
2
2
|
|
|
3
|
+
import android.util.Log;
|
|
3
4
|
import androidx.annotation.Nullable;
|
|
4
5
|
import com.facebook.react.bridge.Promise;
|
|
5
6
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
6
7
|
import com.facebook.react.bridge.ReadableMap;
|
|
8
|
+
import java.util.HashMap;
|
|
9
|
+
import java.util.Map;
|
|
7
10
|
import org.json.JSONException;
|
|
8
11
|
import org.json.JSONObject;
|
|
9
12
|
import org.json.JSONTokener;
|
|
10
13
|
|
|
14
|
+
/**
|
|
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
|
+
*/
|
|
11
20
|
public class UpdateModuleImpl {
|
|
12
21
|
|
|
13
22
|
public static final String NAME = "Pushy";
|
|
14
23
|
|
|
15
|
-
private
|
|
24
|
+
private final ReactApplicationContext reactContext;
|
|
25
|
+
private final UpdateContext updateContext;
|
|
26
|
+
|
|
27
|
+
public UpdateModuleImpl(ReactApplicationContext reactContext) {
|
|
28
|
+
this(reactContext, UpdateContext.getInstance(reactContext));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public UpdateModuleImpl(ReactApplicationContext reactContext, UpdateContext updateContext) {
|
|
32
|
+
this.reactContext = reactContext;
|
|
33
|
+
this.updateContext = updateContext;
|
|
34
|
+
UpdateEventEmitter.register(reactContext);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public Map<String, Object> getConstants() {
|
|
38
|
+
final Map<String, Object> constants = new HashMap<String, Object>();
|
|
39
|
+
constants.put("downloadRootDir", updateContext.getRootDir());
|
|
40
|
+
constants.put("packageVersion", updateContext.getPackageVersion());
|
|
41
|
+
|
|
42
|
+
String currentVersion = updateContext.getCurrentVersion();
|
|
43
|
+
constants.put("currentVersion", currentVersion);
|
|
44
|
+
constants.put("currentVersionInfo", updateContext.getKv("hash_" + currentVersion));
|
|
45
|
+
constants.put("currentBundleSha256", updateContext.currentBundleSha256(currentVersion));
|
|
46
|
+
constants.put("buildTime", updateContext.getBuildTime());
|
|
47
|
+
constants.put("isUsingBundleUrl", updateContext.getIsUsingBundleUrl());
|
|
48
|
+
|
|
49
|
+
// Both one-shot markers are consumed in a single commit: under
|
|
50
|
+
// TurboModules this runs synchronously on the JS thread.
|
|
51
|
+
UpdateContext.LaunchMarkers markers = updateContext.consumeLaunchMarkers();
|
|
52
|
+
constants.put("isFirstTime", markers.isFirstTime);
|
|
53
|
+
constants.put("rolledBackVersion", markers.rolledBackVersion);
|
|
54
|
+
|
|
55
|
+
constants.put("uuid", updateContext.getKv("uuid"));
|
|
56
|
+
int supportedDiffVersion = 0;
|
|
57
|
+
try {
|
|
58
|
+
supportedDiffVersion = NativeUpdateCore.supportedDiffVersion();
|
|
59
|
+
} catch (UnsatisfiedLinkError e) {
|
|
60
|
+
// A mismatched librnupdate.so (stale manual copy / build cache)
|
|
61
|
+
// must not crash startup via getConstants; 0 simply means "no v2
|
|
62
|
+
// diff track" and the server degrades gracefully.
|
|
63
|
+
Log.e("pushy", "supportedDiffVersion missing from librnupdate.so", e);
|
|
64
|
+
}
|
|
65
|
+
constants.put("supportedDiffVersion", supportedDiffVersion);
|
|
66
|
+
return constants;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Reads a required string option. A module call runs on the native
|
|
71
|
+
* modules thread, where anything thrown (a missing key, a wrong type)
|
|
72
|
+
* reaches the bridge's exception handler and kills the app, so a bad
|
|
73
|
+
* argument has to come back as an INVALID_OPTIONS rejection instead
|
|
74
|
+
* (CODE_AUDIT 2.11). Returns null after rejecting.
|
|
75
|
+
*/
|
|
76
|
+
@Nullable
|
|
77
|
+
private static String readRequiredString(
|
|
78
|
+
@Nullable ReadableMap options,
|
|
79
|
+
String key,
|
|
80
|
+
Promise promise
|
|
81
|
+
) {
|
|
82
|
+
String value = null;
|
|
83
|
+
try {
|
|
84
|
+
if (options != null && options.hasKey(key) && !options.isNull(key)) {
|
|
85
|
+
value = options.getString(key);
|
|
86
|
+
}
|
|
87
|
+
} catch (RuntimeException e) {
|
|
88
|
+
// Wrong type: treated like a missing key below.
|
|
89
|
+
}
|
|
90
|
+
return requireNonEmpty(value, "options." + key, promise);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Same contract for a plain string argument. */
|
|
94
|
+
@Nullable
|
|
95
|
+
private static String requireNonEmpty(
|
|
96
|
+
@Nullable String value,
|
|
97
|
+
String name,
|
|
98
|
+
Promise promise
|
|
99
|
+
) {
|
|
100
|
+
if (value == null || value.isEmpty()) {
|
|
101
|
+
promise.reject(ErrorCodes.INVALID_OPTIONS, name + " must be a non-empty string");
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
16
105
|
}
|
|
17
106
|
|
|
18
107
|
private static boolean isValidHashInfo(@Nullable String json) {
|
|
@@ -26,14 +115,8 @@ public class UpdateModuleImpl {
|
|
|
26
115
|
}
|
|
27
116
|
}
|
|
28
117
|
|
|
29
|
-
|
|
30
|
-
UpdateContext
|
|
31
|
-
final ReadableMap options,
|
|
32
|
-
final Promise promise
|
|
33
|
-
) {
|
|
34
|
-
String url = options.getString("updateUrl");
|
|
35
|
-
String hash = options.getString("hash");
|
|
36
|
-
updateContext.downloadFullUpdate(url, hash, new UpdateContext.DownloadFileListener() {
|
|
118
|
+
private static UpdateContext.DownloadFileListener downloadListener(final Promise promise) {
|
|
119
|
+
return new UpdateContext.DownloadFileListener() {
|
|
37
120
|
@Override
|
|
38
121
|
public void onDownloadCompleted(DownloadTaskParams params) {
|
|
39
122
|
promise.resolve(null);
|
|
@@ -43,32 +126,61 @@ public class UpdateModuleImpl {
|
|
|
43
126
|
public void onDownloadFailed(Throwable error) {
|
|
44
127
|
promise.reject(downloadErrorCode(error), error);
|
|
45
128
|
}
|
|
46
|
-
}
|
|
129
|
+
};
|
|
47
130
|
}
|
|
48
131
|
|
|
49
132
|
// Post-download failures (unzip / hdiff / resource copy, incl. copiesCrc
|
|
50
133
|
// verification) reject as PATCH_FAILED so JS-side telemetry can separate
|
|
51
|
-
// patch health from network health
|
|
134
|
+
// patch health from network health; a local refusal (reinstalling the
|
|
135
|
+
// running version in place) as FILE_OPERATION_FAILED. DownloadTask
|
|
136
|
+
// classifies them.
|
|
52
137
|
private static String downloadErrorCode(Throwable error) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
138
|
+
if (error instanceof PatchFailedException) {
|
|
139
|
+
return ErrorCodes.PATCH_FAILED;
|
|
140
|
+
}
|
|
141
|
+
if (error instanceof FileOperationException) {
|
|
142
|
+
return ErrorCodes.FILE_OPERATION_FAILED;
|
|
143
|
+
}
|
|
144
|
+
return ErrorCodes.DOWNLOAD_FAILED;
|
|
56
145
|
}
|
|
57
146
|
|
|
58
|
-
public
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
147
|
+
public void downloadFullUpdate(final ReadableMap options, final Promise promise) {
|
|
148
|
+
String url = readRequiredString(options, "updateUrl", promise);
|
|
149
|
+
if (url == null) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
String hash = readRequiredString(options, "hash", promise);
|
|
153
|
+
if (hash == null) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
updateContext.downloadFullUpdate(url, hash, downloadListener(promise));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public void downloadAndInstallApk(final ReadableMap options, final Promise promise) {
|
|
160
|
+
// Every failure of this path — a missing REQUEST_INSTALL_PACKAGES
|
|
161
|
+
// declaration above all — has to come back as a promise rejection,
|
|
162
|
+
// never as an exception on the native modules thread.
|
|
68
163
|
try {
|
|
69
|
-
final String url = options
|
|
70
|
-
|
|
71
|
-
|
|
164
|
+
final String url = readRequiredString(options, "url", promise);
|
|
165
|
+
if (url == null) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
String hash = readRequiredString(options, "hash", promise);
|
|
169
|
+
if (hash == null) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
String target = readRequiredString(options, "target", promise);
|
|
173
|
+
if (target == null) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!HttpUtils.isHttpsUrl(url)) {
|
|
177
|
+
// Installing an APK is code execution and nothing verifies
|
|
178
|
+
// its bytes yet (CODE_AUDIT 2.6): TLS is the only thing
|
|
179
|
+
// standing between the server and the device, so a plaintext
|
|
180
|
+
// URL is refused outright.
|
|
181
|
+
promise.reject(ErrorCodes.INVALID_OPTIONS, "APK url must use https: " + url);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
72
184
|
if (!ApkInstaller.ensureInstallPermission(reactContext, promise)) {
|
|
73
185
|
return;
|
|
74
186
|
}
|
|
@@ -88,129 +200,87 @@ public class UpdateModuleImpl {
|
|
|
88
200
|
}
|
|
89
201
|
}
|
|
90
202
|
|
|
91
|
-
public
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
String
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
promise.resolve(null);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
@Override
|
|
105
|
-
public void onDownloadFailed(Throwable error) {
|
|
106
|
-
promise.reject(downloadErrorCode(error), error);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
203
|
+
public void downloadPatchFromPackage(final ReadableMap options, final Promise promise) {
|
|
204
|
+
String url = readRequiredString(options, "updateUrl", promise);
|
|
205
|
+
if (url == null) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
String hash = readRequiredString(options, "hash", promise);
|
|
209
|
+
if (hash == null) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
updateContext.downloadPatchFromApk(url, hash, downloadListener(promise));
|
|
109
213
|
}
|
|
110
214
|
|
|
111
|
-
public
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
String originHash = options.getString("originHash");
|
|
120
|
-
|
|
121
|
-
updateContext.downloadPatchFromPpk(url, hash, originHash, new UpdateContext.DownloadFileListener() {
|
|
122
|
-
@Override
|
|
123
|
-
public void onDownloadCompleted(DownloadTaskParams params) {
|
|
124
|
-
promise.resolve(null);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@Override
|
|
128
|
-
public void onDownloadFailed(Throwable error) {
|
|
129
|
-
promise.reject(downloadErrorCode(error), error);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
} catch (Exception e) {
|
|
133
|
-
promise.reject(ErrorCodes.INVALID_OPTIONS, "downloadPatchFromPpk failed: " + e.getMessage(), e);
|
|
215
|
+
public void downloadPatchFromPpk(final ReadableMap options, final Promise promise) {
|
|
216
|
+
String url = readRequiredString(options, "updateUrl", promise);
|
|
217
|
+
if (url == null) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
String hash = readRequiredString(options, "hash", promise);
|
|
221
|
+
if (hash == null) {
|
|
222
|
+
return;
|
|
134
223
|
}
|
|
224
|
+
String originHash = readRequiredString(options, "originHash", promise);
|
|
225
|
+
if (originHash == null) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
updateContext.downloadPatchFromPpk(url, hash, originHash, downloadListener(promise));
|
|
135
229
|
}
|
|
136
230
|
|
|
137
|
-
public
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
restartApp(updateContext, reactContext, options.getString("hash"), promise);
|
|
231
|
+
public void reloadUpdate(final ReadableMap options, final Promise promise) {
|
|
232
|
+
String hash = readRequiredString(options, "hash", promise);
|
|
233
|
+
if (hash == null) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
restartApp(hash, promise);
|
|
144
237
|
}
|
|
145
238
|
|
|
146
|
-
public
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
UiThreadRunner.run(promise, ErrorCodes.RESTART_FAILED, "restartApp", new UiThreadRunner.Operation() {
|
|
239
|
+
public void restartApp(@Nullable final String hash, final Promise promise) {
|
|
240
|
+
// Two hops (CODE_AUDIT 2.2): the switch (bundle digest + commit) and
|
|
241
|
+
// the launch resolution run on the state serial thread like every
|
|
242
|
+
// other state write; only the React reload itself is posted to the
|
|
243
|
+
// UI thread. A failure at either hop rejects with RESTART_FAILED.
|
|
244
|
+
StateSerialRunner.run(promise, ErrorCodes.RESTART_FAILED, "restartApp", new StateSerialRunner.Operation() {
|
|
153
245
|
@Override
|
|
154
246
|
public void run() throws Throwable {
|
|
155
|
-
ReactReloadManager.
|
|
156
|
-
promise.
|
|
247
|
+
final String bundlePath = ReactReloadManager.prepareRestart(updateContext, hash);
|
|
248
|
+
UiThreadRunner.run(promise, ErrorCodes.RESTART_FAILED, "restartApp", new UiThreadRunner.Operation() {
|
|
249
|
+
@Override
|
|
250
|
+
public void run() throws Throwable {
|
|
251
|
+
ReactReloadManager.reload(updateContext, reactContext, bundlePath);
|
|
252
|
+
promise.resolve(true);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
157
255
|
}
|
|
158
256
|
});
|
|
159
257
|
}
|
|
160
258
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
final UpdateContext updateContext,
|
|
167
|
-
final ReadableMap options,
|
|
168
|
-
final Promise promise
|
|
169
|
-
) {
|
|
170
|
-
final String hash = options.getString("hash");
|
|
259
|
+
public void setNeedUpdate(final ReadableMap options, final Promise promise) {
|
|
260
|
+
final String hash = readRequiredString(options, "hash", promise);
|
|
261
|
+
if (hash == null) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
171
264
|
StateSerialRunner.run(promise, ErrorCodes.SWITCH_VERSION_FAILED, "switchVersionLater", new StateSerialRunner.Operation() {
|
|
172
265
|
@Override
|
|
173
266
|
public void run() {
|
|
174
|
-
|
|
267
|
+
updateContext.switchVersion(hash);
|
|
175
268
|
promise.resolve(true);
|
|
176
269
|
}
|
|
177
270
|
});
|
|
178
271
|
}
|
|
179
272
|
|
|
180
|
-
public
|
|
181
|
-
final String hash = options.getString("hash");
|
|
182
|
-
StateSerialRunner.run(null, ErrorCodes.SWITCH_VERSION_FAILED, "switchVersionLater", new StateSerialRunner.Operation() {
|
|
183
|
-
@Override
|
|
184
|
-
public void run() {
|
|
185
|
-
setNeedUpdateInternal(updateContext, hash);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
private static void markSuccessInternal(UpdateContext updateContext) {
|
|
191
|
-
updateContext.markSuccess();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
public static void markSuccess(final UpdateContext updateContext, final Promise promise) {
|
|
273
|
+
public void markSuccess(final Promise promise) {
|
|
195
274
|
StateSerialRunner.run(promise, ErrorCodes.MARK_SUCCESS_FAILED, "markSuccess", new StateSerialRunner.Operation() {
|
|
196
275
|
@Override
|
|
197
276
|
public void run() {
|
|
198
|
-
|
|
277
|
+
updateContext.markSuccess();
|
|
199
278
|
promise.resolve(true);
|
|
200
279
|
}
|
|
201
280
|
});
|
|
202
281
|
}
|
|
203
282
|
|
|
204
|
-
public
|
|
205
|
-
StateSerialRunner.run(null, ErrorCodes.MARK_SUCCESS_FAILED, "markSuccess", new StateSerialRunner.Operation() {
|
|
206
|
-
@Override
|
|
207
|
-
public void run() {
|
|
208
|
-
markSuccessInternal(updateContext);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
public static void getBundleHash(final UpdateContext updateContext, final Promise promise) {
|
|
283
|
+
public void getBundleHash(final Promise promise) {
|
|
214
284
|
// Threading lives in UpdateContext (download executor); resolve is
|
|
215
285
|
// thread-safe. Never rejects — empty string means "unknown".
|
|
216
286
|
updateContext.getBundleHash(new UpdateContext.BundleHashListener() {
|
|
@@ -221,10 +291,7 @@ public class UpdateModuleImpl {
|
|
|
221
291
|
});
|
|
222
292
|
}
|
|
223
293
|
|
|
224
|
-
public
|
|
225
|
-
final UpdateContext updateContext,
|
|
226
|
-
final Promise promise
|
|
227
|
-
) {
|
|
294
|
+
public void resetToPackagedBundle(final Promise promise) {
|
|
228
295
|
StateSerialRunner.run(promise, ErrorCodes.RESET_FAILED, "resetToPackagedBundle", new StateSerialRunner.Operation() {
|
|
229
296
|
@Override
|
|
230
297
|
public void run() {
|
|
@@ -239,18 +306,11 @@ public class UpdateModuleImpl {
|
|
|
239
306
|
* reuse instead of re-checking (§10.3). Empty string when absent; never
|
|
240
307
|
* rejects.
|
|
241
308
|
*/
|
|
242
|
-
public
|
|
243
|
-
final UpdateContext updateContext,
|
|
244
|
-
final Promise promise
|
|
245
|
-
) {
|
|
309
|
+
public void getNativeCheckCache(final Promise promise) {
|
|
246
310
|
String cached = updateContext.getKv(NativeCheckOrchestrator.KEY_RESP_CACHE);
|
|
247
311
|
promise.resolve(cached == null ? "" : cached);
|
|
248
312
|
}
|
|
249
313
|
|
|
250
|
-
private static void setUuidInternal(UpdateContext updateContext, String uuid) {
|
|
251
|
-
updateContext.setKv("uuid", uuid);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
314
|
/**
|
|
255
315
|
* Provisioning for the native cold-start update check
|
|
256
316
|
* (NATIVE_CHECKUPDATE_DESIGN §10.1): the raw JSON persists as-is and is
|
|
@@ -258,26 +318,16 @@ public class UpdateModuleImpl {
|
|
|
258
318
|
* Validated at write time — a corrupt config would otherwise silently
|
|
259
319
|
* disable the native check forever with no signal.
|
|
260
320
|
*/
|
|
261
|
-
public
|
|
262
|
-
|
|
263
|
-
final String config,
|
|
264
|
-
final Promise promise
|
|
265
|
-
) {
|
|
266
|
-
if (config == null || config.isEmpty()) {
|
|
267
|
-
promise.reject(ErrorCodes.INVALID_OPTIONS, "config must be a non-empty string");
|
|
321
|
+
public void markJsCheckCompleted(final String config, final Promise promise) {
|
|
322
|
+
if (requireNonEmpty(config, "config", promise) == null) {
|
|
268
323
|
return;
|
|
269
324
|
}
|
|
270
325
|
NativeCheckOrchestrator.markJsCheckCompleted(config);
|
|
271
326
|
promise.resolve(true);
|
|
272
327
|
}
|
|
273
328
|
|
|
274
|
-
public
|
|
275
|
-
|
|
276
|
-
final String config,
|
|
277
|
-
final Promise promise
|
|
278
|
-
) {
|
|
279
|
-
if (config == null || config.isEmpty()) {
|
|
280
|
-
promise.reject(ErrorCodes.INVALID_OPTIONS, "config must be a JSON object string");
|
|
329
|
+
public void syncNativeConfig(final String config, final Promise promise) {
|
|
330
|
+
if (requireNonEmpty(config, "config", promise) == null) {
|
|
281
331
|
return;
|
|
282
332
|
}
|
|
283
333
|
try {
|
|
@@ -295,92 +345,49 @@ public class UpdateModuleImpl {
|
|
|
295
345
|
});
|
|
296
346
|
}
|
|
297
347
|
|
|
298
|
-
public
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
) {
|
|
348
|
+
public void setUuid(final String uuid, final Promise promise) {
|
|
349
|
+
if (requireNonEmpty(uuid, "uuid", promise) == null) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
303
352
|
StateSerialRunner.run(promise, ErrorCodes.FILE_OPERATION_FAILED, "setUuid", new StateSerialRunner.Operation() {
|
|
304
353
|
@Override
|
|
305
354
|
public void run() {
|
|
306
|
-
|
|
355
|
+
updateContext.setKv("uuid", uuid);
|
|
307
356
|
promise.resolve(true);
|
|
308
357
|
}
|
|
309
358
|
});
|
|
310
359
|
}
|
|
311
360
|
|
|
312
|
-
public
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
public void run() {
|
|
316
|
-
setUuidInternal(updateContext, uuid);
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
private static void setLocalHashInfoInternal(
|
|
322
|
-
UpdateContext updateContext,
|
|
323
|
-
String hash,
|
|
324
|
-
String info
|
|
325
|
-
) {
|
|
326
|
-
if (!isValidHashInfo(info)) {
|
|
327
|
-
throw new IllegalArgumentException("invalid json string");
|
|
328
|
-
}
|
|
329
|
-
try {
|
|
330
|
-
updateContext.setKv("hash_" + hash, info);
|
|
331
|
-
} catch (IllegalStateException e) {
|
|
332
|
-
// Distinguish "could not persist" from "malformed input": JS
|
|
333
|
-
// classifies the former as FILE_OPERATION_FAILED.
|
|
334
|
-
throw new PersistenceException(e);
|
|
361
|
+
public void setLocalHashInfo(final String hash, final String info, final Promise promise) {
|
|
362
|
+
if (requireNonEmpty(hash, "hash", promise) == null) {
|
|
363
|
+
return;
|
|
335
364
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
static final class PersistenceException extends RuntimeException {
|
|
340
|
-
PersistenceException(Throwable cause) {
|
|
341
|
-
super(String.valueOf(cause.getMessage()), cause);
|
|
365
|
+
if (!isValidHashInfo(info)) {
|
|
366
|
+
promise.reject(ErrorCodes.INVALID_HASH_INFO, "setLocalHashInfo failed: invalid json string");
|
|
367
|
+
return;
|
|
342
368
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
final UpdateContext updateContext,
|
|
347
|
-
final String hash,
|
|
348
|
-
final String info,
|
|
349
|
-
final Promise promise
|
|
350
|
-
) {
|
|
351
|
-
StateSerialRunner.run(promise, ErrorCodes.INVALID_HASH_INFO, "setLocalHashInfo", new StateSerialRunner.Operation() {
|
|
369
|
+
// A state write that did not reach disk (SharedPreferences commit
|
|
370
|
+
// false) is FILE_OPERATION_FAILED, distinct from malformed input.
|
|
371
|
+
StateSerialRunner.run(promise, ErrorCodes.FILE_OPERATION_FAILED, "setLocalHashInfo", new StateSerialRunner.Operation() {
|
|
352
372
|
@Override
|
|
353
373
|
public void run() {
|
|
354
|
-
|
|
355
|
-
setLocalHashInfoInternal(updateContext, hash, info);
|
|
356
|
-
} catch (PersistenceException e) {
|
|
357
|
-
promise.reject(ErrorCodes.FILE_OPERATION_FAILED, "setLocalHashInfo failed", e);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
374
|
+
updateContext.setKv("hash_" + hash, info);
|
|
360
375
|
promise.resolve(true);
|
|
361
376
|
}
|
|
362
377
|
});
|
|
363
378
|
}
|
|
364
379
|
|
|
365
|
-
public
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
) {
|
|
370
|
-
StateSerialRunner.run(null, ErrorCodes.INVALID_HASH_INFO, "setLocalHashInfo", new StateSerialRunner.Operation() {
|
|
371
|
-
@Override
|
|
372
|
-
public void run() {
|
|
373
|
-
setLocalHashInfoInternal(updateContext, hash, info);
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
public static void getLocalHashInfo(
|
|
379
|
-
UpdateContext updateContext,
|
|
380
|
-
final String hash,
|
|
381
|
-
final Promise promise
|
|
382
|
-
) {
|
|
380
|
+
public void getLocalHashInfo(final String hash, final Promise promise) {
|
|
381
|
+
if (requireNonEmpty(hash, "hash", promise) == null) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
383
384
|
String value = updateContext.getKv("hash_" + hash);
|
|
385
|
+
if (value == null || value.isEmpty()) {
|
|
386
|
+
// No record (iOS resolves nil the same way): JS treats it as {}.
|
|
387
|
+
// Only a record that exists and is not a JSON object is invalid.
|
|
388
|
+
promise.resolve(null);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
384
391
|
if (!isValidHashInfo(value)) {
|
|
385
392
|
promise.reject(ErrorCodes.INVALID_HASH_INFO, "getLocalHashInfo failed: invalid json string");
|
|
386
393
|
return;
|
|
@@ -9,6 +9,11 @@ import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
|
9
9
|
import java.util.HashMap;
|
|
10
10
|
import java.util.Map;
|
|
11
11
|
|
|
12
|
+
// TurboReactPackage and the 7-argument ReactModuleInfo are deprecated in
|
|
13
|
+
// current React Native but still shipped; their replacements
|
|
14
|
+
// (BaseReactPackage, the 6-argument constructor) only exist from RN 0.74,
|
|
15
|
+
// and this module's peer floor is RN 0.59.
|
|
16
|
+
@SuppressWarnings("deprecation")
|
|
12
17
|
public class UpdatePackage extends TurboReactPackage {
|
|
13
18
|
@Nullable
|
|
14
19
|
@Override
|