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.
Files changed (125) hide show
  1. package/README-CN.md +7 -7
  2. package/README.md +7 -6
  3. package/android/build.gradle +25 -33
  4. package/android/jni/Android.mk +16 -24
  5. package/android/lib/arm64-v8a/librnpushy.so +0 -0
  6. package/android/lib/armeabi-v7a/librnpushy.so +0 -0
  7. package/android/lib/x86/librnpushy.so +0 -0
  8. package/android/lib/x86_64/librnpushy.so +0 -0
  9. package/android/proguard.pro +9 -7
  10. package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +2 -7
  11. package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +23 -6
  12. package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +50 -51
  13. package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +12 -13
  14. package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +3 -0
  15. package/android/src/main/java/cn/reactnative/modules/update/CrashHold.java +22 -30
  16. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +181 -332
  17. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -1
  18. package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +27 -0
  19. package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +1 -1
  20. package/android/src/main/java/cn/reactnative/modules/update/FlowBridge.java +32 -8
  21. package/android/src/main/java/cn/reactnative/modules/update/HttpClients.java +63 -0
  22. package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +0 -11
  23. package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +66 -116
  24. package/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +9 -1
  25. package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +1 -1
  26. package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +32 -60
  27. package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +8 -23
  28. package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +42 -80
  29. package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +22 -39
  30. package/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +176 -299
  31. package/android/src/main/java/cn/reactnative/modules/update/Threads.java +35 -0
  32. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +242 -174
  33. package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +1 -1
  34. package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +75 -17
  35. package/android/src/main/java/cn/reactnative/modules/update/{UpdateModuleImpl.java → UpdateModule.java} +113 -95
  36. package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +16 -19
  37. package/android/src/oldarch/cn/reactnative/modules/update/NativePushySpec.java +69 -0
  38. package/cpp/android_jni/archive_limits_jni.cpp +83 -0
  39. package/cpp/android_jni/install_record_jni.cpp +89 -0
  40. package/cpp/android_jni/jni_log.h +31 -0
  41. package/cpp/android_jni/jni_natives.h +40 -0
  42. package/cpp/android_jni/jni_registration.cpp +133 -0
  43. package/cpp/android_jni/jni_util.h +160 -0
  44. package/cpp/android_jni/update_core_android.cpp +450 -0
  45. package/cpp/android_jni/update_flow_jni.cpp +133 -0
  46. package/{android/jni → cpp/hpatch}/hpatch.c +5 -5
  47. package/{android/jni → cpp/hpatch}/hpatch.h +4 -1
  48. package/cpp/patch_core/archive_limits.cpp +194 -0
  49. package/cpp/patch_core/archive_limits.h +99 -10
  50. package/cpp/patch_core/archive_patch_core.cpp +96 -12
  51. package/cpp/patch_core/archive_patch_core.h +49 -0
  52. package/cpp/patch_core/digest.h +5 -4
  53. package/cpp/patch_core/error_codes.h +5 -3
  54. package/cpp/patch_core/install_record.cpp +430 -0
  55. package/cpp/patch_core/install_record.h +163 -9
  56. package/cpp/patch_core/patch_core.cpp +62 -16
  57. package/cpp/patch_core/patch_core.h +27 -7
  58. package/cpp/patch_core/patch_manifest.cpp +201 -0
  59. package/cpp/patch_core/patch_manifest.h +74 -0
  60. package/cpp/patch_core/state_ops.h +3 -3
  61. package/cpp/update_flow_core/flow_json.cpp +82 -8
  62. package/cpp/update_flow_core/flow_json.h +21 -0
  63. package/cpp/update_flow_core/native_config.cpp +456 -0
  64. package/cpp/update_flow_core/native_config.h +84 -0
  65. package/cpp/update_flow_core/native_round.cpp +326 -0
  66. package/cpp/update_flow_core/native_round.h +90 -0
  67. package/cpp/update_flow_core/update_flow_core.h +2 -2
  68. package/harmony/hvigor-plugin.ts +11 -19
  69. package/harmony/pushy/src/main/cpp/CMakeLists.txt +17 -26
  70. package/harmony/pushy/src/main/cpp/PushyPackage.h +2 -2
  71. package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +55 -94
  72. package/harmony/pushy/src/main/cpp/pushy.cpp +694 -517
  73. package/harmony/pushy/src/main/ets/ArchiveLimits.ts +48 -72
  74. package/harmony/pushy/src/main/ets/DownloadTask.ts +164 -404
  75. package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +1 -3
  76. package/harmony/pushy/src/main/ets/ErrorCodes.ts +1 -14
  77. package/harmony/pushy/src/main/ets/EventHub.ts +1 -1
  78. package/harmony/pushy/src/main/ets/InstallRecord.ts +52 -151
  79. package/harmony/pushy/src/main/ets/Logger.ts +20 -34
  80. package/harmony/pushy/src/main/ets/NativePatchCore.ts +116 -42
  81. package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -85
  82. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -25
  83. package/harmony/pushy/src/main/ets/SaveFile.ts +99 -18
  84. package/harmony/pushy/src/main/ets/SyncCoordinator.ts +136 -335
  85. package/harmony/pushy/src/main/ets/Texts.ts +0 -4
  86. package/harmony/pushy/src/main/ets/UpdateContext.ts +106 -119
  87. package/harmony/pushy.har +0 -0
  88. package/ios/RCTPushy/RCTPushy+Launch.mm +185 -0
  89. package/ios/RCTPushy/RCTPushy.h +12 -0
  90. package/ios/RCTPushy/RCTPushy.mm +87 -2634
  91. package/ios/RCTPushy/RCTPushyCommon.h +34 -0
  92. package/ios/RCTPushy/RCTPushyConfiguration.h +0 -8
  93. package/ios/RCTPushy/RCTPushyConfiguration.mm +40 -121
  94. package/ios/RCTPushy/RCTPushyDownloader.mm +40 -37
  95. package/ios/RCTPushy/RCTPushyInstallRecord.mm +61 -0
  96. package/ios/RCTPushy/RCTPushyInstaller.mm +689 -0
  97. package/ios/RCTPushy/RCTPushyInternal.h +144 -0
  98. package/ios/RCTPushy/RCTPushyOrchestrator.mm +1001 -0
  99. package/ios/RCTPushy/RCTPushyState.mm +314 -0
  100. package/package.json +25 -23
  101. package/react-native-update.podspec +73 -82
  102. package/src/client.ts +361 -451
  103. package/src/context.ts +1 -1
  104. package/src/core.ts +35 -8
  105. package/src/endpoint.ts +0 -2
  106. package/src/error.ts +13 -32
  107. package/src/i18n.ts +18 -83
  108. package/src/locales/en.ts +0 -4
  109. package/src/locales/zh.ts +0 -4
  110. package/src/provider.tsx +60 -53
  111. package/src/telemetry.ts +0 -13
  112. package/src/type.ts +0 -15
  113. package/src/updateFlowCore.ts +116 -10
  114. package/src/utils.ts +49 -42
  115. package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +0 -6
  116. package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +0 -104
  117. package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +0 -37
  118. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +0 -121
  119. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +0 -125
  120. package/cpp/patch_core/jni_natives.h +0 -27
  121. package/cpp/patch_core/jni_registration.cpp +0 -103
  122. package/cpp/patch_core/jni_util.h +0 -60
  123. package/cpp/patch_core/patch_core_android.cpp +0 -97
  124. package/cpp/patch_core/update_core_android.cpp +0 -485
  125. 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
