react-native-update 10.59.0 → 10.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +46 -14
- package/android/jni/Android.mk +2 -1
- 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/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +6 -6
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +4 -4
- package/android/src/main/java/cn/reactnative/modules/update/{CrashRescue.java → CrashHold.java} +12 -12
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +28 -24
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +2 -1
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateFlow.java → FlowBridge.java} +6 -6
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +6 -3
- package/android/src/main/java/cn/reactnative/modules/update/{NativeUpdateCore.java → NativeCore.java} +4 -4
- package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +10 -5
- package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +4 -4
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +7 -7
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/{NativeCheckOrchestrator.java → SyncCoordinator.java} +79 -75
- package/android/src/main/java/cn/reactnative/modules/update/Texts.java +28 -0
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +28 -28
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +12 -9
- package/cpp/patch_core/archive_limits.h +5 -0
- package/cpp/patch_core/archive_patch_core.cpp +26 -19
- package/cpp/patch_core/archive_patch_core.h +17 -8
- package/cpp/patch_core/digest.h +6 -1
- package/cpp/patch_core/error_codes.h +8 -1
- package/cpp/patch_core/hbc_transform.h +5 -0
- package/cpp/patch_core/hbc_transform_wire.h +5 -0
- package/cpp/patch_core/install_record.h +5 -0
- package/cpp/patch_core/jni_natives.h +27 -0
- package/cpp/patch_core/jni_registration.cpp +103 -0
- package/cpp/patch_core/obscured_text.h +44 -0
- package/cpp/patch_core/patch_core.cpp +13 -13
- package/cpp/patch_core/patch_core.h +11 -6
- package/cpp/patch_core/patch_core_android.cpp +10 -10
- package/cpp/patch_core/state_core.h +5 -0
- package/cpp/patch_core/state_ops.h +5 -0
- package/cpp/patch_core/update_core_android.cpp +23 -22
- package/cpp/update_flow_core/flow_json.h +6 -1
- package/cpp/update_flow_core/update_flow_core.cpp +35 -18
- package/cpp/update_flow_core/update_flow_core.h +13 -8
- package/cpp/update_flow_core/update_flow_jni.cpp +11 -13
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +8 -8
- package/harmony/pushy/src/main/cpp/pushy.cpp +33 -33
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +15 -15
- package/harmony/pushy/src/main/ets/DownloadTask.ts +60 -60
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +13 -11
- package/harmony/pushy/src/main/ets/InstallRecord.ts +4 -4
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +9 -9
- package/harmony/pushy/src/main/ets/PathUtils.ts +2 -2
- package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -6
- package/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets +1 -1
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +42 -41
- package/harmony/pushy/src/main/ets/{NativeCheckOrchestrator.ts → SyncCoordinator.ts} +33 -32
- package/harmony/pushy/src/main/ets/Texts.ts +28 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +31 -30
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +148 -140
- package/ios/RCTPushy/RCTPushyConfiguration.h +8 -0
- package/ios/RCTPushy/RCTPushyConfiguration.mm +32 -5
- package/package.json +1 -1
- package/react-native-update.podspec +15 -3
- 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
|
@@ -55,8 +55,8 @@ final class ReactReloadManager {
|
|
|
55
55
|
reloadReactHost(reactHost, createBundleLoader(application, updateBundlePath, true));
|
|
56
56
|
return;
|
|
57
57
|
} catch (Throwable err) {
|
|
58
|
-
Log.e(
|
|
59
|
-
"Failed to
|
|
58
|
+
Log.e(Texts.LOG_TAG,
|
|
59
|
+
"Failed to restart via ReactHost, trying ReactInstanceManager", err);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -123,7 +123,7 @@ final class ReactReloadManager {
|
|
|
123
123
|
return (String) resolvedBundleAssetName;
|
|
124
124
|
}
|
|
125
125
|
} catch (Exception e) {
|
|
126
|
-
Log.e(
|
|
126
|
+
Log.e(Texts.LOG_TAG, "Failed to get default asset name from ReactNativeHost", e);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
return bundleAssetName;
|
|
@@ -170,7 +170,7 @@ final class ReactReloadManager {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
} catch (Throwable ignored) {
|
|
173
|
-
Log.w(
|
|
173
|
+
Log.w(Texts.LOG_TAG, "getReactHost via ReactDelegate reflection failed", ignored);
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -178,7 +178,7 @@ final class ReactReloadManager {
|
|
|
178
178
|
Method getReactHostMethod = application.getClass().getMethod("getReactHost");
|
|
179
179
|
return getReactHostMethod.invoke(application);
|
|
180
180
|
} catch (Throwable ignored) {
|
|
181
|
-
Log.w(
|
|
181
|
+
Log.w(Texts.LOG_TAG, "getReactHost via Application.getReactHost() failed", ignored);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
return null;
|
|
@@ -252,8 +252,8 @@ final class ReactReloadManager {
|
|
|
252
252
|
jsBundleLoaderField.setAccessible(true);
|
|
253
253
|
jsBundleLoaderField.set(reactHostDelegate, loader);
|
|
254
254
|
|
|
255
|
-
Method reloadMethod = reactHost.getClass().getMethod("
|
|
256
|
-
reloadMethod.invoke(reactHost,
|
|
255
|
+
Method reloadMethod = reactHost.getClass().getMethod(Texts.reveal("2812f8deaf8f"), String.class);
|
|
256
|
+
reloadMethod.invoke(reactHost, Texts.LOG_TAG);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
private static ReactInstanceManager resolveReactInstanceManager(
|
|
@@ -56,7 +56,7 @@ final class StateSerialRunner {
|
|
|
56
56
|
if (promise != null) {
|
|
57
57
|
promise.reject(errorCode, operationName + " failed", error);
|
|
58
58
|
} else {
|
|
59
|
-
Log.e(
|
|
59
|
+
Log.e(Texts.LOG_TAG, operationName + " failed", error);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -26,11 +26,15 @@ import org.json.JSONObject;
|
|
|
26
26
|
* process, a few seconds after getBundleUrl, entirely independent of the app
|
|
27
27
|
* bundle — this is what lets a device bricked by a bad hot update pull the
|
|
28
28
|
* fixed version on the next launch. All decisions come from
|
|
29
|
-
* cpp/update_flow_core via
|
|
29
|
+
* cpp/update_flow_core via FlowBridge; this class is IO glue only.
|
|
30
30
|
* Failures are silent and bounded: one round per launch, no retry storms, no
|
|
31
31
|
* version blacklisting.
|
|
32
32
|
*/
|
|
33
|
-
final class
|
|
33
|
+
final class SyncCoordinator {
|
|
34
|
+
// Version-info flags read by JS (metadata.ts), stored encoded.
|
|
35
|
+
private static final String INFO_CRASH_HOLD = Texts.reveal("3905f5c2a6b96d56212a19");
|
|
36
|
+
private static final String INFO_FORCE_BOOT = Texts.reveal("3c18e6d2aba9674a360d19ead5a695");
|
|
37
|
+
|
|
34
38
|
static final String KEY_CONFIG = "nativeConfig";
|
|
35
39
|
// Raw response cache for the JS side to reuse (§10.3), scoped to the
|
|
36
40
|
// exact logical request and native config that produced it.
|
|
@@ -39,11 +43,11 @@ final class NativeCheckOrchestrator {
|
|
|
39
43
|
// next launch means the previous process died mid-round (a crash rescue
|
|
40
44
|
// was truncated): that launch resumes immediately instead of waiting 5s.
|
|
41
45
|
static final String KEY_ROUND_INCOMPLETE = "nativeCheckIncomplete";
|
|
42
|
-
private static final int
|
|
46
|
+
private static final int MAX_QUERY_HTTP_ATTEMPTS = 8;
|
|
43
47
|
private static final long DOWNLOAD_PHASE_TIMEOUT_SECONDS = 600;
|
|
44
48
|
// The check response (and a remote queryUrls list) is a small JSON
|
|
45
49
|
// document; anything bigger is a broken or hijacked endpoint.
|
|
46
|
-
private static final long
|
|
50
|
+
private static final long MAX_QUERY_RESPONSE_BYTES = 1024 * 1024;
|
|
47
51
|
|
|
48
52
|
private static final AtomicBoolean scheduled = new AtomicBoolean(false);
|
|
49
53
|
// One round per process, whoever starts it first — the delayed cold-start
|
|
@@ -55,7 +59,7 @@ final class NativeCheckOrchestrator {
|
|
|
55
59
|
// Flipped the moment a crash is being held. JS is dead from that point
|
|
56
60
|
// on, so there is no second decision maker: the round force-activates
|
|
57
61
|
// whatever it downloads (§11.3).
|
|
58
|
-
private static volatile boolean
|
|
62
|
+
private static volatile boolean crashHoldActive = false;
|
|
59
63
|
// A version this process downloaded but left for JS to activate. If the
|
|
60
64
|
// process then crashes, JS will never activate it — the crash handler
|
|
61
65
|
// activates it directly (bounded local work, no network). The generation
|
|
@@ -118,7 +122,7 @@ final class NativeCheckOrchestrator {
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
|
|
121
|
-
static void
|
|
125
|
+
static void recordJsRound(String config) {
|
|
122
126
|
sJsCompletedConfig = config;
|
|
123
127
|
}
|
|
124
128
|
|
|
@@ -128,12 +132,12 @@ final class NativeCheckOrchestrator {
|
|
|
128
132
|
* duplicate request. Only the scheduled round consults this — the
|
|
129
133
|
* crash-rescue path still runs, JS is dead by then.
|
|
130
134
|
*/
|
|
131
|
-
private static boolean
|
|
135
|
+
private static boolean hasJsRound(UpdateContext context) {
|
|
132
136
|
String jsConfig = sJsCompletedConfig;
|
|
133
137
|
return jsConfig != null && jsConfig.equals(context.getKv(KEY_CONFIG));
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
private
|
|
140
|
+
private SyncCoordinator() {
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
static void schedule(final UpdateContext context, final String launchRolledBackVersion) {
|
|
@@ -149,7 +153,7 @@ final class NativeCheckOrchestrator {
|
|
|
149
153
|
// The crash-hold rescue shares the orchestrator's rollout gate: no
|
|
150
154
|
// persisted config, no handler (§11.3).
|
|
151
155
|
if (context.getKv(KEY_CONFIG) != null) {
|
|
152
|
-
|
|
156
|
+
CrashHold.install();
|
|
153
157
|
}
|
|
154
158
|
Thread thread = new Thread(new Runnable() {
|
|
155
159
|
@Override
|
|
@@ -161,20 +165,20 @@ final class NativeCheckOrchestrator {
|
|
|
161
165
|
if (context.getKv(KEY_ROUND_INCOMPLETE) == null) {
|
|
162
166
|
Thread.sleep(5000);
|
|
163
167
|
}
|
|
164
|
-
if (
|
|
168
|
+
if (hasJsRound(context)) {
|
|
165
169
|
// Not consuming the round: a later crash rescue may
|
|
166
170
|
// still need it.
|
|
167
|
-
Log.i(
|
|
168
|
-
"native
|
|
171
|
+
Log.i(Texts.LOG_TAG,
|
|
172
|
+
"native sync skipped: JS already queried in this process");
|
|
169
173
|
return;
|
|
170
174
|
}
|
|
171
175
|
startRound(0);
|
|
172
176
|
} catch (Throwable e) {
|
|
173
177
|
// The rescue path must never take the app down with it.
|
|
174
|
-
Log.w(
|
|
178
|
+
Log.w(Texts.LOG_TAG, "native sync failed: " + e);
|
|
175
179
|
}
|
|
176
180
|
}
|
|
177
|
-
}, "pushy-native-
|
|
181
|
+
}, "pushy-native-sync");
|
|
178
182
|
thread.setPriority(Thread.MIN_PRIORITY + 1);
|
|
179
183
|
thread.setDaemon(true);
|
|
180
184
|
thread.start();
|
|
@@ -209,7 +213,7 @@ final class NativeCheckOrchestrator {
|
|
|
209
213
|
|
|
210
214
|
static void onConfigured(UpdateContext context) {
|
|
211
215
|
if (nativeReady && sContext == context && hasRunnableConfig(context)) {
|
|
212
|
-
|
|
216
|
+
CrashHold.install();
|
|
213
217
|
}
|
|
214
218
|
}
|
|
215
219
|
|
|
@@ -225,7 +229,7 @@ final class NativeCheckOrchestrator {
|
|
|
225
229
|
try {
|
|
226
230
|
runOnce(sContext, sLaunchRolledBackVersion, deadlineNanos);
|
|
227
231
|
} catch (Throwable e) {
|
|
228
|
-
Log.w(
|
|
232
|
+
Log.w(Texts.LOG_TAG, "native sync failed: " + e);
|
|
229
233
|
roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "internal_error");
|
|
230
234
|
} finally {
|
|
231
235
|
roundCompleted = true;
|
|
@@ -240,12 +244,12 @@ final class NativeCheckOrchestrator {
|
|
|
240
244
|
* activates a downloaded-but-unactivated version if one exists — the
|
|
241
245
|
* last chance before the process is gone.
|
|
242
246
|
*/
|
|
243
|
-
static void
|
|
247
|
+
static void runHoldRound(long deadlineNanos) {
|
|
244
248
|
UpdateContext context = sContext;
|
|
245
249
|
if (context == null) {
|
|
246
250
|
return;
|
|
247
251
|
}
|
|
248
|
-
|
|
252
|
+
crashHoldActive = true;
|
|
249
253
|
startRound(deadlineNanos);
|
|
250
254
|
if (roundStarted.get() && !roundCompleted) {
|
|
251
255
|
long remainingNanos = deadlineNanos - System.nanoTime();
|
|
@@ -275,23 +279,23 @@ final class NativeCheckOrchestrator {
|
|
|
275
279
|
if (existingInfo != null) {
|
|
276
280
|
try {
|
|
277
281
|
JSONObject info = new JSONObject(existingInfo);
|
|
278
|
-
info.put(
|
|
282
|
+
info.put(INFO_CRASH_HOLD, true);
|
|
279
283
|
hashInfoJson = info.toString();
|
|
280
284
|
} catch (JSONException ignored) {
|
|
281
285
|
}
|
|
282
286
|
}
|
|
283
287
|
try {
|
|
284
|
-
if (context.
|
|
288
|
+
if (context.commitSyncResult(
|
|
285
289
|
unactivatedGeneration, hash, hashInfoJson, true, null)) {
|
|
286
290
|
unactivatedHash = null;
|
|
287
|
-
Log.i(
|
|
288
|
-
"crash
|
|
291
|
+
Log.i(Texts.LOG_TAG,
|
|
292
|
+
"crash hold: activated downloaded version " + hash);
|
|
289
293
|
} else {
|
|
290
|
-
Log.i(
|
|
291
|
-
"crash
|
|
294
|
+
Log.i(Texts.LOG_TAG,
|
|
295
|
+
"crash hold: reset since download, dropping activation");
|
|
292
296
|
}
|
|
293
297
|
} catch (Exception e) {
|
|
294
|
-
Log.w(
|
|
298
|
+
Log.w(Texts.LOG_TAG, "crash hold: activation failed: " + e);
|
|
295
299
|
}
|
|
296
300
|
}
|
|
297
301
|
|
|
@@ -396,26 +400,26 @@ final class NativeCheckOrchestrator {
|
|
|
396
400
|
);
|
|
397
401
|
input.put("buildTime", context.getBuildTime());
|
|
398
402
|
input.put("cInfo", cInfo);
|
|
399
|
-
input.put("supportedDiffVersion",
|
|
403
|
+
input.put("supportedDiffVersion", NativeCore.supportedDiffVersion());
|
|
400
404
|
input.put("bundleHash", context.computeBundleHash());
|
|
401
405
|
|
|
402
|
-
String body =
|
|
406
|
+
String body = FlowBridge.buildRequestBody(input.toString());
|
|
403
407
|
if (body == null) {
|
|
404
408
|
roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "invalid_request");
|
|
405
409
|
return;
|
|
406
410
|
}
|
|
407
411
|
|
|
408
|
-
String responseText =
|
|
412
|
+
String responseText = runQueryRequest(config, appKey, body, deadlineNanos);
|
|
409
413
|
if (responseText == null) {
|
|
410
|
-
Log.i(
|
|
411
|
-
"native
|
|
414
|
+
Log.i(Texts.LOG_TAG,
|
|
415
|
+
"native sync: no endpoint reachable, giving up until next launch");
|
|
412
416
|
return;
|
|
413
417
|
}
|
|
414
418
|
// Cache freshness is anchored to when the server response arrived,
|
|
415
419
|
// not to when a potentially long download/patch/activation finished.
|
|
416
420
|
final long responseAtSeconds = System.currentTimeMillis() / 1000;
|
|
417
421
|
|
|
418
|
-
String decisionJson =
|
|
422
|
+
String decisionJson = FlowBridge.handleResponse(
|
|
419
423
|
responseText, identity.toString(), config.optString("afterDownload", ""));
|
|
420
424
|
if (decisionJson == null) {
|
|
421
425
|
roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "invalid_response");
|
|
@@ -423,14 +427,14 @@ final class NativeCheckOrchestrator {
|
|
|
423
427
|
}
|
|
424
428
|
JSONObject decision = new JSONObject(decisionJson);
|
|
425
429
|
if (!"download".equals(decision.optString("action"))) {
|
|
426
|
-
boolean committed = context.
|
|
430
|
+
boolean committed = context.commitSyncResult(
|
|
427
431
|
resetGeneration, null, null, false,
|
|
428
432
|
buildResponseCacheJson(configJson, body, responseText, responseAtSeconds));
|
|
429
433
|
roundResult = committed
|
|
430
434
|
? BundlePreparationResult.of(BundlePreparationResult.NO_UPDATE, decision.optString("reason"))
|
|
431
435
|
: BundlePreparationResult.of(BundlePreparationResult.CANCELLED, "reset");
|
|
432
|
-
Log.i(
|
|
433
|
-
"native
|
|
436
|
+
Log.i(Texts.LOG_TAG,
|
|
437
|
+
"native sync: nothing to do (" + decision.optString("reason") + ")");
|
|
434
438
|
return;
|
|
435
439
|
}
|
|
436
440
|
String hash = decision.optString("hash", "");
|
|
@@ -448,7 +452,7 @@ final class NativeCheckOrchestrator {
|
|
|
448
452
|
if (!downloaded) {
|
|
449
453
|
// The native attempt has finished, so JS may safely reuse the
|
|
450
454
|
// response and retry through its own strategy chain.
|
|
451
|
-
boolean committed = context.
|
|
455
|
+
boolean committed = context.commitSyncResult(
|
|
452
456
|
resetGeneration, null, null, false,
|
|
453
457
|
buildResponseCacheJson(configJson, body, responseText, responseAtSeconds));
|
|
454
458
|
roundResult = BundlePreparationResult.of(
|
|
@@ -459,7 +463,7 @@ final class NativeCheckOrchestrator {
|
|
|
459
463
|
|
|
460
464
|
// Version info (mirroring the JS side's setLocalHashInfo), the
|
|
461
465
|
// activation and the response cache all land in one atomic commit —
|
|
462
|
-
// see UpdateContext.
|
|
466
|
+
// see UpdateContext.commitSyncResult.
|
|
463
467
|
String hashInfoJson = null;
|
|
464
468
|
JSONObject info = decision.optJSONObject("info");
|
|
465
469
|
if (info != null) {
|
|
@@ -476,10 +480,10 @@ final class NativeCheckOrchestrator {
|
|
|
476
480
|
// counts — a silent-strategy activation is ordinary delivery.
|
|
477
481
|
JSONObject infoConfig = info.optJSONObject("config");
|
|
478
482
|
if (infoConfig != null && infoConfig.optBoolean("forceBoot", false)) {
|
|
479
|
-
hashInfo.put(
|
|
483
|
+
hashInfo.put(INFO_FORCE_BOOT, true);
|
|
480
484
|
}
|
|
481
|
-
if (
|
|
482
|
-
hashInfo.put(
|
|
485
|
+
if (crashHoldActive) {
|
|
486
|
+
hashInfo.put(INFO_CRASH_HOLD, true);
|
|
483
487
|
}
|
|
484
488
|
hashInfoJson = hashInfo.toString();
|
|
485
489
|
}
|
|
@@ -488,33 +492,33 @@ final class NativeCheckOrchestrator {
|
|
|
488
492
|
// otherwise activation stays with the JS side. Unless a crash is
|
|
489
493
|
// being held: JS is dead, deferring to it would leave the fix on
|
|
490
494
|
// disk forever (§11.3).
|
|
491
|
-
boolean activate = decision.optBoolean("activate", false) ||
|
|
495
|
+
boolean activate = decision.optBoolean("activate", false) || crashHoldActive;
|
|
492
496
|
boolean committed;
|
|
493
497
|
try {
|
|
494
|
-
committed = context.
|
|
498
|
+
committed = context.commitSyncResult(
|
|
495
499
|
resetGeneration,
|
|
496
500
|
hash,
|
|
497
501
|
hashInfoJson,
|
|
498
502
|
activate,
|
|
499
503
|
buildResponseCacheJson(configJson, body, responseText, responseAtSeconds));
|
|
500
504
|
} catch (Exception e) {
|
|
501
|
-
Log.w(
|
|
505
|
+
Log.w(Texts.LOG_TAG, "native sync: commit failed: " + e);
|
|
502
506
|
roundResult = BundlePreparationResult.of(BundlePreparationResult.FAILED, "commit_failed");
|
|
503
507
|
return;
|
|
504
508
|
}
|
|
505
509
|
if (!committed) {
|
|
506
|
-
Log.i(
|
|
510
|
+
Log.i(Texts.LOG_TAG, "native sync: reset during round, dropping result");
|
|
507
511
|
} else if (activate) {
|
|
508
512
|
unactivatedHash = null;
|
|
509
|
-
Log.i(
|
|
510
|
-
"native
|
|
513
|
+
Log.i(Texts.LOG_TAG,
|
|
514
|
+
"native sync: downloaded " + hash + " and set for next launch");
|
|
511
515
|
} else {
|
|
512
516
|
// Remembered so a crash later in this process can still activate
|
|
513
517
|
// it (activatePendingVersion) — JS never will.
|
|
514
518
|
unactivatedGeneration = resetGeneration;
|
|
515
519
|
unactivatedHash = hash;
|
|
516
|
-
Log.i(
|
|
517
|
-
"native
|
|
520
|
+
Log.i(Texts.LOG_TAG,
|
|
521
|
+
"native sync: downloaded " + hash + ", activation left to JS");
|
|
518
522
|
}
|
|
519
523
|
roundResult = committed
|
|
520
524
|
? BundlePreparationResult.downloaded(hash, activate)
|
|
@@ -577,7 +581,7 @@ final class NativeCheckOrchestrator {
|
|
|
577
581
|
} catch (Exception e) {
|
|
578
582
|
// One line per failed endpoint; the fallback chain is otherwise
|
|
579
583
|
// invisible in the field.
|
|
580
|
-
Log.w(
|
|
584
|
+
Log.w(Texts.LOG_TAG, "native sync: request failed with "
|
|
581
585
|
+ e.getClass().getName() + ": " + e.getMessage());
|
|
582
586
|
return null;
|
|
583
587
|
}
|
|
@@ -590,13 +594,13 @@ final class NativeCheckOrchestrator {
|
|
|
590
594
|
*/
|
|
591
595
|
@Nullable
|
|
592
596
|
private static String readBoundedBody(ResponseBody body) throws IOException {
|
|
593
|
-
if (body.contentLength() >
|
|
597
|
+
if (body.contentLength() > MAX_QUERY_RESPONSE_BYTES) {
|
|
594
598
|
return null;
|
|
595
599
|
}
|
|
596
600
|
BufferedSource source = body.source();
|
|
597
|
-
if (source.request(
|
|
598
|
-
Log.w(
|
|
599
|
-
+
|
|
601
|
+
if (source.request(MAX_QUERY_RESPONSE_BYTES + 1)) {
|
|
602
|
+
Log.w(Texts.LOG_TAG, "native sync: response exceeds "
|
|
603
|
+
+ MAX_QUERY_RESPONSE_BYTES + " bytes, ignoring endpoint");
|
|
600
604
|
return null;
|
|
601
605
|
}
|
|
602
606
|
MediaType contentType = body.contentType();
|
|
@@ -605,11 +609,11 @@ final class NativeCheckOrchestrator {
|
|
|
605
609
|
return source.readString(charset == null ? StandardCharsets.UTF_8 : charset);
|
|
606
610
|
}
|
|
607
611
|
|
|
608
|
-
// Shared schema rule (update_flow_core::
|
|
612
|
+
// Shared schema rule (update_flow_core::IsValidResponse): a 200 with
|
|
609
613
|
// `{"error": ...}` is a failed endpoint, not a verdict, and must not stop
|
|
610
614
|
// the endpoint fallback.
|
|
611
|
-
private static boolean
|
|
612
|
-
return responseText != null &&
|
|
615
|
+
private static boolean isValidResponse(String responseText) {
|
|
616
|
+
return responseText != null && FlowBridge.isValidResponse(responseText);
|
|
613
617
|
}
|
|
614
618
|
|
|
615
619
|
/**
|
|
@@ -619,11 +623,11 @@ final class NativeCheckOrchestrator {
|
|
|
619
623
|
* already-tried) for one more round. No hedged race on purpose — this
|
|
620
624
|
* path is latency-insensitive.
|
|
621
625
|
*/
|
|
622
|
-
private static String
|
|
626
|
+
private static String runQueryRequest(
|
|
623
627
|
JSONObject config, String appKey, String body, long deadlineNanos
|
|
624
628
|
) {
|
|
625
629
|
JSONArray endpoints = config.optJSONArray("endpoints");
|
|
626
|
-
String orderedJson =
|
|
630
|
+
String orderedJson = FlowBridge.orderEndpointCandidates(
|
|
627
631
|
endpoints == null ? "[]" : endpoints.toString(), Math.random());
|
|
628
632
|
JSONArray ordered;
|
|
629
633
|
try {
|
|
@@ -638,12 +642,12 @@ final class NativeCheckOrchestrator {
|
|
|
638
642
|
if (base.isEmpty() || !tried.add(base)) {
|
|
639
643
|
continue;
|
|
640
644
|
}
|
|
641
|
-
if (httpAttempts++ >=
|
|
645
|
+
if (httpAttempts++ >= MAX_QUERY_HTTP_ATTEMPTS) {
|
|
642
646
|
return null;
|
|
643
647
|
}
|
|
644
648
|
String response = httpRequest(
|
|
645
|
-
base +
|
|
646
|
-
if (
|
|
649
|
+
base + HttpUtils.QUERY_PATH + appKey, body, deadlineNanos);
|
|
650
|
+
if (isValidResponse(response)) {
|
|
647
651
|
return response;
|
|
648
652
|
}
|
|
649
653
|
}
|
|
@@ -656,7 +660,7 @@ final class NativeCheckOrchestrator {
|
|
|
656
660
|
if (listUrl.isEmpty()) {
|
|
657
661
|
continue;
|
|
658
662
|
}
|
|
659
|
-
if (httpAttempts++ >=
|
|
663
|
+
if (httpAttempts++ >= MAX_QUERY_HTTP_ATTEMPTS) {
|
|
660
664
|
return null;
|
|
661
665
|
}
|
|
662
666
|
String listText = httpRequest(listUrl, null, deadlineNanos);
|
|
@@ -674,13 +678,13 @@ final class NativeCheckOrchestrator {
|
|
|
674
678
|
if (base.isEmpty() || tried.contains(base)) {
|
|
675
679
|
continue;
|
|
676
680
|
}
|
|
677
|
-
if (httpAttempts++ >=
|
|
681
|
+
if (httpAttempts++ >= MAX_QUERY_HTTP_ATTEMPTS) {
|
|
678
682
|
return null;
|
|
679
683
|
}
|
|
680
684
|
tried.add(base);
|
|
681
685
|
String response = httpRequest(
|
|
682
|
-
base +
|
|
683
|
-
if (
|
|
686
|
+
base + HttpUtils.QUERY_PATH + appKey, body, deadlineNanos);
|
|
687
|
+
if (isValidResponse(response)) {
|
|
684
688
|
return response;
|
|
685
689
|
}
|
|
686
690
|
}
|
|
@@ -690,23 +694,23 @@ final class NativeCheckOrchestrator {
|
|
|
690
694
|
return null;
|
|
691
695
|
}
|
|
692
696
|
|
|
693
|
-
private static long
|
|
694
|
-
if (
|
|
697
|
+
private static long capToHoldBudget(long phaseDeadlineNanos, long holdDeadlineNanos) {
|
|
698
|
+
if (holdDeadlineNanos <= 0) {
|
|
695
699
|
return phaseDeadlineNanos;
|
|
696
700
|
}
|
|
697
|
-
return Math.min(phaseDeadlineNanos,
|
|
701
|
+
return Math.min(phaseDeadlineNanos, holdDeadlineNanos);
|
|
698
702
|
}
|
|
699
703
|
|
|
700
704
|
private static boolean performAttempts(
|
|
701
705
|
UpdateContext context, JSONArray attempts, String hash, String originHash,
|
|
702
|
-
long
|
|
706
|
+
long holdDeadlineNanos
|
|
703
707
|
) {
|
|
704
708
|
if (attempts == null) {
|
|
705
709
|
return false;
|
|
706
710
|
}
|
|
707
|
-
final long incrementalDeadlineNanos =
|
|
711
|
+
final long incrementalDeadlineNanos = capToHoldBudget(
|
|
708
712
|
System.nanoTime() + TimeUnit.SECONDS.toNanos(DOWNLOAD_PHASE_TIMEOUT_SECONDS),
|
|
709
|
-
|
|
713
|
+
holdDeadlineNanos);
|
|
710
714
|
long fullDeadlineNanos = 0;
|
|
711
715
|
for (int i = 0; i < attempts.length(); i++) {
|
|
712
716
|
JSONObject attempt = attempts.optJSONObject(i);
|
|
@@ -722,10 +726,10 @@ final class NativeCheckOrchestrator {
|
|
|
722
726
|
if (isFullAttempt && fullDeadlineNanos == 0) {
|
|
723
727
|
// Incremental failures must not consume the last-resort full
|
|
724
728
|
// download's budget. Each phase gets one bounded 10min window.
|
|
725
|
-
fullDeadlineNanos =
|
|
729
|
+
fullDeadlineNanos = capToHoldBudget(
|
|
726
730
|
System.nanoTime()
|
|
727
731
|
+ TimeUnit.SECONDS.toNanos(DOWNLOAD_PHASE_TIMEOUT_SECONDS),
|
|
728
|
-
|
|
732
|
+
holdDeadlineNanos);
|
|
729
733
|
}
|
|
730
734
|
final long deadlineNanos = isFullAttempt
|
|
731
735
|
? fullDeadlineNanos : incrementalDeadlineNanos;
|
|
@@ -760,7 +764,7 @@ final class NativeCheckOrchestrator {
|
|
|
760
764
|
|
|
761
765
|
@Override
|
|
762
766
|
public void onDownloadFailed(Throwable error) {
|
|
763
|
-
Log.i(
|
|
767
|
+
Log.i(Texts.LOG_TAG, "native sync: " + attemptType
|
|
764
768
|
+ " attempt failed: " + error);
|
|
765
769
|
latch.countDown();
|
|
766
770
|
}
|
|
@@ -778,8 +782,8 @@ final class NativeCheckOrchestrator {
|
|
|
778
782
|
}
|
|
779
783
|
try {
|
|
780
784
|
if (!latch.await(remainingNanos, TimeUnit.NANOSECONDS)) {
|
|
781
|
-
Log.w(
|
|
782
|
-
"native
|
|
785
|
+
Log.w(Texts.LOG_TAG,
|
|
786
|
+
"native sync: download phase timed out during " + type);
|
|
783
787
|
// The task shares one download thread with the next
|
|
784
788
|
// attempt: cancel its transfer (or keep it from
|
|
785
789
|
// starting) instead of queueing behind it
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package cn.reactnative.modules.update;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encoded text helpers with no Android or native dependencies, so any class
|
|
5
|
+
* (and the plain-JVM unit tests) can use them without triggering
|
|
6
|
+
* UpdateContext's native library load.
|
|
7
|
+
*/
|
|
8
|
+
final class Texts {
|
|
9
|
+
/** Log tag shared by the module. */
|
|
10
|
+
static final String LOG_TAG = reveal("2812f5d2bac6664436360afc9ba680694b3301");
|
|
11
|
+
|
|
12
|
+
private Texts() {
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Decodes text produced by scripts/encode-native-text.ts: byte i is XORed
|
|
17
|
+
* with (0x5A + 0x1D * i) & 0xFF. Keeps service addresses and paths out of
|
|
18
|
+
* static string scans of the binary; it is not a secret.
|
|
19
|
+
*/
|
|
20
|
+
static String reveal(String hex) {
|
|
21
|
+
char[] out = new char[hex.length() / 2];
|
|
22
|
+
for (int i = 0; i < out.length; i++) {
|
|
23
|
+
int b = Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
24
|
+
out[i] = (char) (b ^ ((0x5A + 0x1D * i) & 0xFF));
|
|
25
|
+
}
|
|
26
|
+
return new String(out);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -28,7 +28,7 @@ final class UiThreadRunner {
|
|
|
28
28
|
if (promise != null) {
|
|
29
29
|
promise.reject(errorCode, operationName + " failed", error);
|
|
30
30
|
} else {
|
|
31
|
-
Log.e(
|
|
31
|
+
Log.e(Texts.LOG_TAG, operationName + " failed", error);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|