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.
- package/README-CN.md +7 -7
- package/README.md +7 -6
- package/android/build.gradle +25 -33
- package/android/jni/Android.mk +16 -24
- package/android/lib/arm64-v8a/librnpushy.so +0 -0
- package/android/lib/armeabi-v7a/librnpushy.so +0 -0
- package/android/lib/x86/librnpushy.so +0 -0
- package/android/lib/x86_64/librnpushy.so +0 -0
- package/android/proguard.pro +9 -7
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +11 -12
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +23 -6
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +50 -51
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +12 -13
- package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/CrashHold.java +22 -30
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +181 -332
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -1
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +27 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/FlowBridge.java +32 -8
- package/android/src/main/java/cn/reactnative/modules/update/HttpClients.java +63 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +0 -11
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +66 -116
- package/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +9 -1
- package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +32 -60
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +8 -23
- package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +42 -80
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +22 -39
- package/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +176 -299
- package/android/src/main/java/cn/reactnative/modules/update/Threads.java +35 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +242 -174
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +75 -17
- package/android/src/main/java/cn/reactnative/modules/update/{UpdateModuleImpl.java → UpdateModule.java} +113 -95
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +16 -19
- package/android/src/oldarch/cn/reactnative/modules/update/NativePushySpec.java +69 -0
- package/cpp/android_jni/archive_limits_jni.cpp +83 -0
- package/cpp/android_jni/install_record_jni.cpp +89 -0
- package/cpp/android_jni/jni_log.h +31 -0
- package/cpp/android_jni/jni_natives.h +40 -0
- package/cpp/android_jni/jni_registration.cpp +133 -0
- package/cpp/android_jni/jni_util.h +160 -0
- package/cpp/android_jni/update_core_android.cpp +450 -0
- package/cpp/android_jni/update_flow_jni.cpp +133 -0
- package/{android/jni → cpp/hpatch}/hpatch.c +5 -5
- package/{android/jni → cpp/hpatch}/hpatch.h +4 -1
- package/cpp/patch_core/archive_limits.cpp +194 -0
- package/cpp/patch_core/archive_limits.h +99 -10
- package/cpp/patch_core/archive_patch_core.cpp +96 -12
- package/cpp/patch_core/archive_patch_core.h +49 -0
- package/cpp/patch_core/digest.h +5 -4
- package/cpp/patch_core/error_codes.h +5 -3
- package/cpp/patch_core/install_record.cpp +430 -0
- package/cpp/patch_core/install_record.h +163 -9
- package/cpp/patch_core/patch_core.cpp +62 -16
- package/cpp/patch_core/patch_core.h +27 -7
- package/cpp/patch_core/patch_manifest.cpp +201 -0
- package/cpp/patch_core/patch_manifest.h +74 -0
- package/cpp/patch_core/state_ops.h +3 -3
- package/cpp/update_flow_core/flow_json.cpp +82 -8
- package/cpp/update_flow_core/flow_json.h +21 -0
- package/cpp/update_flow_core/native_config.cpp +456 -0
- package/cpp/update_flow_core/native_config.h +84 -0
- package/cpp/update_flow_core/native_round.cpp +326 -0
- package/cpp/update_flow_core/native_round.h +90 -0
- package/cpp/update_flow_core/update_flow_core.h +2 -2
- package/harmony/hvigor-plugin.ts +11 -19
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +19 -26
- package/harmony/pushy/src/main/cpp/PushyPackage.h +2 -2
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +55 -94
- package/harmony/pushy/src/main/cpp/pushy.cpp +694 -517
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +48 -72
- package/harmony/pushy/src/main/ets/DownloadTask.ts +164 -404
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +1 -3
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +1 -14
- package/harmony/pushy/src/main/ets/EventHub.ts +1 -1
- package/harmony/pushy/src/main/ets/InstallRecord.ts +52 -151
- package/harmony/pushy/src/main/ets/Logger.ts +20 -34
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +116 -42
- package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -85
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -25
- package/harmony/pushy/src/main/ets/SaveFile.ts +99 -18
- package/harmony/pushy/src/main/ets/SyncCoordinator.ts +136 -335
- package/harmony/pushy/src/main/ets/Texts.ts +0 -4
- package/harmony/pushy/src/main/ets/UpdateContext.ts +106 -119
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy+Launch.mm +185 -0
- package/ios/RCTPushy/RCTPushy.h +12 -0
- package/ios/RCTPushy/RCTPushy.mm +87 -2628
- package/ios/RCTPushy/RCTPushyCommon.h +34 -0
- package/ios/RCTPushy/RCTPushyConfiguration.h +0 -8
- package/ios/RCTPushy/RCTPushyConfiguration.mm +40 -121
- package/ios/RCTPushy/RCTPushyDownloader.mm +40 -37
- package/ios/RCTPushy/RCTPushyInstallRecord.mm +61 -0
- package/ios/RCTPushy/RCTPushyInstaller.mm +689 -0
- package/ios/RCTPushy/RCTPushyInternal.h +144 -0
- package/ios/RCTPushy/RCTPushyOrchestrator.mm +1001 -0
- package/ios/RCTPushy/RCTPushyState.mm +314 -0
- package/package.json +25 -23
- package/react-native-update.podspec +74 -83
- package/src/client.ts +361 -451
- package/src/context.ts +1 -1
- package/src/core.ts +35 -8
- package/src/endpoint.ts +0 -2
- package/src/error.ts +13 -32
- package/src/i18n.ts +18 -83
- package/src/locales/en.ts +0 -4
- package/src/locales/zh.ts +0 -4
- package/src/provider.tsx +60 -53
- package/src/telemetry.ts +0 -13
- package/src/type.ts +0 -15
- package/src/updateFlowCore.ts +116 -10
- package/src/utils.ts +49 -42
- package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +0 -6
- package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +0 -104
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +0 -37
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +0 -121
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +0 -125
- package/cpp/patch_core/jni_natives.h +0 -27
- package/cpp/patch_core/jni_registration.cpp +0 -103
- package/cpp/patch_core/jni_util.h +0 -60
- package/cpp/patch_core/patch_core_android.cpp +0 -97
- package/cpp/patch_core/update_core_android.cpp +0 -485
- package/cpp/update_flow_core/update_flow_jni.cpp +0 -150
|
@@ -8,27 +8,26 @@ import android.util.Log;
|
|
|
8
8
|
import androidx.annotation.Nullable;
|
|
9
9
|
import com.facebook.react.ReactInstanceManager;
|
|
10
10
|
import java.io.File;
|
|
11
|
-
import org.json.JSONObject;
|
|
12
11
|
import java.io.IOException;
|
|
13
12
|
import java.io.InputStream;
|
|
14
|
-
import java.security.MessageDigest;
|
|
15
|
-
import java.security.NoSuchAlgorithmException;
|
|
16
13
|
import java.util.concurrent.Executor;
|
|
17
|
-
import java.util.concurrent.Executors;
|
|
18
|
-
import java.util.concurrent.ThreadFactory;
|
|
19
14
|
|
|
20
15
|
public class UpdateContext {
|
|
16
|
+
// The one place that loads librnpushy.so (see NativeCore).
|
|
21
17
|
static {
|
|
22
18
|
NativeCore.ensureLoaded();
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
static final String TAG = Texts.LOG_TAG;
|
|
26
|
-
static final boolean DEBUG = BuildConfig.DEBUG;
|
|
27
22
|
|
|
28
23
|
private final Context context;
|
|
29
24
|
private final File rootDir;
|
|
30
25
|
private final Executor executor;
|
|
31
26
|
private final SharedPreferences sp;
|
|
27
|
+
// The native check's raw response cache (SyncCoordinator.KEY_RESP_CACHE)
|
|
28
|
+
// lives in its own file: it is a whole server response, and keeping it in
|
|
29
|
+
// sp made every small state commit rewrite it.
|
|
30
|
+
private final SharedPreferences checkCacheSp;
|
|
32
31
|
// Our own PackageInfo, looked up once: every getPackageInfo call is a
|
|
33
32
|
// Binder IPC, and the constructor, the bundle-hash cache and the native
|
|
34
33
|
// check all need the version name / install time.
|
|
@@ -48,6 +47,16 @@ public class UpdateContext {
|
|
|
48
47
|
// running app has not loaded yet. Volatile: written during launch, read
|
|
49
48
|
// from the state serial and download executors.
|
|
50
49
|
private volatile String launchVersion;
|
|
50
|
+
// bundleSha256 of launchVersion's install record, read once at launch
|
|
51
|
+
// resolution so getConstants does not hit the file system on the JS
|
|
52
|
+
// thread. {hash, sha256}; null until a version was resolved.
|
|
53
|
+
private volatile String[] launchBundleSha256;
|
|
54
|
+
// bundleHash of the embedded bundle, computed once per process on its own
|
|
55
|
+
// thread (see startBundleHashComputation); null until known.
|
|
56
|
+
private final Object bundleHashLock = new Object();
|
|
57
|
+
private String bundleHash;
|
|
58
|
+
private java.util.ArrayList<BundleHashListener> pendingBundleHashListeners =
|
|
59
|
+
new java.util.ArrayList<>();
|
|
51
60
|
private static final int STATE_OP_SWITCH_VERSION = 1;
|
|
52
61
|
private static final int STATE_OP_MARK_SUCCESS = 2;
|
|
53
62
|
private static final int STATE_OP_ROLLBACK = 3;
|
|
@@ -55,7 +64,6 @@ public class UpdateContext {
|
|
|
55
64
|
private static final int STATE_OP_CLEAR_ROLLBACK_MARK = 5;
|
|
56
65
|
private static final int STATE_OP_RESOLVE_LAUNCH = 6;
|
|
57
66
|
private static final String KEY_FIRST_LOAD_MARKED = "firstLoadMarked";
|
|
58
|
-
static final String VERSION_COMPLETE_FILE = InstallRecord.FILE_NAME;
|
|
59
67
|
// Bumped by resetToPackagedBundle. The cold-start check runs for minutes
|
|
60
68
|
// and may already hold a decision when the app resets to the packaged
|
|
61
69
|
// bundle; the orchestrator samples this counter and abandons activation
|
|
@@ -100,12 +108,7 @@ public class UpdateContext {
|
|
|
100
108
|
|
|
101
109
|
private UpdateContext(Context context) {
|
|
102
110
|
this.context = context.getApplicationContext();
|
|
103
|
-
this.executor =
|
|
104
|
-
@Override
|
|
105
|
-
public Thread newThread(Runnable r) {
|
|
106
|
-
return new Thread(r, "pushy-download");
|
|
107
|
-
}
|
|
108
|
-
});
|
|
111
|
+
this.executor = Threads.single("pushy-download", false);
|
|
109
112
|
|
|
110
113
|
this.rootDir = new File(this.context.getFilesDir(), Texts.reveal("0502e4d5af9f6d"));
|
|
111
114
|
|
|
@@ -114,6 +117,8 @@ public class UpdateContext {
|
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
this.sp = this.context.getSharedPreferences(Texts.reveal("2f07f0d0ba8e"), Context.MODE_PRIVATE);
|
|
120
|
+
this.checkCacheSp = this.context.getSharedPreferences(
|
|
121
|
+
Texts.reveal("2f07f0d0ba8e") + "_check", Context.MODE_PRIVATE);
|
|
117
122
|
this.packageInfo = lookupPackageInfo(this.context);
|
|
118
123
|
this.reactInstanceManager = pendingReactInstanceManager;
|
|
119
124
|
|
|
@@ -127,11 +132,28 @@ public class UpdateContext {
|
|
|
127
132
|
|
|
128
133
|
if (nextState.changed) {
|
|
129
134
|
// Execute cleanUp before clearing SharedPreferences to avoid race condition
|
|
130
|
-
|
|
135
|
+
cleanUp(DownloadTaskParams.DEFAULT_MAX_AGE_DAYS);
|
|
131
136
|
SharedPreferences.Editor editor = this.sp.edit();
|
|
132
137
|
editor.clear();
|
|
133
138
|
applyState(editor, nextState);
|
|
134
139
|
persistEditor(editor, "sync state with binary version");
|
|
140
|
+
clearNativeCheckCache();
|
|
141
|
+
} else {
|
|
142
|
+
migrateNativeCheckCache();
|
|
143
|
+
}
|
|
144
|
+
startBundleHashComputation();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// One-time move of the response cache out of the main state file.
|
|
148
|
+
private void migrateNativeCheckCache() {
|
|
149
|
+
String legacy = sp.getString(SyncCoordinator.KEY_RESP_CACHE, null);
|
|
150
|
+
if (legacy == null) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (checkCacheSp.edit().putString(SyncCoordinator.KEY_RESP_CACHE, legacy).commit()) {
|
|
154
|
+
SharedPreferences.Editor editor = sp.edit();
|
|
155
|
+
editor.remove(SyncCoordinator.KEY_RESP_CACHE);
|
|
156
|
+
persistEditor(editor, "migrate check cache");
|
|
135
157
|
}
|
|
136
158
|
}
|
|
137
159
|
|
|
@@ -177,32 +199,87 @@ public class UpdateContext {
|
|
|
177
199
|
* identity of the binary itself, not of whatever hot update is currently
|
|
178
200
|
* running. Hashes exactly the bytes pdiff patches from: the hardcoded
|
|
179
201
|
* "index.android.bundle" asset read via AssetManager, same as
|
|
180
|
-
* DownloadTask.copyBundledAssetToFile.
|
|
181
|
-
*
|
|
182
|
-
* to the buildTime heuristic.
|
|
202
|
+
* DownloadTask.copyBundledAssetToFile. Never fails — an empty string
|
|
203
|
+
* means "unknown" and the server falls back to the buildTime heuristic.
|
|
183
204
|
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
205
|
+
* Computed once per process on a dedicated thread started by the
|
|
206
|
+
* constructor (not on the download executor, where it used to queue
|
|
207
|
+
* behind cleanup and downloads and make the first JS checkUpdate send an
|
|
208
|
+
* empty hash). The listener runs on that thread, or on the caller's when
|
|
209
|
+
* the value is already known.
|
|
210
|
+
*
|
|
211
|
+
* java.security.MessageDigest rather than the C++ pushy::digest: the
|
|
212
|
+
* bytes come from an AssetManager stream on the Java side, and the
|
|
213
|
+
* platform digest is native-backed already. The NIST vectors in the
|
|
214
|
+
* patch_core tests anchor both implementations to the same standard.
|
|
188
215
|
*/
|
|
189
216
|
public void getBundleHash(final BundleHashListener listener) {
|
|
190
|
-
|
|
217
|
+
String known;
|
|
218
|
+
synchronized (bundleHashLock) {
|
|
219
|
+
known = bundleHash;
|
|
220
|
+
if (known == null) {
|
|
221
|
+
pendingBundleHashListeners.add(listener);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
listener.onBundleHash(known);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Package-private: also the native cold-start check's request input
|
|
229
|
+
// (SyncCoordinator). Blocks until the shared computation finished — call
|
|
230
|
+
// off the main thread.
|
|
231
|
+
String computeBundleHash() {
|
|
232
|
+
synchronized (bundleHashLock) {
|
|
233
|
+
while (bundleHash == null) {
|
|
234
|
+
try {
|
|
235
|
+
bundleHashLock.wait();
|
|
236
|
+
} catch (InterruptedException e) {
|
|
237
|
+
Thread.currentThread().interrupt();
|
|
238
|
+
return "";
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return bundleHash;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private void startBundleHashComputation() {
|
|
246
|
+
if (BuildConfig.DEBUG) {
|
|
191
247
|
// Metro serves the bundle in debug; mirror the dev buildTime behaviour.
|
|
192
|
-
|
|
248
|
+
publishBundleHash("");
|
|
193
249
|
return;
|
|
194
250
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
251
|
+
Threads.startDaemon("pushy-bundle-hash", () -> {
|
|
252
|
+
String hash = "";
|
|
253
|
+
try {
|
|
254
|
+
hash = loadOrHashBundledAsset();
|
|
255
|
+
} catch (Throwable e) {
|
|
256
|
+
Log.w(TAG, "bundle hash failed: " + e);
|
|
257
|
+
} finally {
|
|
258
|
+
publishBundleHash(hash);
|
|
199
259
|
}
|
|
200
260
|
});
|
|
201
261
|
}
|
|
202
262
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
263
|
+
private void publishBundleHash(String hash) {
|
|
264
|
+
java.util.ArrayList<BundleHashListener> listeners;
|
|
265
|
+
synchronized (bundleHashLock) {
|
|
266
|
+
bundleHash = hash;
|
|
267
|
+
listeners = pendingBundleHashListeners;
|
|
268
|
+
pendingBundleHashListeners = null;
|
|
269
|
+
bundleHashLock.notifyAll();
|
|
270
|
+
}
|
|
271
|
+
if (listeners != null) {
|
|
272
|
+
for (BundleHashListener listener : listeners) {
|
|
273
|
+
try {
|
|
274
|
+
listener.onBundleHash(hash);
|
|
275
|
+
} catch (Throwable e) {
|
|
276
|
+
Log.w(TAG, "bundle hash listener failed: " + e);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private String loadOrHashBundledAsset() {
|
|
206
283
|
String cachePrefix = getPackageVersion() + "|" + getPackageLastUpdateTime() + "|";
|
|
207
284
|
String cached = sp.getString(KEY_BUNDLE_HASH_CACHE, null);
|
|
208
285
|
if (cached != null && cached.startsWith(cachePrefix)) {
|
|
@@ -223,20 +300,8 @@ public class UpdateContext {
|
|
|
223
300
|
|
|
224
301
|
private String sha256OfBundledAsset(String assetName) {
|
|
225
302
|
try (InputStream in = context.getAssets().open(assetName)) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
int read;
|
|
229
|
-
while ((read = in.read(buffer)) != -1) {
|
|
230
|
-
md.update(buffer, 0, read);
|
|
231
|
-
}
|
|
232
|
-
byte[] digest = md.digest();
|
|
233
|
-
StringBuilder hex = new StringBuilder(digest.length * 2);
|
|
234
|
-
for (byte b : digest) {
|
|
235
|
-
hex.append(Character.forDigit((b >> 4) & 0xf, 16));
|
|
236
|
-
hex.append(Character.forDigit(b & 0xf, 16));
|
|
237
|
-
}
|
|
238
|
-
return hex.toString();
|
|
239
|
-
} catch (IOException | NoSuchAlgorithmException e) {
|
|
303
|
+
return UpdateFileUtils.sha256Hex(in);
|
|
304
|
+
} catch (IOException e) {
|
|
240
305
|
// Expected when there is no embedded bundle (custom name, no
|
|
241
306
|
// release bundle); "unknown" is a valid answer, not an error.
|
|
242
307
|
Log.i(TAG, "Cannot hash bundled asset " + assetName + ": " + e);
|
|
@@ -261,16 +326,30 @@ public class UpdateContext {
|
|
|
261
326
|
void onDownloadFailed(Throwable error);
|
|
262
327
|
}
|
|
263
328
|
|
|
264
|
-
|
|
265
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Params shared by every download task. deadlineNanos is an absolute
|
|
331
|
+
* System.nanoTime deadline for orchestrated (native cold-start) tasks; 0
|
|
332
|
+
* keeps the per-call timeout of a JS-initiated download.
|
|
333
|
+
*/
|
|
334
|
+
private static DownloadTaskParams downloadParams(
|
|
335
|
+
int type, String url, String hash, DownloadFileListener listener, long deadlineNanos
|
|
336
|
+
) {
|
|
337
|
+
DownloadTaskParams params = new DownloadTaskParams();
|
|
338
|
+
params.type = type;
|
|
339
|
+
params.url = url;
|
|
340
|
+
params.hash = hash;
|
|
341
|
+
params.listener = listener;
|
|
342
|
+
params.deadlineNanos = deadlineNanos;
|
|
343
|
+
return params;
|
|
266
344
|
}
|
|
267
345
|
|
|
268
|
-
|
|
269
|
-
* The
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
346
|
+
/*
|
|
347
|
+
* The patch downloads return the queued task's params so the native
|
|
348
|
+
* orchestrator can cancel the task when its phase budget expires
|
|
349
|
+
* (CODE_AUDIT 2.12); null when the request was rejected up front (the
|
|
350
|
+
* listener has already been told).
|
|
273
351
|
*/
|
|
352
|
+
|
|
274
353
|
@Nullable
|
|
275
354
|
DownloadTaskParams downloadFullUpdate(
|
|
276
355
|
String url, String hash, DownloadFileListener listener, long deadlineNanos
|
|
@@ -278,37 +357,25 @@ public class UpdateContext {
|
|
|
278
357
|
if (rejectUnsafeComponent(hash, listener)) {
|
|
279
358
|
return null;
|
|
280
359
|
}
|
|
281
|
-
DownloadTaskParams params =
|
|
282
|
-
|
|
283
|
-
params.url = url;
|
|
284
|
-
params.hash = hash;
|
|
285
|
-
params.listener = listener;
|
|
286
|
-
params.deadlineNanos = deadlineNanos;
|
|
360
|
+
DownloadTaskParams params = downloadParams(
|
|
361
|
+
DownloadTaskParams.TASK_TYPE_PATCH_FULL, url, hash, listener, deadlineNanos);
|
|
287
362
|
params.targetFile = new File(rootDir, hash + ".ppk");
|
|
288
363
|
params.unzipDirectory = new File(rootDir, hash);
|
|
289
364
|
enqueue(params);
|
|
290
365
|
return params;
|
|
291
366
|
}
|
|
292
367
|
|
|
368
|
+
/** A plain file download into the root dir; hash only keys progress events. */
|
|
293
369
|
public void downloadFile(String url, String hash, String fileName, DownloadFileListener listener) {
|
|
294
370
|
if (rejectUnsafeComponent(fileName, listener)) {
|
|
295
371
|
return;
|
|
296
372
|
}
|
|
297
|
-
DownloadTaskParams params =
|
|
298
|
-
|
|
299
|
-
params.url = url;
|
|
300
|
-
params.hash = hash;
|
|
301
|
-
params.listener = listener;
|
|
302
|
-
|
|
373
|
+
DownloadTaskParams params = downloadParams(
|
|
374
|
+
DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD, url, hash, listener, 0);
|
|
303
375
|
params.targetFile = new File(rootDir, fileName);
|
|
304
|
-
// params.unzipDirectory = new File(rootDir, hash);
|
|
305
376
|
enqueue(params);
|
|
306
377
|
}
|
|
307
378
|
|
|
308
|
-
public void downloadPatchFromApk(String url, String hash, DownloadFileListener listener) {
|
|
309
|
-
downloadPatchFromApk(url, hash, listener, 0);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
379
|
@Nullable
|
|
313
380
|
DownloadTaskParams downloadPatchFromApk(
|
|
314
381
|
String url, String hash, DownloadFileListener listener, long deadlineNanos
|
|
@@ -316,22 +383,14 @@ public class UpdateContext {
|
|
|
316
383
|
if (rejectUnsafeComponent(hash, listener)) {
|
|
317
384
|
return null;
|
|
318
385
|
}
|
|
319
|
-
DownloadTaskParams params =
|
|
320
|
-
|
|
321
|
-
params.url = url;
|
|
322
|
-
params.hash = hash;
|
|
323
|
-
params.listener = listener;
|
|
324
|
-
params.deadlineNanos = deadlineNanos;
|
|
386
|
+
DownloadTaskParams params = downloadParams(
|
|
387
|
+
DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK, url, hash, listener, deadlineNanos);
|
|
325
388
|
params.targetFile = new File(rootDir, hash + Texts.reveal("7416e4dae09b69512137"));
|
|
326
389
|
params.unzipDirectory = new File(rootDir, hash);
|
|
327
390
|
enqueue(params);
|
|
328
391
|
return params;
|
|
329
392
|
}
|
|
330
393
|
|
|
331
|
-
public void downloadPatchFromPpk(String url, String hash, String originHash, DownloadFileListener listener) {
|
|
332
|
-
downloadPatchFromPpk(url, hash, originHash, listener, 0);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
394
|
@Nullable
|
|
336
395
|
DownloadTaskParams downloadPatchFromPpk(
|
|
337
396
|
String url,
|
|
@@ -343,13 +402,9 @@ public class UpdateContext {
|
|
|
343
402
|
if (rejectUnsafeComponent(hash, listener) || rejectUnsafeComponent(originHash, listener)) {
|
|
344
403
|
return null;
|
|
345
404
|
}
|
|
346
|
-
DownloadTaskParams params =
|
|
347
|
-
|
|
348
|
-
params.url = url;
|
|
349
|
-
params.hash = hash;
|
|
405
|
+
DownloadTaskParams params = downloadParams(
|
|
406
|
+
DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK, url, hash, listener, deadlineNanos);
|
|
350
407
|
params.originHash = originHash;
|
|
351
|
-
params.listener = listener;
|
|
352
|
-
params.deadlineNanos = deadlineNanos;
|
|
353
408
|
params.targetFile = new File(rootDir, originHash + "-" + hash + Texts.reveal("7407e4dae09b69512137"));
|
|
354
409
|
params.unzipDirectory = new File(rootDir, hash);
|
|
355
410
|
params.originDirectory = new File(rootDir, originHash);
|
|
@@ -429,28 +484,16 @@ public class UpdateContext {
|
|
|
429
484
|
if (!UpdateFileUtils.isSafePathComponent(hash)) {
|
|
430
485
|
throw new IllegalArgumentException("Invalid hash: " + hash);
|
|
431
486
|
}
|
|
432
|
-
File versionDir = new File(rootDir, hash);
|
|
433
|
-
File bundleFile = new File(versionDir, "index.bundlejs");
|
|
434
|
-
if (!bundleFile.isFile()) {
|
|
435
|
-
throw new IllegalStateException("Bundle version " + hash + " not found.");
|
|
436
|
-
}
|
|
437
487
|
StateCoreResult currentState = getStateSnapshot();
|
|
438
|
-
|
|
488
|
+
// Versions activated before completion records existed are
|
|
489
|
+
// grandfathered through current/last state, but only while their
|
|
490
|
+
// directory has no record; any other directory must carry a record
|
|
491
|
+
// whose bundle digest matches the bytes on disk before the next
|
|
492
|
+
// launch is pointed at them.
|
|
493
|
+
boolean grandfathered = hash.equals(currentState.currentVersion)
|
|
439
494
|
|| hash.equals(currentState.lastVersion);
|
|
440
|
-
if (isLegacyActivatedVersion) {
|
|
441
|
-
// Versions activated before completion markers were introduced are
|
|
442
|
-
// explicitly grandfathered through current/last state.
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
if (!InstallRecord.isComplete(versionDir, hash)) {
|
|
446
|
-
// An arbitrary markerless directory may be a crash-left partial
|
|
447
|
-
// install.
|
|
448
|
-
throw new IllegalStateException("Bundle version " + hash + " is incomplete.");
|
|
449
|
-
}
|
|
450
|
-
// The record's bundle digest must match the bytes on disk before
|
|
451
|
-
// the next launch is pointed at them.
|
|
452
495
|
try {
|
|
453
|
-
InstallRecord.
|
|
496
|
+
InstallRecord.verifyInstalled(new File(rootDir, hash), hash, grandfathered);
|
|
454
497
|
} catch (IOException e) {
|
|
455
498
|
throw new IllegalStateException(e.getMessage(), e);
|
|
456
499
|
}
|
|
@@ -462,13 +505,12 @@ public class UpdateContext {
|
|
|
462
505
|
* holds commitLock and has run verifySwitchTarget.
|
|
463
506
|
*/
|
|
464
507
|
private StateCoreResult computeSwitchState(String hash) {
|
|
465
|
-
return
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
);
|
|
508
|
+
return runStateOp(STATE_OP_SWITCH_VERSION, hash);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** One state-core operation on the current snapshot; caller holds commitLock. */
|
|
512
|
+
private StateCoreResult runStateOp(int operation, @Nullable String hash) {
|
|
513
|
+
return runStateCore(operation, getStateSnapshot(), hash, false, false);
|
|
472
514
|
}
|
|
473
515
|
|
|
474
516
|
public void switchVersion(String hash) {
|
|
@@ -518,9 +560,9 @@ public class UpdateContext {
|
|
|
518
560
|
|
|
519
561
|
/**
|
|
520
562
|
* Reads and clears the first-load marker and the rollback mark in one
|
|
521
|
-
*
|
|
522
|
-
* TurboModule constant read), so the
|
|
523
|
-
*
|
|
563
|
+
* editor. getConstants runs this synchronously on the JS thread (a
|
|
564
|
+
* TurboModule constant read), so the write is applied asynchronously;
|
|
565
|
+
* persistence is best-effort on this launch path.
|
|
524
566
|
*/
|
|
525
567
|
LaunchMarkers consumeLaunchMarkers() {
|
|
526
568
|
LaunchMarkers markers;
|
|
@@ -534,21 +576,18 @@ public class UpdateContext {
|
|
|
534
576
|
editor.remove(KEY_FIRST_LOAD_MARKED);
|
|
535
577
|
}
|
|
536
578
|
if (rolledBackVersion != null) {
|
|
537
|
-
|
|
538
|
-
STATE_OP_CLEAR_ROLLBACK_MARK,
|
|
539
|
-
getStateSnapshot(),
|
|
540
|
-
null,
|
|
541
|
-
false,
|
|
542
|
-
false
|
|
543
|
-
);
|
|
544
|
-
applyState(editor, nextState);
|
|
579
|
+
applyState(editor, runStateOp(STATE_OP_CLEAR_ROLLBACK_MARK, null));
|
|
545
580
|
}
|
|
546
|
-
|
|
581
|
+
// Asynchronous on purpose: the in-memory state changes
|
|
582
|
+
// immediately, the disk write happens on the
|
|
583
|
+
// SharedPreferences writer. Losing it to a crash only
|
|
584
|
+
// re-reports the one-shot markers on the next launch.
|
|
585
|
+
editor.apply();
|
|
547
586
|
}
|
|
548
587
|
}
|
|
549
588
|
if (markers.rolledBackVersion != null) {
|
|
550
589
|
// The rolled-back version's directory is now eligible for cleanup.
|
|
551
|
-
|
|
590
|
+
cleanUp(DownloadTaskParams.DEFAULT_MAX_AGE_DAYS);
|
|
552
591
|
}
|
|
553
592
|
return markers;
|
|
554
593
|
}
|
|
@@ -562,14 +601,7 @@ public class UpdateContext {
|
|
|
562
601
|
return;
|
|
563
602
|
}
|
|
564
603
|
synchronized (commitLock) {
|
|
565
|
-
StateCoreResult
|
|
566
|
-
StateCoreResult nextState = runStateCore(
|
|
567
|
-
STATE_OP_MARK_SUCCESS,
|
|
568
|
-
currentState,
|
|
569
|
-
null,
|
|
570
|
-
false,
|
|
571
|
-
false
|
|
572
|
-
);
|
|
604
|
+
StateCoreResult nextState = runStateOp(STATE_OP_MARK_SUCCESS, null);
|
|
573
605
|
SharedPreferences.Editor editor = sp.edit();
|
|
574
606
|
applyState(editor, nextState);
|
|
575
607
|
if (nextState.staleVersionToDelete != null) {
|
|
@@ -578,7 +610,7 @@ public class UpdateContext {
|
|
|
578
610
|
persistEditorOrThrow(editor, "mark success");
|
|
579
611
|
}
|
|
580
612
|
|
|
581
|
-
|
|
613
|
+
cleanUp(DownloadTaskParams.DEFAULT_MAX_AGE_DAYS);
|
|
582
614
|
}
|
|
583
615
|
|
|
584
616
|
/**
|
|
@@ -606,6 +638,11 @@ public class UpdateContext {
|
|
|
606
638
|
resetState.firstTime = false;
|
|
607
639
|
resetState.firstTimeOk = true;
|
|
608
640
|
String uuid = sp.getString("uuid", null);
|
|
641
|
+
// The cached check response still advertises the version this reset
|
|
642
|
+
// removes; drop it first so a failure aborts before the state wipe.
|
|
643
|
+
if (!clearNativeCheckCache()) {
|
|
644
|
+
throw new IllegalStateException("Failed to persist state for reset check cache");
|
|
645
|
+
}
|
|
609
646
|
SharedPreferences.Editor editor = sp.edit();
|
|
610
647
|
editor.clear();
|
|
611
648
|
applyState(editor, resetState);
|
|
@@ -614,17 +651,8 @@ public class UpdateContext {
|
|
|
614
651
|
}
|
|
615
652
|
persistEditorOrThrow(editor, "reset to packaged bundle");
|
|
616
653
|
ignoreRollback = false;
|
|
617
|
-
// editor.clear() above already dropped the cached check response; it
|
|
618
|
-
// still advertised the version this reset removed.
|
|
619
654
|
Log.i(TAG, "Reset to packaged bundle");
|
|
620
|
-
|
|
621
|
-
DownloadTaskParams params = new DownloadTaskParams();
|
|
622
|
-
params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
|
|
623
|
-
params.maxAgeDays = 0;
|
|
624
|
-
// Keep names are resolved by the queued task at execution time, so a
|
|
625
|
-
// later activation is protected too.
|
|
626
|
-
params.unzipDirectory = rootDir;
|
|
627
|
-
enqueue(params);
|
|
655
|
+
cleanUp(0);
|
|
628
656
|
}
|
|
629
657
|
|
|
630
658
|
public static void setCustomInstanceManager(ReactInstanceManager instanceManager) {
|
|
@@ -713,7 +741,7 @@ public class UpdateContext {
|
|
|
713
741
|
// the main thread.
|
|
714
742
|
java.util.HashSet<String> visitedVersions = new java.util.HashSet<>();
|
|
715
743
|
while (currentVersion != null && visitedVersions.add(currentVersion)) {
|
|
716
|
-
File bundleFile = new File(rootDir, currentVersion
|
|
744
|
+
File bundleFile = InstallRecord.bundleFile(new File(rootDir, currentVersion));
|
|
717
745
|
if (!bundleFile.exists()) {
|
|
718
746
|
Log.e(TAG, "Bundle version " + currentVersion + " not found.");
|
|
719
747
|
currentVersion = this.rollBack();
|
|
@@ -721,6 +749,8 @@ public class UpdateContext {
|
|
|
721
749
|
continue;
|
|
722
750
|
}
|
|
723
751
|
launchVersion = currentVersion;
|
|
752
|
+
launchBundleSha256 = new String[] {
|
|
753
|
+
currentVersion, readBundleSha256(currentVersion)};
|
|
724
754
|
syncRolledBackVersion = rolledBackVersion();
|
|
725
755
|
return bundleFile.toString();
|
|
726
756
|
}
|
|
@@ -736,7 +766,6 @@ public class UpdateContext {
|
|
|
736
766
|
}
|
|
737
767
|
}
|
|
738
768
|
|
|
739
|
-
/** Sampled/compared by the native check orchestrator; see resetGeneration. */
|
|
740
769
|
/** Shared by JS and native hosts. Config replacement invalidates old native decisions. */
|
|
741
770
|
private static final java.util.concurrent.atomic.AtomicLong nativeConfigGeneration =
|
|
742
771
|
new java.util.concurrent.atomic.AtomicLong(0);
|
|
@@ -747,15 +776,22 @@ public class UpdateContext {
|
|
|
747
776
|
|
|
748
777
|
void setNativeConfig(String config) {
|
|
749
778
|
synchronized (commitLock) {
|
|
750
|
-
|
|
779
|
+
// Canonical comparison (C1, FlowBridge.configsEquivalent):
|
|
780
|
+
// re-sending the same config with another key order or trailing
|
|
781
|
+
// slashes must not cancel an in-flight round or drop its cache.
|
|
782
|
+
boolean changed = !FlowBridge.configsEquivalent(
|
|
783
|
+
config, sp.getString(SyncCoordinator.KEY_CONFIG, null));
|
|
751
784
|
SharedPreferences.Editor editor = sp.edit();
|
|
752
785
|
if (changed) {
|
|
753
786
|
// Also invalidate on a failed persistence attempt: an older
|
|
754
787
|
// round must not commit over uncertain configuration state.
|
|
755
788
|
resetGeneration.incrementAndGet();
|
|
756
789
|
nativeConfigGeneration.incrementAndGet();
|
|
757
|
-
editor.remove(SyncCoordinator.KEY_RESP_CACHE);
|
|
758
790
|
SyncCoordinator.recordJsRound(null);
|
|
791
|
+
if (!clearNativeCheckCache()) {
|
|
792
|
+
throw new IllegalStateException(
|
|
793
|
+
"Failed to persist state for configuration check cache");
|
|
794
|
+
}
|
|
759
795
|
}
|
|
760
796
|
// A native-only first launch needs a stable gray-release identity
|
|
761
797
|
// before JS initializes. Never replace an existing installation ID.
|
|
@@ -771,7 +807,11 @@ public class UpdateContext {
|
|
|
771
807
|
SyncCoordinator.onConfigured(this);
|
|
772
808
|
}
|
|
773
809
|
|
|
774
|
-
|
|
810
|
+
/**
|
|
811
|
+
* Native-decision generation, sampled/compared by the native check
|
|
812
|
+
* orchestrator: bumped by reset AND configuration replacement (see
|
|
813
|
+
* resetGeneration).
|
|
814
|
+
*/
|
|
775
815
|
static long getResetGeneration() {
|
|
776
816
|
return resetGeneration.get();
|
|
777
817
|
}
|
|
@@ -820,13 +860,20 @@ public class UpdateContext {
|
|
|
820
860
|
if (switching) {
|
|
821
861
|
applyState(editor, computeSwitchState(hash));
|
|
822
862
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
863
|
+
// The round is over once its result lands: clear the
|
|
864
|
+
// interrupted-round breadcrumb in the same commit.
|
|
865
|
+
editor.remove(SyncCoordinator.KEY_ROUND_INCOMPLETE);
|
|
826
866
|
persistEditorOrThrow(editor, "commit sync result");
|
|
827
867
|
if (switching) {
|
|
828
868
|
ignoreRollback = false;
|
|
829
869
|
}
|
|
870
|
+
// Advisory cache, separate file: written after the state it
|
|
871
|
+
// describes, still under commitLock so a reset cannot interleave.
|
|
872
|
+
if (responseCacheJson != null
|
|
873
|
+
&& !checkCacheSp.edit()
|
|
874
|
+
.putString(SyncCoordinator.KEY_RESP_CACHE, responseCacheJson).commit()) {
|
|
875
|
+
Log.w(TAG, "Failed to persist native sync response cache");
|
|
876
|
+
}
|
|
830
877
|
return true;
|
|
831
878
|
}
|
|
832
879
|
}
|
|
@@ -837,33 +884,49 @@ public class UpdateContext {
|
|
|
837
884
|
* unreadable record. Exposed to JS for crash-report attribution.
|
|
838
885
|
*/
|
|
839
886
|
String currentBundleSha256(String hash) {
|
|
887
|
+
String[] cached = launchBundleSha256;
|
|
888
|
+
if (cached != null && cached[0].equals(hash)) {
|
|
889
|
+
return cached[1];
|
|
890
|
+
}
|
|
891
|
+
return readBundleSha256(hash);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
private String readBundleSha256(String hash) {
|
|
840
895
|
if (hash == null || hash.isEmpty() || !UpdateFileUtils.isSafePathComponent(hash)) {
|
|
841
896
|
return "";
|
|
842
897
|
}
|
|
843
|
-
|
|
844
|
-
|
|
898
|
+
return InstallRecord.recordedBundleSha256(new File(rootDir, hash));
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/** Raw native check response for JS (§10.3); null when absent. */
|
|
902
|
+
@Nullable
|
|
903
|
+
String getNativeCheckCache() {
|
|
904
|
+
return checkCacheSp.getString(SyncCoordinator.KEY_RESP_CACHE, null);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
private boolean clearNativeCheckCache() {
|
|
908
|
+
if (!checkCacheSp.contains(SyncCoordinator.KEY_RESP_CACHE)) {
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
if (!checkCacheSp.edit().remove(SyncCoordinator.KEY_RESP_CACHE).commit()) {
|
|
912
|
+
Log.e(TAG, "Failed to clear native sync response cache");
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
return true;
|
|
845
916
|
}
|
|
846
917
|
|
|
847
918
|
boolean hasCompletedVersion(String hash) {
|
|
848
919
|
if (!UpdateFileUtils.isSafePathComponent(hash)) {
|
|
849
920
|
return false;
|
|
850
921
|
}
|
|
851
|
-
|
|
852
|
-
return new File(versionDir, "index.bundlejs").isFile()
|
|
853
|
-
&& InstallRecord.isComplete(versionDir, hash);
|
|
922
|
+
return InstallRecord.isInstalled(new File(rootDir, hash), hash);
|
|
854
923
|
}
|
|
855
924
|
|
|
856
925
|
private String rollBack() {
|
|
857
926
|
synchronized (commitLock) {
|
|
858
|
-
|
|
859
|
-
StateCoreResult nextState =
|
|
860
|
-
|
|
861
|
-
currentState,
|
|
862
|
-
null,
|
|
863
|
-
false,
|
|
864
|
-
false
|
|
865
|
-
);
|
|
866
|
-
Log.e(TAG, "Rolling back version " + currentState.currentVersion
|
|
927
|
+
String rollingBack = getCurrentVersion();
|
|
928
|
+
StateCoreResult nextState = runStateOp(STATE_OP_ROLLBACK, null);
|
|
929
|
+
Log.e(TAG, "Rolling back version " + rollingBack
|
|
867
930
|
+ " to " + nextState.currentVersion);
|
|
868
931
|
SharedPreferences.Editor editor = sp.edit();
|
|
869
932
|
applyState(editor, nextState);
|
|
@@ -884,10 +947,15 @@ public class UpdateContext {
|
|
|
884
947
|
}
|
|
885
948
|
|
|
886
949
|
interface CleanupAction {
|
|
887
|
-
void run(
|
|
950
|
+
void run(String[] keepNames);
|
|
888
951
|
}
|
|
889
952
|
|
|
890
|
-
/**
|
|
953
|
+
/**
|
|
954
|
+
* Resolve keep names immediately before the cleanup acts, never when
|
|
955
|
+
* enqueued. The action runs under versionFilesLock, so it must stay cheap
|
|
956
|
+
* (DownloadTask only renames doomed entries into a trash directory and
|
|
957
|
+
* deletes them after this returns).
|
|
958
|
+
*/
|
|
891
959
|
void runCleanupWithLatestState(CleanupAction action) {
|
|
892
960
|
synchronized (versionFilesLock) {
|
|
893
961
|
final String[] keepNames;
|
|
@@ -898,19 +966,19 @@ public class UpdateContext {
|
|
|
898
966
|
launchVersion
|
|
899
967
|
);
|
|
900
968
|
}
|
|
901
|
-
|
|
902
|
-
// JNI has only two keep slots; three distinct live versions are
|
|
903
|
-
// safer to defer until the next process than to delete one.
|
|
904
|
-
Log.i(TAG, "Skipping cleanup: three live versions need protection");
|
|
905
|
-
return;
|
|
906
|
-
}
|
|
907
|
-
action.run(keepNames[0], keepNames[1]);
|
|
969
|
+
action.run(keepNames);
|
|
908
970
|
}
|
|
909
971
|
}
|
|
910
972
|
|
|
911
|
-
|
|
973
|
+
/**
|
|
974
|
+
* Queues removal of every version entry older than maxAgeDays (0: all)
|
|
975
|
+
* except the keep names, which the queued task resolves at execution
|
|
976
|
+
* time so a later activation is protected too.
|
|
977
|
+
*/
|
|
978
|
+
private void cleanUp(int maxAgeDays) {
|
|
912
979
|
DownloadTaskParams params = new DownloadTaskParams();
|
|
913
980
|
params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
|
|
981
|
+
params.maxAgeDays = maxAgeDays;
|
|
914
982
|
params.unzipDirectory = rootDir;
|
|
915
983
|
enqueue(params);
|
|
916
984
|
}
|