react-native-update 10.55.1 → 10.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-CN.md +35 -0
- package/README.md +37 -0
- package/android/build.gradle +53 -27
- package/android/jni/Application.mk +6 -1
- package/android/jni/hpatch.c +59 -4
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/proguard.pro +70 -14
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +15 -8
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +1 -0
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +1 -2
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +25 -0
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -34
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +29 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +15 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +59 -0
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +27 -1
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +57 -16
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +38 -46
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +279 -183
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +14 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +222 -215
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +5 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +23 -25
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +29 -29
- package/cpp/patch_core/archive_limits.h +6 -0
- package/cpp/patch_core/jni_util.h +6 -2
- package/cpp/patch_core/patch_core.cpp +237 -84
- package/cpp/patch_core/patch_core.h +11 -0
- package/cpp/patch_core/patch_core_android.cpp +49 -33
- package/cpp/patch_core/update_core_android.cpp +134 -103
- package/cpp/update_flow_core/flow_json.cpp +150 -17
- package/cpp/update_flow_core/flow_json.h +13 -1
- package/cpp/update_flow_core/update_flow_core.cpp +78 -8
- package/cpp/update_flow_core/update_flow_core.h +9 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +44 -25
- package/harmony/hvigor-plugin.ts +168 -21
- package/harmony/pushy/src/README.md +74 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +13 -3
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +11 -5
- package/harmony/pushy/src/main/cpp/pushy.cpp +103 -18
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +61 -5
- package/harmony/pushy/src/main/ets/DownloadTask.ts +255 -166
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +75 -0
- package/harmony/pushy/src/main/ets/EventHub.ts +22 -20
- package/harmony/pushy/src/main/ets/InstallRecord.ts +79 -21
- package/harmony/pushy/src/main/ets/Logger.ts +42 -16
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +98 -17
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +10 -3
- package/harmony/pushy/src/main/ets/PathUtils.ts +9 -3
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +139 -53
- package/harmony/pushy/src/main/ets/UpdateContext.ts +269 -159
- package/harmony/pushy/src/main/module.json5 +5 -1
- package/harmony/pushy.har +0 -0
- package/ios/PrivacyInfo.xcprivacy +79 -0
- package/ios/RCTPushy/RCTPushy.mm +399 -170
- package/ios/RCTPushy/RCTPushyDownloader.h +14 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +136 -11
- package/package.json +14 -24
- package/react-native-update.podspec +43 -3
- package/src/client.ts +236 -82
- package/src/context.ts +40 -27
- package/src/core.ts +41 -16
- package/src/endpoint.ts +2 -1
- package/src/error.ts +49 -11
- package/src/index.ts +14 -0
- package/src/locales/en.ts +1 -23
- package/src/locales/zh.ts +1 -22
- package/src/provider.tsx +159 -111
- package/src/type.ts +0 -2
- package/src/updateFlowCore.ts +10 -2
- package/src/utils.ts +61 -4
- package/android/src/main/java/cn/reactnative/modules/update/ReactNativeHostHandler.java +0 -13
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +0 -45
package/README-CN.md
CHANGED
|
@@ -16,6 +16,41 @@
|
|
|
16
16
|
|
|
17
17
|
请查看[文档](https://pushy.reactnative.cn/docs/getting-started.html)
|
|
18
18
|
|
|
19
|
+
### 配置项速查
|
|
20
|
+
|
|
21
|
+
下表中的每一项都声明在包根导出的 `ClientOptions` 上,完整说明见
|
|
22
|
+
<https://pushy.reactnative.cn/docs/api>。运行时修改统一走 `client.setOptions(...)`。
|
|
23
|
+
|
|
24
|
+
| 配置项 | 默认值 | 作用 |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `appKey` | 必填 | 应用在热更新平台上的身份 |
|
|
27
|
+
| `server` | Pushy/Cresc 预设 | `main` 检查端点与 `queryUrls` 远程端点发现(仅接受 https) |
|
|
28
|
+
| `updateStrategy` | `alertUpdateAndIgnoreError`(开发环境 `alwaysAlert`) | `alwaysAlert` / `alertUpdateAndIgnoreError` / `silentAndNow` / `silentAndLater` / `null` |
|
|
29
|
+
| `checkStrategy` | `both` | `onAppStart` / `onAppResume` / `both` / `null`(不自动检查;原生冷启动检测仍会下载但不会自行激活) |
|
|
30
|
+
| `autoMarkSuccess` | `true` | 由 Provider 自动标记当前热更版本健康 |
|
|
31
|
+
| `autoMarkSuccessDelayMs` | `1000` | 自动标记前的延迟;关键模块加载较晚时应调大 |
|
|
32
|
+
| `healthCheck` | – | 自动标记计时器触发时调用,返回 `false` 则保持崩溃保护 |
|
|
33
|
+
| `beforeCheckUpdate` / `afterCheckUpdate` | – | 否决一次检查 / 观察检查的最终状态 |
|
|
34
|
+
| `beforeDownloadUpdate` / `afterDownloadUpdate` | – | 否决下载 / 否决下载后的激活 |
|
|
35
|
+
| `beforeReload` | – | 否决 `switchVersion` / `restartApp` 的重载 |
|
|
36
|
+
| `onPackageExpired` | – | 否决"原生包过旧"流程(`downloadUrl`) |
|
|
37
|
+
| `maxRetries` | `3` | 下载重试次数(带抖动退避) |
|
|
38
|
+
| `overridePackageVersion` | – | 向服务端上报另一个原生包版本 |
|
|
39
|
+
| `dismissErrorAfter` | – | N 毫秒后自动清除 `lastError` |
|
|
40
|
+
| `locale` | `zh`(Pushy)/ `en`(Cresc) | 弹窗与提示语言 |
|
|
41
|
+
| `debug` | `false` | 允许在开发构建中执行更新操作,并开启 SDK 详细日志 |
|
|
42
|
+
| `throwError` | `false` | 把流程错误抛给调用方(错误总会到达 `onError`) |
|
|
43
|
+
| `disableTelemetry` | `false` | 不向服务端上报生命周期事件(下载/补丁失败、回滚、标记成功) |
|
|
44
|
+
| `disableErrorReporting` | `false` | 关闭 JS 错误上报(见下节) |
|
|
45
|
+
| `disableNativeCheck` | `false` | 关闭原生冷启动检测(救砖通道) |
|
|
46
|
+
| `testChannel` | `true` | 是否响应 `__rnPushyVersionHash` 深链/二维码;生产构建建议设为 `false` |
|
|
47
|
+
| `logger` | – | 接收 SDK 全部事件(`type`、`data`)用于自定义日志 |
|
|
48
|
+
|
|
49
|
+
除 Provider 钩子外,client 还提供 `onError(listener)`、
|
|
50
|
+
`captureException(error, context)`、`resetToPackagedBundle({ restart })`、
|
|
51
|
+
面向崩溃上报的 `getUpdateMetadata()` / `attachToSentry()` / `attachToCrashlytics()`,
|
|
52
|
+
以及供进度条单独订阅、避免整棵树重渲染的 `useUpdateProgress()`。
|
|
53
|
+
|
|
19
54
|
### JS 错误上报
|
|
20
55
|
|
|
21
56
|
SDK 可以把未捕获错误和手动捕获的 JavaScript 错误关联到当前实际运行的热更版本。
|
package/README.md
CHANGED
|
@@ -14,6 +14,43 @@ See the docs:
|
|
|
14
14
|
|
|
15
15
|
- English docs: <https://cresc.dev/docs/getting-started>
|
|
16
16
|
|
|
17
|
+
## Options reference
|
|
18
|
+
|
|
19
|
+
Every option below is declared on `ClientOptions` (exported from the package
|
|
20
|
+
root) and documented in full at <https://cresc.dev/docs/api>. Runtime changes go
|
|
21
|
+
through `client.setOptions(...)`.
|
|
22
|
+
|
|
23
|
+
| Option | Default | What it controls |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `appKey` | required | App identity on the update platform |
|
|
26
|
+
| `server` | Pushy/Cresc preset | `main` endpoints and `queryUrls` for remote endpoint discovery (https only) |
|
|
27
|
+
| `updateStrategy` | `alertUpdateAndIgnoreError` (`alwaysAlert` in dev) | `alwaysAlert` / `alertUpdateAndIgnoreError` / `silentAndNow` / `silentAndLater` / `null` |
|
|
28
|
+
| `checkStrategy` | `both` | `onAppStart` / `onAppResume` / `both` / `null` (no automatic check; the native cold-start check still downloads but never activates) |
|
|
29
|
+
| `autoMarkSuccess` | `true` | Mark the running update healthy from the Provider |
|
|
30
|
+
| `autoMarkSuccessDelayMs` | `1000` | Delay before the automatic mark; raise it when critical modules load late |
|
|
31
|
+
| `healthCheck` | – | Consulted when the auto-mark timer fires; return `false` to keep crash protection armed |
|
|
32
|
+
| `beforeCheckUpdate` / `afterCheckUpdate` | – | Veto a check / observe its final state |
|
|
33
|
+
| `beforeDownloadUpdate` / `afterDownloadUpdate` | – | Veto a download / veto the activation after it |
|
|
34
|
+
| `beforeReload` | – | Veto a `switchVersion` / `restartApp` reload |
|
|
35
|
+
| `onPackageExpired` | – | Veto the "binary too old" flow (`downloadUrl`) |
|
|
36
|
+
| `maxRetries` | `3` | Download retries with jittered backoff |
|
|
37
|
+
| `overridePackageVersion` | – | Report a different binary version to the server |
|
|
38
|
+
| `dismissErrorAfter` | – | Auto-clear `lastError` after N ms |
|
|
39
|
+
| `locale` | `zh` (Pushy) / `en` (Cresc) | Language of alerts and messages |
|
|
40
|
+
| `debug` | `false` | Enable update operations in development builds and verbose SDK logging |
|
|
41
|
+
| `throwError` | `false` | Rethrow pipeline errors to the caller (they always reach `onError`) |
|
|
42
|
+
| `disableTelemetry` | `false` | Stop lifecycle events (download/patch failure, rollback, mark success) reaching the server |
|
|
43
|
+
| `disableErrorReporting` | `false` | Stop JS exception reporting (see below) |
|
|
44
|
+
| `disableNativeCheck` | `false` | Turn off the native cold-start check that rescues bricked devices |
|
|
45
|
+
| `testChannel` | `true` | Honour `__rnPushyVersionHash` deep links / QR codes; set `false` in production builds |
|
|
46
|
+
| `logger` | – | Receives every SDK event (`type`, `data`) for your own logging |
|
|
47
|
+
|
|
48
|
+
Beyond the Provider hooks, the client exposes `onError(listener)`,
|
|
49
|
+
`captureException(error, context)`, `resetToPackagedBundle({ restart })`,
|
|
50
|
+
`getUpdateMetadata()` / `attachToSentry()` / `attachToCrashlytics()` for crash
|
|
51
|
+
reporters, and `useUpdateProgress()` for progress bars that must not re-render
|
|
52
|
+
the whole tree.
|
|
53
|
+
|
|
17
54
|
## JS error reporting
|
|
18
55
|
|
|
19
56
|
The SDK reports uncaught and manually captured JavaScript errors against the
|
package/android/build.gradle
CHANGED
|
@@ -57,6 +57,26 @@ def resolveInstalledExpoMajorVersion(File projectDir) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Reads node_modules/expo/package.json the way Node would resolve it (walking
|
|
61
|
+
// up from the app directory, so hoisted monorepo installs work) — a file read
|
|
62
|
+
// instead of a `node` process at configuration time.
|
|
63
|
+
def readInstalledExpoMajorVersion(File startDir) {
|
|
64
|
+
File dir = startDir
|
|
65
|
+
while (dir != null) {
|
|
66
|
+
File expoPackageJson = new File(dir, 'node_modules/expo/package.json')
|
|
67
|
+
if (expoPackageJson.isFile()) {
|
|
68
|
+
try {
|
|
69
|
+
return parseMajorVersion(
|
|
70
|
+
new groovy.json.JsonSlurper().parseText(expoPackageJson.text).version)
|
|
71
|
+
} catch (Exception ignored) {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
dir = dir.parentFile
|
|
76
|
+
}
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
60
80
|
def checkProjectInfo() {
|
|
61
81
|
def hasExpoModulesCore = rootProject.subprojects.any { it.name == 'expo-modules-core' }
|
|
62
82
|
def packageJsonFile = new File(rootProject.projectDir.parentFile, 'package.json')
|
|
@@ -68,20 +88,21 @@ def checkProjectInfo() {
|
|
|
68
88
|
def packageJson = new groovy.json.JsonSlurper().parseText(packageJsonFile.text)
|
|
69
89
|
projectVersion = packageJson.version ?: '1.0.0' // Get project version
|
|
70
90
|
|
|
71
|
-
// Check for expo dependency and version >= 50.
|
|
72
|
-
//
|
|
73
|
-
//
|
|
91
|
+
// Check for expo dependency and version >= 50. Only resolved when
|
|
92
|
+
// expo-modules-core is part of the build and expo is declared; every
|
|
93
|
+
// other project pays nothing beyond this package.json read.
|
|
74
94
|
String expoVersionString = packageJson.dependencies?.expo ?: packageJson.devDependencies?.expo
|
|
75
|
-
Integer
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
Integer expoMajorVersion = null
|
|
96
|
+
if (hasExpoModulesCore && expoVersionString) {
|
|
97
|
+
// For pnpm catalogs (e.g. "catalog:native") the declared version is
|
|
98
|
+
// not numeric: prefer the installed package, then the declaration,
|
|
99
|
+
// and spawn `node` only as the last resort.
|
|
100
|
+
expoMajorVersion = readInstalledExpoMajorVersion(packageJsonFile.parentFile)
|
|
101
|
+
?: parseMajorVersion(expoVersionString)
|
|
102
|
+
?: resolveInstalledExpoMajorVersion(packageJsonFile.parentFile)
|
|
103
|
+
}
|
|
83
104
|
|
|
84
|
-
hasExpoDependency =
|
|
105
|
+
hasExpoDependency = expoMajorVersion != null && expoMajorVersion >= 50
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
def isExpo = hasExpoModulesCore && hasExpoDependency
|
|
@@ -125,13 +146,11 @@ android {
|
|
|
125
146
|
}
|
|
126
147
|
}
|
|
127
148
|
}
|
|
128
|
-
compileSdkVersion safeExtGet('compileSdkVersion',
|
|
129
|
-
buildToolsVersion safeExtGet('buildToolsVersion', '
|
|
149
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 34)
|
|
150
|
+
buildToolsVersion safeExtGet('buildToolsVersion', '34.0.0')
|
|
130
151
|
defaultConfig {
|
|
131
152
|
minSdkVersion safeExtGet('minSdkVersion', 21)
|
|
132
|
-
targetSdkVersion safeExtGet('targetSdkVersion',
|
|
133
|
-
versionCode 1
|
|
134
|
-
versionName "1.81.4"
|
|
153
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 34)
|
|
135
154
|
consumerProguardFiles "proguard.pro"
|
|
136
155
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
137
156
|
}
|
|
@@ -146,9 +165,9 @@ android {
|
|
|
146
165
|
java.srcDirs += ['src/oldarch']
|
|
147
166
|
}
|
|
148
167
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
168
|
+
// The Expo module lives in the default src/main/java tree and is
|
|
169
|
+
// only compiled when the host is an Expo (SDK 50+) project.
|
|
170
|
+
if (!expoProject) {
|
|
152
171
|
java.exclude 'expo/modules/pushy/**'
|
|
153
172
|
}
|
|
154
173
|
}
|
|
@@ -172,20 +191,27 @@ android {
|
|
|
172
191
|
resValue("string", "pushy_build_time", "0")
|
|
173
192
|
}
|
|
174
193
|
}
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
if (agpMajor >= 7) {
|
|
195
|
+
lint {
|
|
196
|
+
abortOnError false
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
lintOptions {
|
|
200
|
+
abortOnError false
|
|
201
|
+
}
|
|
177
202
|
}
|
|
178
|
-
}
|
|
179
203
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
|
|
204
|
+
testOptions {
|
|
205
|
+
// Pure-JVM unit tests (android/src/test): android.* calls return
|
|
206
|
+
// defaults instead of throwing, org.json comes from the real library.
|
|
207
|
+
unitTests.returnDefaultValues = true
|
|
184
208
|
}
|
|
185
209
|
}
|
|
186
210
|
|
|
187
211
|
dependencies {
|
|
188
212
|
implementation 'com.facebook.react:react-native:+'
|
|
213
|
+
testImplementation 'junit:junit:4.13.2'
|
|
214
|
+
testImplementation 'org.json:json:20240303'
|
|
189
215
|
}
|
|
190
216
|
if (isNewArchitectureEnabled()) {
|
|
191
217
|
react {
|
|
@@ -3,7 +3,12 @@ APP_CFLAGS += -Wno-error=format-security
|
|
|
3
3
|
APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
|
|
4
4
|
APP_CFLAGS += -ffunction-sections -fdata-sections
|
|
5
5
|
APP_CFLAGS += -Oz -fno-unwind-tables -fno-asynchronous-unwind-tables
|
|
6
|
-
|
|
6
|
+
# -fexceptions (ndk-build defaults to -fno-exceptions): the cpp/ cores allocate
|
|
7
|
+
# proportionally to untrusted input, and without exceptions a bad_alloc /
|
|
8
|
+
# length_error would std::terminate the process instead of surfacing as a Java
|
|
9
|
+
# exception (every JNI entry point catches and rethrows). Unwind tables are
|
|
10
|
+
# still emitted for throwing frames.
|
|
11
|
+
APP_CPPFLAGS += -std=c++17 -Oz -fexceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables
|
|
7
12
|
APP_LDFLAGS += -Wl,--gc-sections -Wl,--exclude-libs,ALL
|
|
8
13
|
APP_LDFLAGS += -Wl,--icf=all
|
|
9
14
|
APP_BUILD_SCRIPT := Android.mk
|
package/android/jni/hpatch.c
CHANGED
|
@@ -15,9 +15,64 @@
|
|
|
15
15
|
|
|
16
16
|
#define kMaxLoadMemOldSize ((1<<20)*8)
|
|
17
17
|
|
|
18
|
+
// LZMA/LZMA2 declare their dictionary size inside the compressed stream, i.e.
|
|
19
|
+
// in attacker-controlled patch bytes, and LzmaDec_Allocate() allocates it
|
|
20
|
+
// verbatim — up to 4 GB. The plugins live in the HDiffPatch submodule, so the
|
|
21
|
+
// cap is applied here by wrapping open(): decode the declared dictionary the
|
|
22
|
+
// way the decoder does and refuse anything above kMaxLzmaDictSize before any
|
|
23
|
+
// allocation happens. hdiffz defaults to an 8 MB dictionary and reduces it to
|
|
24
|
+
// the input size (compress_plugin_demo.h reduceSize), so real patches sit far
|
|
25
|
+
// below the cap; a refused stream fails the patch cleanly (kHPatch_error_patch)
|
|
26
|
+
// and the caller falls back to the full package.
|
|
27
|
+
#define kMaxLzmaDictSize ((hpatch_uint32_t)((1<<20)*128))
|
|
28
|
+
|
|
18
29
|
#define _check(v,errorType) do{ \
|
|
19
30
|
if (!(v)){ if (result==kHPatch_ok) result=errorType; if (!_isInClear){ goto _clear; }; } }while(0)
|
|
20
31
|
|
|
32
|
+
#ifdef _CompressPlugin_lzma
|
|
33
|
+
static hpatch_decompressHandle _capped_lzma_open(hpatch_TDecompress* decompressPlugin,
|
|
34
|
+
hpatch_StreamPos_t dataSize,
|
|
35
|
+
const hpatch_TStreamInput* codeStream,
|
|
36
|
+
hpatch_StreamPos_t code_begin,
|
|
37
|
+
hpatch_StreamPos_t code_end){
|
|
38
|
+
// stream layout (see _lzma_open): propsSize byte, then propsSize bytes of
|
|
39
|
+
// LZMA props whose bytes 1..4 hold the little-endian dictionary size.
|
|
40
|
+
unsigned char propsSize=0;
|
|
41
|
+
unsigned char props[LZMA_PROPS_SIZE];
|
|
42
|
+
hpatch_uint32_t dicSize;
|
|
43
|
+
if (code_end-code_begin<1) return 0;
|
|
44
|
+
if (!codeStream->read(codeStream,code_begin,&propsSize,&propsSize+1)) return 0;
|
|
45
|
+
if ((propsSize<LZMA_PROPS_SIZE)||(propsSize>code_end-code_begin-1)) return 0;
|
|
46
|
+
if (!codeStream->read(codeStream,code_begin+1,props,props+LZMA_PROPS_SIZE)) return 0;
|
|
47
|
+
dicSize=((hpatch_uint32_t)props[1])|(((hpatch_uint32_t)props[2])<<8)
|
|
48
|
+
|(((hpatch_uint32_t)props[3])<<16)|(((hpatch_uint32_t)props[4])<<24);
|
|
49
|
+
if (dicSize>kMaxLzmaDictSize) return 0;
|
|
50
|
+
return lzmaDecompressPlugin.open(decompressPlugin,dataSize,codeStream,code_begin,code_end);
|
|
51
|
+
}
|
|
52
|
+
static hpatch_TDecompress cappedLzmaDecompressPlugin={_lzma_is_can_open,_capped_lzma_open,
|
|
53
|
+
_lzma_close,_lzma_decompress_part};
|
|
54
|
+
#endif
|
|
55
|
+
#ifdef _CompressPlugin_lzma2
|
|
56
|
+
static hpatch_decompressHandle _capped_lzma2_open(hpatch_TDecompress* decompressPlugin,
|
|
57
|
+
hpatch_StreamPos_t dataSize,
|
|
58
|
+
const hpatch_TStreamInput* codeStream,
|
|
59
|
+
hpatch_StreamPos_t code_begin,
|
|
60
|
+
hpatch_StreamPos_t code_end){
|
|
61
|
+
// stream layout (see _lzma2_open): a single LZMA2 property byte encodes
|
|
62
|
+
// the dictionary size (Lzma2Dec_GetOldProps): 40 means 4 GB - 1.
|
|
63
|
+
unsigned char prop=0;
|
|
64
|
+
hpatch_uint32_t dicSize;
|
|
65
|
+
if (code_end-code_begin<1) return 0;
|
|
66
|
+
if (!codeStream->read(codeStream,code_begin,&prop,&prop+1)) return 0;
|
|
67
|
+
if (prop>40) return 0;
|
|
68
|
+
dicSize=(prop==40)?0xFFFFFFFF:(((hpatch_uint32_t)2|(prop&1))<<(prop/2+11));
|
|
69
|
+
if (dicSize>kMaxLzmaDictSize) return 0;
|
|
70
|
+
return lzma2DecompressPlugin.open(decompressPlugin,dataSize,codeStream,code_begin,code_end);
|
|
71
|
+
}
|
|
72
|
+
static hpatch_TDecompress cappedLzma2DecompressPlugin={_lzma2_is_can_open,_capped_lzma2_open,
|
|
73
|
+
_lzma2_close,_lzma2_decompress_part};
|
|
74
|
+
#endif
|
|
75
|
+
|
|
21
76
|
static hpatch_TDecompress* getDecompressPlugin(const char* compressType){
|
|
22
77
|
#ifdef _CompressPlugin_zlib
|
|
23
78
|
if (zlibDecompressPlugin.is_can_open(compressType))
|
|
@@ -28,12 +83,12 @@ static hpatch_TDecompress* getDecompressPlugin(const char* compressType){
|
|
|
28
83
|
return &bz2DecompressPlugin;
|
|
29
84
|
#endif
|
|
30
85
|
#ifdef _CompressPlugin_lzma
|
|
31
|
-
if (
|
|
32
|
-
return &
|
|
86
|
+
if (cappedLzmaDecompressPlugin.is_can_open(compressType))
|
|
87
|
+
return &cappedLzmaDecompressPlugin;
|
|
33
88
|
#endif
|
|
34
89
|
#ifdef _CompressPlugin_lzma2
|
|
35
|
-
if (
|
|
36
|
-
return &
|
|
90
|
+
if (cappedLzma2DecompressPlugin.is_can_open(compressType))
|
|
91
|
+
return &cappedLzma2DecompressPlugin;
|
|
37
92
|
#endif
|
|
38
93
|
return 0;
|
|
39
94
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/android/proguard.pro
CHANGED
|
@@ -1,18 +1,74 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
-
|
|
4
|
-
-keepnames
|
|
1
|
+
# JNI-constructed result classes: cpp/patch_core/update_core_android.cpp
|
|
2
|
+
# resolves them by FindClass + GetMethodID("<init>") + GetFieldID, so the
|
|
3
|
+
# no-arg constructor and every field must survive shrinking with their names
|
|
4
|
+
# (a -keepnames rule alone lets R8 strip the unreferenced constructor/fields).
|
|
5
|
+
-keep class cn.reactnative.modules.update.StateCoreResult { <init>(); <fields>; }
|
|
6
|
+
-keep class cn.reactnative.modules.update.ArchivePatchPlanResult { <init>(); <fields>; }
|
|
7
|
+
-keep class cn.reactnative.modules.update.CopyGroupResult { <init>(); <fields>; }
|
|
5
8
|
|
|
6
|
-
#
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-keepnames class com.facebook.react.devsupport.** { *; }
|
|
9
|
+
# JNI entry points are bound by their mangled Java names.
|
|
10
|
+
-keepclasseswithmembernames class cn.reactnative.modules.update.** {
|
|
11
|
+
native <methods>;
|
|
12
|
+
}
|
|
11
13
|
|
|
12
|
-
#
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
14
|
+
# Instantiated by React Native / Expo / the system by name.
|
|
15
|
+
-keep class cn.reactnative.modules.update.UpdateModule { *; }
|
|
16
|
+
-keep class cn.reactnative.modules.update.UpdatePackage { *; }
|
|
17
|
+
-keep class cn.reactnative.modules.update.PackageInstallerStatusReceiver { *; }
|
|
18
|
+
-keep class expo.modules.pushy.** { *; }
|
|
19
|
+
|
|
20
|
+
# Reflection targets of ReactReloadManager / UpdateEventEmitter. Only the
|
|
21
|
+
# members looked up by name are pinned; React Native's own consumer rules
|
|
22
|
+
# decide what else of com.facebook.react.** survives.
|
|
23
|
+
-keepclassmembers class com.facebook.react.ReactInstanceManager {
|
|
24
|
+
*** mBundleLoader;
|
|
25
|
+
*** bundleLoader;
|
|
26
|
+
*** mJSBundleFile;
|
|
27
|
+
*** jsBundleFile;
|
|
28
|
+
}
|
|
29
|
+
-keepclassmembers class com.facebook.react.ReactActivity {
|
|
30
|
+
*** getReactDelegate();
|
|
31
|
+
}
|
|
32
|
+
-keepclassmembers class com.facebook.react.ReactDelegate {
|
|
33
|
+
*** mReactHost;
|
|
34
|
+
*** reactHost;
|
|
35
|
+
}
|
|
36
|
+
-keepclassmembers class com.facebook.react.ReactNativeHost {
|
|
37
|
+
*** getBundleAssetName();
|
|
38
|
+
*** isNewArchEnabled();
|
|
39
|
+
}
|
|
40
|
+
-keepclassmembers class * extends com.facebook.react.ReactNativeHost {
|
|
41
|
+
*** getBundleAssetName();
|
|
42
|
+
*** isNewArchEnabled();
|
|
43
|
+
}
|
|
44
|
+
-keepclassmembers class com.facebook.react.runtime.ReactHostImpl {
|
|
45
|
+
*** mUseDevSupport;
|
|
46
|
+
*** useDevSupport;
|
|
47
|
+
*** mReactHostDelegate;
|
|
48
|
+
*** reactHostDelegate;
|
|
49
|
+
*** reload(java.lang.String);
|
|
50
|
+
}
|
|
51
|
+
# reload(String) returns TaskInterface<Void> (not void) and is looked up on
|
|
52
|
+
# the runtime class of whatever ReactHost the app provides; the `***` return
|
|
53
|
+
# type is what makes the rule match — verified against an R8-minified
|
|
54
|
+
# RN 0.85 build, where `void reload(...)` silently kept nothing.
|
|
55
|
+
-keepclassmembers class * implements com.facebook.react.ReactHost {
|
|
56
|
+
*** reload(java.lang.String);
|
|
57
|
+
}
|
|
58
|
+
-keepclassmembers class * implements com.facebook.react.runtime.ReactHostDelegate {
|
|
59
|
+
*** jsBundleLoader;
|
|
60
|
+
*** _jsBundleLoader;
|
|
61
|
+
}
|
|
62
|
+
-keepclassmembers class * implements com.facebook.react.ReactApplication {
|
|
63
|
+
*** getReactHost();
|
|
64
|
+
}
|
|
65
|
+
-keepclassmembers class com.facebook.react.bridge.ReactContext {
|
|
66
|
+
*** hasActiveReactInstance();
|
|
67
|
+
}
|
|
68
|
+
# The host app's BuildConfig is located by name (Class.forName) and its
|
|
69
|
+
# new-architecture flag read by reflection.
|
|
70
|
+
-keep class **.BuildConfig {
|
|
71
|
+
public static boolean IS_NEW_ARCHITECTURE_ENABLED;
|
|
72
|
+
}
|
|
17
73
|
|
|
18
74
|
-keepnames class expo.modules.ExpoReactHostFactory$ExpoReactHostDelegate { *; }
|
|
@@ -55,14 +55,6 @@ final class ApkInstaller {
|
|
|
55
55
|
ReactApplicationContext reactContext,
|
|
56
56
|
Promise promise
|
|
57
57
|
) {
|
|
58
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
59
|
-
promise.reject(
|
|
60
|
-
ErrorCodes.UNSUPPORTED_PLATFORM,
|
|
61
|
-
"APK installation requires Android 5.0 or newer"
|
|
62
|
-
);
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
58
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
67
59
|
return true;
|
|
68
60
|
}
|
|
@@ -129,12 +121,23 @@ final class ApkInstaller {
|
|
|
129
121
|
return false;
|
|
130
122
|
}
|
|
131
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Stages the downloaded APK in a PackageInstaller session and commits it;
|
|
126
|
+
* the status receiver settles the promise. The APK file is deleted once
|
|
127
|
+
* the session has the bytes or the attempt failed, so a leftover can
|
|
128
|
+
* never be installed by a later call (CODE_AUDIT 2.6).
|
|
129
|
+
*/
|
|
132
130
|
static void install(
|
|
133
131
|
ReactApplicationContext reactContext,
|
|
134
132
|
File apkFile,
|
|
135
133
|
String sourceUrl,
|
|
136
134
|
Promise promise
|
|
137
135
|
) {
|
|
136
|
+
// TODO(CODE_AUDIT 2.6): verify a server-provided sha256 of the APK
|
|
137
|
+
// before staging it. The `hash` JS passes to downloadAndInstallApk is
|
|
138
|
+
// the progress-event key ("downloadingApk"), not a digest, so there
|
|
139
|
+
// is nothing to check against yet; until the server sends one, the
|
|
140
|
+
// https-only rule in UpdateModuleImpl is the integrity boundary.
|
|
138
141
|
PackageInstaller packageInstaller = null;
|
|
139
142
|
int sessionId = -1;
|
|
140
143
|
boolean committed = false;
|
|
@@ -216,6 +219,10 @@ final class ApkInstaller {
|
|
|
216
219
|
}
|
|
217
220
|
}
|
|
218
221
|
promise.reject(ErrorCodes.APK_INSTALL_FAILED, "Unable to stage APK installation", error);
|
|
222
|
+
} finally {
|
|
223
|
+
if (apkFile.exists() && !apkFile.delete()) {
|
|
224
|
+
Log.w(UpdateContext.TAG, "Unable to delete staged APK " + apkFile);
|
|
225
|
+
}
|
|
219
226
|
}
|
|
220
227
|
}
|
|
221
228
|
|
|
@@ -13,6 +13,7 @@ final class ArchiveLimits {
|
|
|
13
13
|
static final long RATIO_CHECK_MIN_BYTES = 1L * 1024 * 1024;
|
|
14
14
|
static final long MAX_MANIFEST_BYTES = 16L * 1024 * 1024;
|
|
15
15
|
static final long FREE_DISK_MARGIN_BYTES = 64L * 1024 * 1024;
|
|
16
|
+
static final long UNKNOWN_LENGTH_FREE_SPACE_PROBE_BYTES = 8L * 1024 * 1024;
|
|
16
17
|
|
|
17
18
|
private ArchiveLimits() {
|
|
18
19
|
}
|
|
@@ -4,7 +4,6 @@ import android.content.Context;
|
|
|
4
4
|
import android.content.pm.ApplicationInfo;
|
|
5
5
|
import android.content.pm.PackageManager;
|
|
6
6
|
import android.content.res.Resources;
|
|
7
|
-
import android.os.Build;
|
|
8
7
|
import android.util.DisplayMetrics;
|
|
9
8
|
import android.util.Log;
|
|
10
9
|
import android.util.TypedValue;
|
|
@@ -362,7 +361,7 @@ final class BundledResourceCopier {
|
|
|
362
361
|
try {
|
|
363
362
|
ApplicationInfo appInfo =
|
|
364
363
|
context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
|
|
365
|
-
if (
|
|
364
|
+
if (appInfo.splitSourceDirs != null) {
|
|
366
365
|
for (String splitPath : appInfo.splitSourceDirs) {
|
|
367
366
|
apkPaths.add(splitPath);
|
|
368
367
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package cn.reactnative.modules.update;
|
|
2
|
+
|
|
3
|
+
/** Selects the at-most-two distinct version names supported by cleanup JNI. */
|
|
4
|
+
final class CleanupKeepNames {
|
|
5
|
+
private CleanupKeepNames() {
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static String[] select(String current, String previous, String running) {
|
|
9
|
+
String[] keep = new String[2];
|
|
10
|
+
for (String candidate : new String[] {current, previous, running}) {
|
|
11
|
+
if (candidate == null || candidate.isEmpty()
|
|
12
|
+
|| candidate.equals(keep[0]) || candidate.equals(keep[1])) {
|
|
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;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return keep;
|
|
24
|
+
}
|
|
25
|
+
}
|