react-native-update 10.59.2 → 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 +2 -7
- 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 +17 -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 -2634
- 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 +73 -82
- 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
package/README-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English README](./README.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`react-native-update`(Pushy)是面向 React Native 的热更新(OTA)组件,可替代已于 2025 年 3 月 31 日随 App Center 停服的 Microsoft CodePush。它下发针对 Hermes 字节码优化的增量包(改一行代码约 3.4 KB),新版本崩溃自动回滚,支持灰度发布、数据分析与 JS 报错堆栈还原,覆盖 iOS、Android 和鸿蒙(HarmonyOS),兼容 Expo 与新架构。详情请访问官方网站 <https://pushy.reactnative.cn>。
|
|
6
6
|
|
|
7
7
|
### 区域服务说明
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
### 快速开始
|
|
16
16
|
|
|
17
|
-
请查看[文档](https://pushy.reactnative.cn/docs/getting-started
|
|
17
|
+
请查看[文档](https://pushy.reactnative.cn/docs/getting-started)
|
|
18
18
|
|
|
19
19
|
### 配置项速查
|
|
20
20
|
|
|
@@ -102,15 +102,15 @@ Hermes 字节码对通用二进制 diff 极不友好,我们在两个环节同
|
|
|
102
102
|
|
|
103
103
|
此外字节码在产出时就不含 debug info 段(实测小 **21%**,与 React Native 自身 release 构建一致),全量包和由它派生的每一个补丁都随之变小。
|
|
104
104
|
|
|
105
|
-
以下数据在真实 RN 0.86 应用的 release 产物上实测(Hermes HBC v98
|
|
105
|
+
以下数据在真实 RN 0.86 应用的 release 产物上实测(Hermes HBC v98,剥离调试信息后字节码约 3.5 MB,即实际下发的产物)。每个补丁都先通过完整往返校验再记录体积,每个 delta 模式产物都通过与普通编译的等价性校验。完整的评测方法、测试数据与可复现代码见 **[hbc-diff-benchmark](https://github.com/sunnylqm/hbc-diff-benchmark)**。
|
|
106
106
|
|
|
107
107
|
**Hermes 字节码(.hbc)——生产环境 Hermes 应用的实际发包格式:**
|
|
108
108
|
|
|
109
109
|
| 迭代场景 | 全量 OTA(gzip) | bsdiff | **react-native-update** | 对比 bsdiff |
|
|
110
110
|
|---|---|---|---|---|
|
|
111
|
-
| 单行文案修改 |
|
|
112
|
-
| 小功能(约 60 行) |
|
|
113
|
-
| 中等功能(约 300 行) |
|
|
111
|
+
| 单行文案修改 | 1544.6 KB | 93.7 KB | **3.4 KB** | **小 28 倍** |
|
|
112
|
+
| 小功能(约 60 行) | 1555.8 KB | 411.6 KB | **50.2 KB** | **小 8.2 倍** |
|
|
113
|
+
| 中等功能(约 300 行) | 1604.7 KB | 551.6 KB | **97.8 KB** | **小 5.6 倍** |
|
|
114
114
|
|
|
115
115
|
**文本 JS bundle——非 Hermes 应用(同一应用、同样三个场景):**
|
|
116
116
|
|
|
@@ -178,4 +178,4 @@ Hermes 字节码对通用二进制 diff 极不友好,我们在两个环节同
|
|
|
178
178
|
原生宿主入口统一为 `prepareBundle`:Android 使用 `PushyRuntime`,
|
|
179
179
|
Apple 平台使用 `RCTPushy`,Harmony 使用已有的 `PushyFileJSBundleProvider`。
|
|
180
180
|
请在正常启动 bundle 解析后调用。改名不改变 JS 桥接契约,但原生接入代码需要迁移并重新构建。
|
|
181
|
-
详见[原生接口迁移说明](NATIVE_HOST_API_MIGRATION.md)。
|
|
181
|
+
详见[原生接口迁移说明](docs/NATIVE_HOST_API_MIGRATION.md)。
|
package/README.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[中文文档](./README-CN.md)
|
|
4
4
|
|
|
5
|
-
`react-native-update`
|
|
5
|
+
`react-native-update` is an over-the-air (OTA) hot update library for React Native, and a maintained alternative to Microsoft CodePush (retired with App Center on March 31, 2025). It ships Hermes-optimized incremental patches (a one-line fix is about 3.4 KB), rolls back crashing updates automatically, supports staged rollouts, analytics and JS error reporting with source-mapped stacks, and runs on iOS, Android and HarmonyOS, including Expo projects and the New Architecture. For full documentation, visit:
|
|
6
6
|
|
|
7
7
|
- Global service: <https://cresc.dev>
|
|
8
|
+
- China service: <https://pushy.reactnative.cn>
|
|
8
9
|
|
|
9
10
|
**React Native New Architecture is supported.**
|
|
10
11
|
|
|
@@ -105,15 +106,15 @@ Hermes bytecode is hostile to generic binary diffing, and we attack that at two
|
|
|
105
106
|
|
|
106
107
|
The bytecode is also emitted without its debug-info section — a flat **21%** smaller, the same thing React Native's own release builds do — which shrinks the full package and every patch derived from it.
|
|
107
108
|
|
|
108
|
-
The numbers below are measured on real release bundles of a React Native 0.86 app (Hermes HBC v98, ~
|
|
109
|
+
The numbers below are measured on real release bundles of a React Native 0.86 app (Hermes HBC v98, ~3.5 MB bytecode with debug info stripped, as shipped). Every patch is verified by an actual round-trip before its size is recorded, and every delta-mode build is verified equivalent to a plain compile. Full methodology, fixtures, and runnable code: **[hbc-diff-benchmark](https://github.com/sunnylqm/hbc-diff-benchmark)**.
|
|
109
110
|
|
|
110
111
|
**Hermes bytecode (.hbc) — what ships in production Hermes apps:**
|
|
111
112
|
|
|
112
113
|
| Scenario | Full OTA (gzip) | bsdiff | **react-native-update** | vs bsdiff |
|
|
113
114
|
|---|---|---|---|---|
|
|
114
|
-
| One-line text change |
|
|
115
|
-
| Small feature (~60 LOC) |
|
|
116
|
-
| Medium feature (~300 LOC) |
|
|
115
|
+
| One-line text change | 1544.6 KB | 93.7 KB | **3.4 KB** | **28× smaller** |
|
|
116
|
+
| Small feature (~60 LOC) | 1555.8 KB | 411.6 KB | **50.2 KB** | **8.2× smaller** |
|
|
117
|
+
| Medium feature (~300 LOC) | 1604.7 KB | 551.6 KB | **97.8 KB** | **5.6× smaller** |
|
|
117
118
|
|
|
118
119
|
**Plain-text JS bundle — non-Hermes apps (same scenarios, same app):**
|
|
119
120
|
|
|
@@ -159,5 +160,5 @@ Since 10.52.1 every cold start runs one background update check that **does not
|
|
|
159
160
|
Bridge-free hosts use `PushyRuntime.prepareBundle` (Android),
|
|
160
161
|
`RCTPushy.prepareBundle` (Swift), or `PushyFileJSBundleProvider.prepareBundle`
|
|
161
162
|
(HarmonyOS), after normal launch bundle resolution. See the
|
|
162
|
-
[native host API migration guide](NATIVE_HOST_API_MIGRATION.md) for the
|
|
163
|
+
[native host API migration guide](docs/NATIVE_HOST_API_MIGRATION.md) for the
|
|
163
164
|
source-breaking rename, unchanged JS bridge contract, and native rebuild steps.
|
package/android/build.gradle
CHANGED
|
@@ -56,34 +56,17 @@ def parseDeclaredMajorVersion(String declared) {
|
|
|
56
56
|
def resolveInstalledExpoMajorVersion(File projectDir) {
|
|
57
57
|
def nodeArgs = ['node', '--print', "require('expo/package.json').version"]
|
|
58
58
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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()
|
|
59
|
+
// providers.exec exists since Gradle 7.5 (Expo SDK 50+ requires
|
|
60
|
+
// Gradle 8); Project.exec is gone in 9.
|
|
61
|
+
def output = providers.exec {
|
|
62
|
+
workingDir projectDir
|
|
63
|
+
commandLine nodeArgs
|
|
64
|
+
ignoreExitValue = true
|
|
84
65
|
}
|
|
85
|
-
|
|
86
|
-
|
|
66
|
+
if (output.result.get().exitValue != 0) {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
return parseMajorVersion(output.standardOutput.asText.get().trim())
|
|
87
70
|
} catch (Exception ignored) {
|
|
88
71
|
return null
|
|
89
72
|
}
|
|
@@ -154,18 +137,16 @@ if (isNewArchitectureEnabled()) {
|
|
|
154
137
|
apply plugin: 'com.facebook.react'
|
|
155
138
|
}
|
|
156
139
|
|
|
140
|
+
version = projectVersion
|
|
157
141
|
if (expoProject) {
|
|
158
142
|
group = 'expo.modules.pushy'
|
|
159
|
-
version = projectVersion
|
|
160
143
|
|
|
161
144
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
162
145
|
apply from: expoModulesCorePlugin
|
|
163
146
|
applyKotlinExpoModulesCorePlugin()
|
|
164
|
-
// useExpoPublishing()
|
|
165
147
|
useCoreDependencies()
|
|
166
148
|
} else {
|
|
167
149
|
group = 'cn.reactnative.modules.update'
|
|
168
|
-
version = projectVersion
|
|
169
150
|
}
|
|
170
151
|
|
|
171
152
|
android {
|
|
@@ -179,6 +160,9 @@ android {
|
|
|
179
160
|
}
|
|
180
161
|
}
|
|
181
162
|
compileSdkVersion safeExtGet('compileSdkVersion', 34)
|
|
163
|
+
// Follows the app's ext.buildToolsVersion: without it AGP falls back to
|
|
164
|
+
// its own default, which hosts that only install the app's build-tools
|
|
165
|
+
// then have to download.
|
|
182
166
|
buildToolsVersion safeExtGet('buildToolsVersion', '34.0.0')
|
|
183
167
|
defaultConfig {
|
|
184
168
|
minSdkVersion safeExtGet('minSdkVersion', 21)
|
|
@@ -186,14 +170,22 @@ android {
|
|
|
186
170
|
consumerProguardFiles "proguard.pro"
|
|
187
171
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
188
172
|
}
|
|
173
|
+
|
|
174
|
+
// The sources use lambdas, which AGP < 4.2 (React Native < 0.65) only
|
|
175
|
+
// compiles with an explicit Java 8 level. React Native's Gradle plugin
|
|
176
|
+
// (0.73+) raises every module to its own JDK level in finalizeDsl.
|
|
177
|
+
compileOptions {
|
|
178
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
179
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
180
|
+
}
|
|
189
181
|
|
|
190
182
|
sourceSets {
|
|
191
183
|
main {
|
|
192
184
|
// let gradle pack the shared library into apk
|
|
193
185
|
jniLibs.srcDirs = ['./lib']
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
186
|
+
// Under the new architecture codegen generates NativePushySpec;
|
|
187
|
+
// the old one compiles the hand-written stand-in instead.
|
|
188
|
+
if (!isNewArchitectureEnabled()) {
|
|
197
189
|
java.srcDirs += ['src/oldarch']
|
|
198
190
|
}
|
|
199
191
|
|
package/android/jni/Android.mk
CHANGED
|
@@ -3,36 +3,28 @@ LOCAL_PATH := $(call my-dir)
|
|
|
3
3
|
include $(CLEAR_VARS)
|
|
4
4
|
|
|
5
5
|
LOCAL_MODULE := rnpushy
|
|
6
|
-
|
|
7
|
-
LOCAL_LDFLAGS += -Wl,--exclude-libs,ALL
|
|
8
|
-
LOCAL_C_INCLUDES := \
|
|
9
|
-
$(LOCAL_PATH) \
|
|
10
|
-
$(LOCAL_PATH)/HDiffPatch \
|
|
11
|
-
$(LOCAL_PATH)/HDiffPatch/libHDiffPatch/HPatch \
|
|
12
|
-
$(LOCAL_PATH)/lzma/C \
|
|
13
|
-
$(LOCAL_PATH)/../../cpp/patch_core \
|
|
14
|
-
$(LOCAL_PATH)/../../cpp/update_flow_core
|
|
6
|
+
LOCAL_LDLIBS := -llog
|
|
15
7
|
|
|
8
|
+
# The shared cores (cpp/patch_core, cpp/update_flow_core, cpp/hpatch) and the
|
|
9
|
+
# Android JNI glue (cpp/android_jni) are picked up by directory, so a new
|
|
10
|
+
# source file there needs no edit here; tests/ subdirectories are not matched.
|
|
11
|
+
# Every include is file-relative (as on iOS), so no include path is needed.
|
|
12
|
+
CPP_DIR := $(LOCAL_PATH)/../../cpp
|
|
13
|
+
Shared_Files := \
|
|
14
|
+
$(sort $(wildcard $(CPP_DIR)/patch_core/*.cpp)) \
|
|
15
|
+
$(sort $(wildcard $(CPP_DIR)/update_flow_core/*.cpp)) \
|
|
16
|
+
$(sort $(wildcard $(CPP_DIR)/android_jni/*.cpp)) \
|
|
17
|
+
$(sort $(wildcard $(CPP_DIR)/hpatch/*.c))
|
|
18
|
+
|
|
19
|
+
# HDiffPatch / lzma submodules: only the decoder sources hpatch.c uses.
|
|
16
20
|
Hdp_Files := \
|
|
17
|
-
|
|
18
|
-
HDiffPatch/libHDiffPatch/HPatch/patch.c \
|
|
21
|
+
HDiffPatch/libHDiffPatch/HPatch/patch.c \
|
|
19
22
|
HDiffPatch/file_for_patch.c \
|
|
20
23
|
lzma/C/LzmaDec.c \
|
|
21
|
-
|
|
24
|
+
lzma/C/Lzma2Dec.c
|
|
22
25
|
|
|
23
26
|
LOCAL_SRC_FILES := \
|
|
24
|
-
|
|
25
|
-
../../cpp/patch_core/digest.cpp \
|
|
26
|
-
../../cpp/patch_core/hbc_transform.cpp \
|
|
27
|
-
../../cpp/patch_core/hbc_transform_wire.cpp \
|
|
28
|
-
../../cpp/patch_core/jni_registration.cpp \
|
|
29
|
-
../../cpp/patch_core/patch_core.cpp \
|
|
30
|
-
../../cpp/patch_core/patch_core_android.cpp \
|
|
31
|
-
../../cpp/patch_core/state_core.cpp \
|
|
32
|
-
../../cpp/patch_core/update_core_android.cpp \
|
|
33
|
-
../../cpp/update_flow_core/flow_json.cpp \
|
|
34
|
-
../../cpp/update_flow_core/update_flow_core.cpp \
|
|
35
|
-
../../cpp/update_flow_core/update_flow_jni.cpp \
|
|
27
|
+
$(patsubst $(LOCAL_PATH)/%,%,$(Shared_Files)) \
|
|
36
28
|
$(Hdp_Files)
|
|
37
29
|
|
|
38
30
|
include $(BUILD_SHARED_LIBRARY)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/android/proguard.pro
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
# JNI-constructed result classes: cpp/
|
|
1
|
+
# JNI-constructed result classes: cpp/android_jni/update_core_android.cpp
|
|
2
2
|
# resolves them by FindClass + GetMethodID("<init>") + GetFieldID, so the
|
|
3
3
|
# no-arg constructor and every field must survive shrinking with their names
|
|
4
4
|
# (a -keepnames rule alone lets R8 strip the unreferenced constructor/fields).
|
|
5
5
|
-keep class cn.reactnative.modules.update.StateCoreResult { <init>(); <fields>; }
|
|
6
|
-
-keep class cn.reactnative.modules.update.ArchivePatchPlanResult { <init>(); <fields>; }
|
|
7
6
|
-keep class cn.reactnative.modules.update.CopyGroupResult { <init>(); <fields>; }
|
|
8
7
|
|
|
9
|
-
#
|
|
8
|
+
# Native methods are bound from JNI_OnLoad by class/method name (the encoded
|
|
9
|
+
# table in cpp/android_jni/jni_registration.cpp), not by exported symbols, so
|
|
10
|
+
# their classes and names must still survive shrinking unchanged.
|
|
10
11
|
-keepclasseswithmembernames class cn.reactnative.modules.update.** {
|
|
11
12
|
native <methods>;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
# Instantiated by
|
|
15
|
+
# Instantiated or called by name: the module's methods by the bridge /
|
|
16
|
+
# TurboModule JNI (not every React Native version ships a consumer rule
|
|
17
|
+
# keeping NativeModule members), the receiver by the system, Expo's package
|
|
18
|
+
# by Expo.
|
|
19
|
+
# UpdatePackage needs no rule: hosts construct it directly.
|
|
15
20
|
-keep class cn.reactnative.modules.update.UpdateModule { *; }
|
|
16
|
-
-keep class cn.reactnative.modules.update.UpdatePackage { *; }
|
|
17
21
|
-keep class cn.reactnative.modules.update.PackageInstallerStatusReceiver { *; }
|
|
18
22
|
-keep class expo.modules.pushy.** { *; }
|
|
19
23
|
|
|
@@ -70,5 +74,3 @@
|
|
|
70
74
|
-keep class **.BuildConfig {
|
|
71
75
|
public static boolean IS_NEW_ARCHITECTURE_ENABLED;
|
|
72
76
|
}
|
|
73
|
-
|
|
74
|
-
-keepnames class expo.modules.ExpoReactHostFactory$ExpoReactHostDelegate { *; }
|
|
@@ -20,7 +20,6 @@ import java.io.OutputStream;
|
|
|
20
20
|
import java.util.concurrent.ConcurrentHashMap;
|
|
21
21
|
|
|
22
22
|
final class ApkInstaller {
|
|
23
|
-
private static final int COPY_BUFFER_SIZE = 64 * 1024;
|
|
24
23
|
private static final String SESSION_ID_EXTRA =
|
|
25
24
|
Texts.reveal("3919bac3ab8a6b512c3e08f0c0b6de60452311edfbc8f680624b2d1de38da5a58e65557f3ecaeb8ea3b859661f3dc3f98bab48646c042bd0e19ca546681c2939");
|
|
26
25
|
private static final ConcurrentHashMap<Integer, Promise> pendingPromises =
|
|
@@ -137,7 +136,7 @@ final class ApkInstaller {
|
|
|
137
136
|
// before staging it. The `hash` JS passes to downloadAndInstallApk is
|
|
138
137
|
// the progress-event key ("downloadingApk"), not a digest, so there
|
|
139
138
|
// is nothing to check against yet; until the server sends one, the
|
|
140
|
-
// https-only rule in
|
|
139
|
+
// https-only rule in UpdateModule is the integrity boundary.
|
|
141
140
|
PackageInstaller packageInstaller = null;
|
|
142
141
|
int sessionId = -1;
|
|
143
142
|
boolean committed = false;
|
|
@@ -177,11 +176,7 @@ final class ApkInstaller {
|
|
|
177
176
|
FileInputStream input = new FileInputStream(apkFile);
|
|
178
177
|
OutputStream output = session.openWrite("base.apk", 0, apkFile.length())
|
|
179
178
|
) {
|
|
180
|
-
|
|
181
|
-
int count;
|
|
182
|
-
while ((count = input.read(buffer)) != -1) {
|
|
183
|
-
output.write(buffer, 0, count);
|
|
184
|
-
}
|
|
179
|
+
UpdateFileUtils.copy(input, output);
|
|
185
180
|
session.fsync(output);
|
|
186
181
|
}
|
|
187
182
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
package cn.reactnative.modules.update;
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Damage bounds for a corrupt or hostile update package. The accounting
|
|
5
|
+
* itself (entry count, sizes, ratios, reserved names, the unknown-length
|
|
6
|
+
* probe plan) is the shared core's (cpp/patch_core/archive_limits.h, over
|
|
7
|
+
* JNI); the constants below are the ones this side still compares against
|
|
8
|
+
* itself, mirrored by hand (scripts/check-constant-parity.js asserts they
|
|
9
|
+
* match the header).
|
|
6
10
|
*/
|
|
7
11
|
final class ArchiveLimits {
|
|
8
12
|
static final long MAX_ARCHIVE_BYTES = 512L * 1024 * 1024;
|
|
9
13
|
static final long MAX_TOTAL_UNCOMPRESSED_BYTES = 2048L * 1024 * 1024;
|
|
10
14
|
static final long MAX_ENTRY_BYTES = 512L * 1024 * 1024;
|
|
11
|
-
static final long MAX_ENTRIES = 20000;
|
|
12
|
-
static final long MAX_COMPRESSION_RATIO = 100;
|
|
13
|
-
static final long RATIO_CHECK_MIN_BYTES = 1L * 1024 * 1024;
|
|
14
15
|
static final long MAX_MANIFEST_BYTES = 16L * 1024 * 1024;
|
|
15
16
|
static final long FREE_DISK_MARGIN_BYTES = 64L * 1024 * 1024;
|
|
16
|
-
static final long UNKNOWN_LENGTH_FREE_SPACE_PROBE_BYTES = 8L * 1024 * 1024;
|
|
17
17
|
|
|
18
18
|
private ArchiveLimits() {
|
|
19
19
|
}
|
|
@@ -42,4 +42,21 @@ final class ArchiveLimits {
|
|
|
42
42
|
"insufficient disk space: need " + needed + " bytes, have " + usable);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The central directory (one entry per index) through the shared
|
|
48
|
+
* EntryAccumulator. Returns the total uncompressed size; throws with the
|
|
49
|
+
* first violation.
|
|
50
|
+
*/
|
|
51
|
+
static native long checkEntries(
|
|
52
|
+
long archiveBytes, String[] names, long[] sizes, long[] compressedSizes)
|
|
53
|
+
throws java.io.IOException;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Before one chunk of a download is written: {bytes to probe for, 0 when
|
|
57
|
+
* no probe is due; the reservation end after the chunk}. writtenBefore
|
|
58
|
+
* counts this response's body bytes before the chunk.
|
|
59
|
+
*/
|
|
60
|
+
static native long[] planFreeSpaceProbe(
|
|
61
|
+
long knownTotal, long writtenBefore, long chunkBytes, long reservedUntil);
|
|
45
62
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
package cn.reactnative.modules.update;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
|
-
import android.content.pm.ApplicationInfo;
|
|
5
|
-
import android.content.pm.PackageManager;
|
|
6
4
|
import android.content.res.Resources;
|
|
7
5
|
import android.util.DisplayMetrics;
|
|
8
6
|
import android.util.Log;
|
|
@@ -32,9 +30,9 @@ final class BundledResourceCopier {
|
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
33
|
+
// An entry together with the exact archive it came from, so a copy reads
|
|
34
|
+
// from that archive even if another APK exposes the same entry name with
|
|
35
|
+
// different bytes.
|
|
38
36
|
private static final class ZipSource {
|
|
39
37
|
final ZipEntry entry;
|
|
40
38
|
final SafeZipFile zipFile;
|
|
@@ -49,14 +47,29 @@ final class BundledResourceCopier {
|
|
|
49
47
|
this.context = context.getApplicationContext();
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Performs the manifest copies of an APK-based patch (the host share of
|
|
52
|
+
* the native applyArchiveDelta): each group's targets, relative to
|
|
53
|
+
* targetRoot, receive its source entry. The native manifest parser has
|
|
54
|
+
* already validated every path and the copiesCrc rules.
|
|
55
|
+
*/
|
|
56
|
+
void copyFromResource(File targetRoot, CopyGroupResult[] groups) throws IOException {
|
|
57
|
+
HashMap<String, ArrayList<File>> resToCopy = new HashMap<String, ArrayList<File>>();
|
|
58
|
+
HashMap<String, Long> crcByFrom = new HashMap<String, Long>();
|
|
59
|
+
for (CopyGroupResult group : groups) {
|
|
60
|
+
ArrayList<File> targets = new ArrayList<File>();
|
|
61
|
+
for (String to : group.toPaths) {
|
|
62
|
+
targets.add(new File(targetRoot, to));
|
|
63
|
+
}
|
|
64
|
+
resToCopy.put(group.from, targets);
|
|
65
|
+
if (group.expectedCrc >= 0) {
|
|
66
|
+
crcByFrom.put(group.from, group.expectedCrc);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
56
69
|
ArrayList<String> apkPaths = collectApkPaths();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
HashMap<String,
|
|
70
|
+
ArrayList<SafeZipFile> zipFiles = new ArrayList<SafeZipFile>();
|
|
71
|
+
// Entry name -> first archive (base APK first) that carries it.
|
|
72
|
+
HashMap<String, ZipSource> availableEntries = new HashMap<String, ZipSource>();
|
|
60
73
|
// Content checksum index: CRC32 -> matched archive source. Lets us
|
|
61
74
|
// locate a file by content when its origin path is not present verbatim
|
|
62
75
|
// on device (e.g. APK baseline diff applied on an AAB/split-apk install
|
|
@@ -66,18 +79,18 @@ final class BundledResourceCopier {
|
|
|
66
79
|
try {
|
|
67
80
|
for (String apkPath : apkPaths) {
|
|
68
81
|
SafeZipFile zipFile = new SafeZipFile(new File(apkPath));
|
|
69
|
-
|
|
82
|
+
zipFiles.add(zipFile);
|
|
70
83
|
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
|
71
84
|
while (entries.hasMoreElements()) {
|
|
72
85
|
ZipEntry ze = entries.nextElement();
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
ZipSource source = new ZipSource(ze, zipFile);
|
|
87
|
+
// No Map.putIfAbsent: it is API 24+ and minSdk is 21.
|
|
88
|
+
if (!availableEntries.containsKey(ze.getName())) {
|
|
89
|
+
availableEntries.put(ze.getName(), source);
|
|
77
90
|
}
|
|
78
91
|
long crc = ze.getCrc();
|
|
79
92
|
if (crc != -1L && !crcToEntry.containsKey(crc)) {
|
|
80
|
-
crcToEntry.put(crc,
|
|
93
|
+
crcToEntry.put(crc, source);
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
}
|
|
@@ -85,10 +98,11 @@ final class BundledResourceCopier {
|
|
|
85
98
|
HashMap<String, String> normalizedEntryMap = new HashMap<String, String>();
|
|
86
99
|
for (String entryName : availableEntries.keySet()) {
|
|
87
100
|
String normalized = normalizeResPath(entryName);
|
|
88
|
-
normalizedEntryMap.
|
|
101
|
+
if (!normalizedEntryMap.containsKey(normalized)) {
|
|
102
|
+
normalizedEntryMap.put(normalized, entryName);
|
|
103
|
+
}
|
|
89
104
|
}
|
|
90
105
|
|
|
91
|
-
SafeZipFile baseZipFile = zipFileMap.get(context.getPackageResourcePath());
|
|
92
106
|
HashMap<String, ArrayList<File>> remainingFiles =
|
|
93
107
|
new HashMap<String, ArrayList<File>>(resToCopy);
|
|
94
108
|
// Track copies that were located but failed to write. These are real
|
|
@@ -109,11 +123,10 @@ final class BundledResourceCopier {
|
|
|
109
123
|
continue;
|
|
110
124
|
}
|
|
111
125
|
|
|
112
|
-
|
|
126
|
+
ZipSource entry = availableEntries.get(fromPath);
|
|
113
127
|
String actualSourcePath = fromPath;
|
|
114
|
-
SafeZipFile matchedZipFile = null;
|
|
115
128
|
ResolvedResourceSource resolvedResource = null;
|
|
116
|
-
Long wantedCrc = crcByFrom
|
|
129
|
+
Long wantedCrc = crcByFrom.get(fromPath);
|
|
117
130
|
|
|
118
131
|
if (entry == null) {
|
|
119
132
|
String normalizedFrom = normalizeResPath(fromPath);
|
|
@@ -130,7 +143,8 @@ final class BundledResourceCopier {
|
|
|
130
143
|
// locate the expected content elsewhere. An entry with an
|
|
131
144
|
// unknown CRC (-1) cannot be verified and is accepted as-is.
|
|
132
145
|
if (entry != null && wantedCrc != null
|
|
133
|
-
&& entry.getCrc() != -1L
|
|
146
|
+
&& entry.entry.getCrc() != -1L
|
|
147
|
+
&& entry.entry.getCrc() != wantedCrc.longValue()) {
|
|
134
148
|
entry = null;
|
|
135
149
|
actualSourcePath = fromPath;
|
|
136
150
|
}
|
|
@@ -141,8 +155,7 @@ final class BundledResourceCopier {
|
|
|
141
155
|
if (entry == null && wantedCrc != null) {
|
|
142
156
|
ZipSource matched = crcToEntry.get(wantedCrc);
|
|
143
157
|
if (matched != null) {
|
|
144
|
-
entry = matched
|
|
145
|
-
matchedZipFile = matched.zipFile;
|
|
158
|
+
entry = matched;
|
|
146
159
|
actualSourcePath = matched.entry.getName();
|
|
147
160
|
}
|
|
148
161
|
}
|
|
@@ -166,7 +179,7 @@ final class BundledResourceCopier {
|
|
|
166
179
|
// archives so the right variant is used. (openRawResource
|
|
167
180
|
// would re-resolve the id at the current configuration
|
|
168
181
|
// density and ignore the requested one.)
|
|
169
|
-
|
|
182
|
+
ZipSource resolvedEntry = availableEntries.get(actualSourcePath);
|
|
170
183
|
if (resolvedEntry != null) {
|
|
171
184
|
entry = resolvedEntry;
|
|
172
185
|
resolvedResource = null;
|
|
@@ -184,13 +197,7 @@ final class BundledResourceCopier {
|
|
|
184
197
|
if (lastTarget != null) {
|
|
185
198
|
UpdateFileUtils.copyFile(lastTarget, target);
|
|
186
199
|
} else if (entry != null) {
|
|
187
|
-
|
|
188
|
-
? matchedZipFile
|
|
189
|
-
: entryToZipFileMap.get(actualSourcePath);
|
|
190
|
-
if (sourceZipFile == null) {
|
|
191
|
-
sourceZipFile = baseZipFile;
|
|
192
|
-
}
|
|
193
|
-
sourceZipFile.unzipToFile(entry, target);
|
|
200
|
+
entry.zipFile.unzipToFile(entry.entry, target);
|
|
194
201
|
} else {
|
|
195
202
|
InputStream in = openResolvedResourceStream(resolvedResource);
|
|
196
203
|
UpdateFileUtils.copyInputStreamToFile(in, target);
|
|
@@ -235,7 +242,7 @@ final class BundledResourceCopier {
|
|
|
235
242
|
);
|
|
236
243
|
}
|
|
237
244
|
} finally {
|
|
238
|
-
closeZipFiles(
|
|
245
|
+
closeZipFiles(zipFiles);
|
|
239
246
|
}
|
|
240
247
|
}
|
|
241
248
|
|
|
@@ -354,33 +361,25 @@ final class BundledResourceCopier {
|
|
|
354
361
|
}
|
|
355
362
|
}
|
|
356
363
|
|
|
364
|
+
// The base APK first, then the split APKs of an AAB install.
|
|
357
365
|
private ArrayList<String> collectApkPaths() {
|
|
358
366
|
ArrayList<String> apkPaths = new ArrayList<String>();
|
|
359
367
|
apkPaths.add(context.getPackageResourcePath());
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (appInfo.splitSourceDirs != null) {
|
|
365
|
-
for (String splitPath : appInfo.splitSourceDirs) {
|
|
366
|
-
apkPaths.add(splitPath);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
} catch (PackageManager.NameNotFoundException e) {
|
|
370
|
-
if (UpdateContext.DEBUG) {
|
|
371
|
-
Log.w(Texts.LOG_TAG, "Failed to get application info: " + e.getMessage());
|
|
368
|
+
String[] splitSourceDirs = context.getApplicationInfo().splitSourceDirs;
|
|
369
|
+
if (splitSourceDirs != null) {
|
|
370
|
+
for (String splitPath : splitSourceDirs) {
|
|
371
|
+
apkPaths.add(splitPath);
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
|
-
|
|
375
374
|
return apkPaths;
|
|
376
375
|
}
|
|
377
376
|
|
|
378
|
-
private void closeZipFiles(
|
|
379
|
-
for (SafeZipFile zipFile :
|
|
377
|
+
private void closeZipFiles(ArrayList<SafeZipFile> zipFiles) {
|
|
378
|
+
for (SafeZipFile zipFile : zipFiles) {
|
|
380
379
|
try {
|
|
381
380
|
zipFile.close();
|
|
382
381
|
} catch (IOException e) {
|
|
383
|
-
if (
|
|
382
|
+
if (BuildConfig.DEBUG) {
|
|
384
383
|
Log.w(Texts.LOG_TAG, "Failed to close zip file", e);
|
|
385
384
|
}
|
|
386
385
|
}
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
package cn.reactnative.modules.update;
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import java.util.LinkedHashSet;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The distinct version names cleanup must never touch: the persisted current
|
|
7
|
+
* and last versions AND the version this process booted from (two switches
|
|
8
|
+
* without a restart otherwise evict the running bundle while its on-demand
|
|
9
|
+
* assets are still being served).
|
|
10
|
+
*/
|
|
4
11
|
final class CleanupKeepNames {
|
|
5
12
|
private CleanupKeepNames() {
|
|
6
13
|
}
|
|
7
14
|
|
|
8
15
|
static String[] select(String current, String previous, String running) {
|
|
9
|
-
String
|
|
16
|
+
LinkedHashSet<String> keep = new LinkedHashSet<>();
|
|
10
17
|
for (String candidate : new String[] {current, previous, running}) {
|
|
11
|
-
if (candidate
|
|
12
|
-
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
if (keep[0] == null) {
|
|
16
|
-
keep[0] = candidate;
|
|
17
|
-
} else if (keep[1] == null) {
|
|
18
|
-
keep[1] = candidate;
|
|
19
|
-
} else {
|
|
20
|
-
return null;
|
|
18
|
+
if (candidate != null && !candidate.isEmpty()) {
|
|
19
|
+
keep.add(candidate);
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
|
-
return keep;
|
|
22
|
+
return keep.toArray(new String[0]);
|
|
24
23
|
}
|
|
25
24
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
package cn.reactnative.modules.update;
|
|
2
2
|
|
|
3
|
+
/** Built by the native applyArchiveDelta: one copy source and its targets. */
|
|
3
4
|
class CopyGroupResult {
|
|
4
5
|
String from;
|
|
5
6
|
String[] toPaths;
|
|
7
|
+
// CRC32 the source content must match (__diff.json copiesCrc), or -1.
|
|
8
|
+
long expectedCrc = -1;
|
|
6
9
|
}
|