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