- 本组件是面向 React Native 提供热更新功能的组件,详情请访问我们的官方网站 <https://pushy.reactnative.cn>。
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.html)
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,字节码约 4.4 MB)。每个补丁都先通过完整往返校验再记录体积,每个 delta 模式产物都通过与普通编译的等价性校验。完整的评测方法、测试数据与可复现代码见 **[hbc-diff-benchmark](https://github.com/sunnylqm/hbc-diff-benchmark)**。
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
- | 单行文案修改 | 1901.5 KB | 93.7 KB | **3.4 KB** | **小 28 倍** |
112
- | 小功能(约 60 行) | 1913.9 KB | 411.6 KB | **50.2 KB** | **小 8.2 倍** |
113
- | 中等功能(约 300 行) | 1973.7 KB | 551.6 KB | **97.8 KB** | **小 5.6 倍** |
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` provides over-the-air update capabilities for React Native apps. For full documentation, visit:
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, ~4.4 MB bytecode). 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
+ 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 | 1901.5 KB | 93.7 KB | **3.4 KB** | **28× smaller** |
115
- | Small feature (~60 LOC) | 1913.9 KB | 411.6 KB | **50.2 KB** | **8.2× smaller** |
116
- | Medium feature (~300 LOC) | 1973.7 KB | 551.6 KB | **97.8 KB** | **5.6× smaller** |
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.
@@ -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
- String stdout
60
- if (GradleVersion.current() >= GradleVersion.version('7.5')) {
61
- // providers.exec exists since Gradle 7.5; Project.exec is gone in 9.
62
- def output = providers.exec {
63
- workingDir projectDir
64
- commandLine nodeArgs
65
- ignoreExitValue = true
66
- }
67
- if (output.result.get().exitValue != 0) {
68
- return null
69
- }
70
- stdout = output.standardOutput.asText.get()
71
- } else {
72
- def out = new ByteArrayOutputStream()
73
- def result = project.exec {
74
- workingDir projectDir
75
- commandLine nodeArgs
76
- standardOutput = out
77
- errorOutput = new ByteArrayOutputStream()
78
- ignoreExitValue true
79
- }
80
- if (result.exitValue != 0) {
81
- return null
82
- }
83
- stdout = out.toString()
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
- return parseMajorVersion(stdout.trim())
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
- if (isNewArchitectureEnabled()) {
195
- java.srcDirs += ['src/newarch']
196
- } else {
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
 
@@ -3,36 +3,28 @@ LOCAL_PATH := $(call my-dir)
3
3
  include $(CLEAR_VARS)
4
4
 
5
5
  LOCAL_MODULE := rnpushy
6
- LOCAL_CPPFLAGS += -std=c++17
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
- hpatch.c \
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
- lzma/C/Lzma2Dec.c
24
+ lzma/C/Lzma2Dec.c
22
25
 
23
26
  LOCAL_SRC_FILES := \
24
- ../../cpp/patch_core/archive_patch_core.cpp \
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
@@ -1,19 +1,23 @@
1
- # JNI-constructed result classes: cpp/patch_core/update_core_android.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
- # JNI entry points are bound by their mangled Java names.
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 React Native / Expo / the system by name.
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 UpdateModuleImpl is the integrity boundary.
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
- byte[] buffer = new byte[COPY_BUFFER_SIZE];
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
- * Java mirror of cpp/patch_core/archive_limits.h — keep the two in sync by
5
- * hand. Damage bounds for a corrupt or hostile update package.
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
- // Holds the exact archive a CRC32 match came from, so the fallback copy
36
- // reads from that archive even if another APK exposes the same entry name
37
- // with different bytes.
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
- void copyFromResource(
53
- HashMap<String, ArrayList<File>> resToCopy,
54
- HashMap<String, Long> crcByFrom
55
- ) throws IOException {
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
- HashMap<String, ZipEntry> availableEntries = new HashMap<String, ZipEntry>();
58
- HashMap<String, SafeZipFile> zipFileMap = new HashMap<String, SafeZipFile>();
59
- HashMap<String, SafeZipFile> entryToZipFileMap = new HashMap<String, SafeZipFile>();
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
- zipFileMap.put(apkPath, zipFile);
82
+ zipFiles.add(zipFile);
70
83
  Enumeration<? extends ZipEntry> entries = zipFile.entries();
71
84
  while (entries.hasMoreElements()) {
72
85
  ZipEntry ze = entries.nextElement();
73
- String entryName = ze.getName();
74
- if (!availableEntries.containsKey(entryName)) {
75
- availableEntries.put(entryName, ze);
76
- entryToZipFileMap.put(entryName, zipFile);
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, new ZipSource(ze, zipFile));
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.putIfAbsent(normalized, entryName);
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
- ZipEntry entry = availableEntries.get(fromPath);
126
+ ZipSource entry = availableEntries.get(fromPath);
113
127
  String actualSourcePath = fromPath;
114
- SafeZipFile matchedZipFile = null;
115
128
  ResolvedResourceSource resolvedResource = null;
116
- Long wantedCrc = crcByFrom != null ? crcByFrom.get(fromPath) : null;
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 && entry.getCrc() != wantedCrc.longValue()) {
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.entry;
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
- ZipEntry resolvedEntry = availableEntries.get(actualSourcePath);
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
- SafeZipFile sourceZipFile = matchedZipFile != null
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(zipFileMap);
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
- try {
362
- ApplicationInfo appInfo =
363
- context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
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(HashMap<String, SafeZipFile> zipFileMap) {
379
- for (SafeZipFile zipFile : zipFileMap.values()) {
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 (UpdateContext.DEBUG) {
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
- /** Selects the at-most-two distinct version names supported by cleanup JNI. */
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[] keep = new String[2];
16
+ LinkedHashSet<String> keep = new LinkedHashSet<>();
10
17
  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;
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
  }