react-native-update 10.55.0 → 10.56.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.
Files changed (76) hide show
  1. package/README-CN.md +35 -0
  2. package/README.md +37 -0
  3. package/android/build.gradle +53 -27
  4. package/android/jni/Application.mk +6 -1
  5. package/android/jni/hpatch.c +59 -4
  6. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  7. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  8. package/android/lib/x86/librnupdate.so +0 -0
  9. package/android/lib/x86_64/librnupdate.so +0 -0
  10. package/android/proguard.pro +70 -14
  11. package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +15 -8
  12. package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +1 -0
  13. package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +1 -2
  14. package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +25 -0
  15. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -34
  16. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +29 -0
  17. package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +15 -0
  18. package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +59 -0
  19. package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +27 -1
  20. package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +57 -16
  21. package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +38 -46
  22. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +279 -183
  23. package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +14 -0
  24. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +222 -215
  25. package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +5 -0
  26. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +23 -25
  27. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +29 -29
  28. package/cpp/patch_core/archive_limits.h +6 -0
  29. package/cpp/patch_core/jni_util.h +6 -2
  30. package/cpp/patch_core/patch_core.cpp +237 -84
  31. package/cpp/patch_core/patch_core.h +11 -0
  32. package/cpp/patch_core/patch_core_android.cpp +49 -33
  33. package/cpp/patch_core/update_core_android.cpp +134 -103
  34. package/cpp/update_flow_core/flow_json.cpp +150 -17
  35. package/cpp/update_flow_core/flow_json.h +13 -1
  36. package/cpp/update_flow_core/update_flow_core.cpp +78 -8
  37. package/cpp/update_flow_core/update_flow_core.h +9 -0
  38. package/cpp/update_flow_core/update_flow_jni.cpp +44 -25
  39. package/harmony/hvigor-plugin.ts +168 -21
  40. package/harmony/pushy/src/README.md +74 -0
  41. package/harmony/pushy/src/main/cpp/CMakeLists.txt +13 -3
  42. package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +11 -5
  43. package/harmony/pushy/src/main/cpp/pushy.cpp +103 -18
  44. package/harmony/pushy/src/main/ets/ArchiveLimits.ts +61 -5
  45. package/harmony/pushy/src/main/ets/DownloadTask.ts +255 -166
  46. package/harmony/pushy/src/main/ets/ErrorCodes.ts +75 -0
  47. package/harmony/pushy/src/main/ets/EventHub.ts +22 -20
  48. package/harmony/pushy/src/main/ets/InstallRecord.ts +79 -21
  49. package/harmony/pushy/src/main/ets/Logger.ts +42 -16
  50. package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +98 -17
  51. package/harmony/pushy/src/main/ets/NativePatchCore.ts +10 -3
  52. package/harmony/pushy/src/main/ets/PathUtils.ts +9 -3
  53. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +139 -53
  54. package/harmony/pushy/src/main/ets/UpdateContext.ts +269 -159
  55. package/harmony/pushy/src/main/module.json5 +5 -1
  56. package/harmony/pushy.har +0 -0
  57. package/ios/PrivacyInfo.xcprivacy +79 -0
  58. package/ios/RCTPushy/RCTPushy.mm +406 -170
  59. package/ios/RCTPushy/RCTPushyDownloader.h +14 -0
  60. package/ios/RCTPushy/RCTPushyDownloader.mm +136 -11
  61. package/package.json +14 -24
  62. package/react-native-update.podspec +36 -3
  63. package/src/client.ts +244 -86
  64. package/src/context.ts +40 -27
  65. package/src/core.ts +41 -16
  66. package/src/endpoint.ts +2 -1
  67. package/src/error.ts +49 -11
  68. package/src/index.ts +14 -0
  69. package/src/locales/en.ts +1 -23
  70. package/src/locales/zh.ts +1 -22
  71. package/src/provider.tsx +159 -111
  72. package/src/type.ts +0 -2
  73. package/src/updateFlowCore.ts +10 -2
  74. package/src/utils.ts +61 -4
  75. package/android/src/main/java/cn/reactnative/modules/update/ReactNativeHostHandler.java +0 -13
  76. 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
@@ -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
- // For pnpm catalogs (e.g. "catalog:native"), the version string in package.json
73
- // is not numeric, so we resolve the installed expo package version as fallback.
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 declaredExpoMajorVersion = parseMajorVersion(expoVersionString)
76
- Integer installedExpoMajorVersion = expoVersionString ?
77
- resolveInstalledExpoMajorVersion(packageJsonFile.parentFile) :
78
- null
79
- Integer expoMajorVersion = installedExpoMajorVersion ?: declaredExpoMajorVersion
80
-
81
- boolean expoVersionIsHighEnough =
82
- expoMajorVersion != null && expoMajorVersion >= 50
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 = expoVersionIsHighEnough // Update based on version check
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', 31)
129
- buildToolsVersion safeExtGet('buildToolsVersion', '31.0.0')
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', 27)
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
- if (expoProject) {
150
- java.srcDirs += ['java/expo/modules/pushy']
151
- } else {
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
- lintOptions {
176
- abortOnError false
194
+ if (agpMajor >= 7) {
195
+ lint {
196
+ abortOnError false
197
+ }
198
+ } else {
199
+ lintOptions {
200
+ abortOnError false
201
+ }
177
202
  }
178
- }
179
203
 
180
- repositories {
181
- maven {
182
- // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
183
- url "$rootDir/../node_modules/react-native/android"
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
- APP_CPPFLAGS += -std=c++17 -Oz -fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables
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
@@ -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 (lzmaDecompressPlugin.is_can_open(compressType))
32
- return &lzmaDecompressPlugin;
86
+ if (cappedLzmaDecompressPlugin.is_can_open(compressType))
87
+ return &cappedLzmaDecompressPlugin;
33
88
  #endif
34
89
  #ifdef _CompressPlugin_lzma2
35
- if (lzma2DecompressPlugin.is_can_open(compressType))
36
- return &lzma2DecompressPlugin;
90
+ if (cappedLzma2DecompressPlugin.is_can_open(compressType))
91
+ return &cappedLzma2DecompressPlugin;
37
92
  #endif
38
93
  return 0;
39
94
  }
Binary file
Binary file
@@ -1,18 +1,74 @@
1
- # Keep our update module classes
2
- -keepnames class cn.reactnative.modules.update.DownloadTask { *; }
3
- -keepnames class cn.reactnative.modules.update.UpdateModuleImpl { *; }
4
- -keepnames class cn.reactnative.modules.update.** { *; }
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
- # Keep React Native classes
7
- -keepnames class com.facebook.react.ReactInstanceManager { *; }
8
- -keepnames class com.facebook.react.** { *; }
9
- -keepnames class com.facebook.react.bridge.** { *; }
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
- # Keep fields used in reflection
13
- -keepclassmembers class com.facebook.react.ReactActivity { *; }
14
- -keepclassmembers class com.facebook.react.ReactInstanceManager { *; }
15
- -keepclassmembers class com.facebook.react.ReactDelegate { *; }
16
- -keepclassmembers class com.facebook.react.ReactHost { *; }
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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && appInfo.splitSourceDirs != null) {
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
+ }