react-native-update 10.50.0 → 10.51.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/NATIVE_CHECKUPDATE_DESIGN.md +251 -21
- package/NATIVE_CHECK_FOLLOWUPS.md +365 -0
- package/README-CN.md +14 -1
- package/README.md +14 -1
- package/android/build.gradle +10 -0
- package/android/jni/Android.mk +5 -1
- 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/src/main/java/cn/reactnative/modules/update/DownloadTask.java +76 -21
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +455 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java +23 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +160 -42
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +44 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/cpp/update_flow_core/flow_json.cpp +522 -0
- package/cpp/update_flow_core/flow_json.h +103 -0
- package/cpp/update_flow_core/tests/flow_vectors.json +1401 -0
- package/cpp/update_flow_core/tests/update_flow_core_test.cpp +317 -0
- package/cpp/update_flow_core/update_flow_core.cpp +301 -0
- package/cpp/update_flow_core/update_flow_core.h +77 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +121 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +7 -0
- package/harmony/pushy/src/main/cpp/pushy.cpp +103 -1
- package/harmony/pushy/src/main/ets/DownloadTask.ts +48 -9
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +3 -0
- package/harmony/pushy/src/main/ets/MonotonicClock.ts +13 -0
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +500 -0
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +14 -0
- package/harmony/pushy/src/main/ets/PathUtils.ts +21 -0
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +22 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +148 -69
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +779 -61
- package/ios/RCTPushy/RCTPushyDownloader.h +1 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +4 -3
- package/package.json +3 -1
- package/react-native-update.podspec +2 -0
- package/src/NativePushy.ts +14 -0
- package/src/client.ts +341 -118
- package/src/endpoint.ts +7 -29
- package/src/provider.tsx +38 -2
- package/src/type.ts +26 -0
- package/src/updateFlowCore.ts +320 -0
- package/src/utils.ts +1 -12
- package/src/isInRollout.ts +0 -87
- package/src/resolveCheckResult.ts +0 -30
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# 原生检测更新设计:让砖机也能被修好
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 状态:**实现完成(2026-08-07,e34f390..24f3adf)**——三端编排全部落地:
|
|
4
|
+
> 标定层 syncNativeConfig、iOS RCTPushyOrchestrator、Android
|
|
5
|
+
> NativeCheckOrchestrator(librnupdate.so 已重编 4 ABI 并过符号/对齐校验)、
|
|
6
|
+
> Harmony NativeCheckOrchestrator.ts + NAPI,§10.3 的 JS 响应缓存复用
|
|
7
|
+
> (getNativeCheckCache,2 分钟 TTL)也已闭环。剩余:e2e 用例(沿用
|
|
8
|
+
> Example/e2etest 基建,验证"坏 bundle 下原生仍拉到修复版"端到端场景)、
|
|
9
|
+
> README/CHANGELOG、发版。
|
|
4
10
|
> 取代:`REMOTE_RESET_DESIGN.md`(本地启动熔断方案,已放弃,理由见 §1.2)
|
|
5
|
-
>
|
|
11
|
+
> 前置:bundleHash 迁移 Phase 0/1/2 已上线且 buildTime 永久保留作 fallback 已定稿
|
|
12
|
+
> (2026-08-03)——checkUpdate 的 wire protocol 已稳定,协议前置解除
|
|
6
13
|
> 关联:[[native-update-reset-design]] 的 Phase 3
|
|
7
14
|
|
|
8
15
|
---
|
|
@@ -82,9 +89,35 @@ A 直接淘汰。B 是 [[native-update-reset-design]] 里 Phase 3 的原计划
|
|
|
82
89
|
|
|
83
90
|
**B 与 C 的差别只有一个:检测逻辑本身出 bug 时,能不能不发新 binary 就修好。**
|
|
84
91
|
|
|
92
|
+
补充两点评审结论(2026-08-06):其一,B 并不消灭 TS 实现——app JS 侧的交互
|
|
93
|
+
流程仍要用同一套决策逻辑,所以 B 实际是 **TS + C++ 双实现长期同步**(每次协
|
|
94
|
+
议演进都要重编预编译 `.so`),而 C 是同一份 TS 源两处复用,这是 C 相对 B 的
|
|
95
|
+
另一日常优势。其二,C 的独占收益比上表暗示的窄:只要 JS 侧 `checkUpdate` 保
|
|
96
|
+
留为全功能回退(§6 是复用结果、不是拆除),原生检测逻辑出 bug 的最坏情形是
|
|
97
|
+
正常设备仍被 JS 路径救起、只有砖机在窗口期救不了——即退回现状,而非"全网
|
|
98
|
+
瘫"。真正集中风险的是原生**编排**代码(R2),而那部分 B / C 完全相同,
|
|
99
|
+
guardian 救不了它。
|
|
100
|
+
|
|
101
|
+
> **最终裁决(2026-08-07):选 B,三端统一 C++,金标向量作为双实现的强制契
|
|
102
|
+
> 约(已实现)。** 推理链:引擎方案的全部价值 = 单一事实来源;Android 宿主
|
|
103
|
+
> Hermes 被符号侦察排除、javascriptengine 因可用性是运行时属性只能当可选优
|
|
104
|
+
> 化——一个必定可用的 C++ 实现无论如何要存在;一旦如此,混合形态(iOS/
|
|
105
|
+
> Harmony 求值 + Android C++)要养两套机制,统一 C++ 的改动次数相同却删掉了
|
|
106
|
+
> 求值器机制整个类别;QuickJS 统一保单源要付 200KB×4 + 三端引擎集成,只换
|
|
107
|
+
> 来"少改一处代码",而那处改动有向量护栏,是机械劳动。
|
|
108
|
+
> "单源"要保的性质是**语义唯一且被机械强制**,由金标向量提供:
|
|
109
|
+
> `src/updateFlowCore.ts` 是参照实现(oracle),
|
|
110
|
+
> `scripts/generate-flow-vectors.ts` 产出
|
|
111
|
+
> `cpp/update_flow_core/tests/flow_vectors.json`(金标向量集),
|
|
112
|
+
> `src/__tests__/flowVectors.test.ts` 钉住 TS 与向量文件一致,
|
|
113
|
+
> `scripts/test-update-flow-core.sh`(CI cpp-test job,带 ASan/UBSan)钉住
|
|
114
|
+
> C++ 移植与向量一致。**纪律:语义改动先落 TS → 重新生成向量 → 移植 C++,
|
|
115
|
+
> 两侧不绿不发版。** C++ 侧的 `flow_json` 按 JS 语义实现(插入序对象、
|
|
116
|
+
> undefined ≠ null、JS truthiness / 严格相等),移植才能逐字节对齐。
|
|
117
|
+
|
|
85
118
|
---
|
|
86
119
|
|
|
87
|
-
## 5. Guardian bundle
|
|
120
|
+
## 5. Guardian bundle(已否决路线,留档防止重新发明;裁决见 §4)
|
|
88
121
|
|
|
89
122
|
### 5.1 关键设计约束:它必须是纯函数,不做 IO
|
|
90
123
|
|
|
@@ -93,31 +126,93 @@ A 直接淘汰。B 是 [[native-update-reset-design]] 里 Phase 3 的原计划
|
|
|
93
126
|
正确的边界是:**guardian 只做决策,IO 全部由原生执行。**
|
|
94
127
|
|
|
95
128
|
```
|
|
96
|
-
原生 ──> guardian.buildCheckRequest(state) ──> {
|
|
97
|
-
原生 <── (
|
|
129
|
+
原生 ──> guardian.buildCheckRequest(state) ──> { endpoints, queryUrls, path, headers, body, timeoutMs }
|
|
130
|
+
原生 <── (按 endpoints 顺序逐个请求,单个超时即换下一个;
|
|
131
|
+
首个失败后拉 queryUrls 合并远程候选、排除已失败的,继续顺序尝试)
|
|
98
132
|
原生 ──> guardian.decide(state, responseText) ──> { action, hash, url, type } | { action: 'none' }
|
|
99
133
|
原生 <── (下载 + patch_core 应用 + state_core switchVersion,全是现成的)
|
|
100
134
|
原生 ──> guardian.onOutcome(state, result) ──> { nextState }
|
|
101
135
|
```
|
|
102
136
|
|
|
137
|
+
**endpoint 计划是声明式的**(初稿此处只画了"一问一答",漏掉了 JS 侧真实存在的
|
|
138
|
+
多 endpoint 回退与远程 endpoint 发现):候选排序(随机首选分摊负载 + 配置序回
|
|
139
|
+
退 + 失败排除)是纯策略,由 `orderEndpointCandidates` 给出,随机数由原生作为
|
|
140
|
+
输入注入(guardian 不可自取随机)。**原生侧刻意不实现 JS 交互路径的 hedged
|
|
141
|
+
race**(`src/endpoint.ts` 的 250ms 错峰竞速):原生检测跑在冷启动后台、结果下
|
|
142
|
+
次启动才生效,延迟不敏感(§7 R5 本来就要求延迟数秒),顺序回退 + 单请求超时
|
|
143
|
+
就够了——换来的是三端各自的执行引擎退化为一个 for 循环,无定时器、无 abort
|
|
144
|
+
协调。两条路径共享同一份候选排序策略,只在并发形态上分叉。
|
|
145
|
+
|
|
103
146
|
这个边界带来的简化是决定性的:
|
|
104
147
|
|
|
105
|
-
- **不需要事件循环、不需要 Promise、不需要注入 HTTP** ——
|
|
148
|
+
- **不需要事件循环、不需要 Promise、不需要注入 HTTP** —— 只是同步求值一段小 JS 再调几个函数
|
|
106
149
|
- **隔离性天然成立** —— guardian 与 app bundle 是两个独立的 parse 单元、独立求值;app bundle 的语法错误跟它毫无关系
|
|
107
|
-
-
|
|
150
|
+
- 灰度分桶、diff→pdiff→full 选择、`expVersion` 解析、URL 拼接、请求体构造、
|
|
151
|
+
endpoint 排序**已全部抽为纯函数并在 JS 侧原地使用**(`src/updateFlowCore.ts`:
|
|
152
|
+
`buildCheckRequestBody` / `resolveCheckResult` / `decideDownload` /
|
|
153
|
+
`isInRollout` / `joinUrls` / `orderEndpointCandidates`,import 闭包为纯,
|
|
154
|
+
可在裸引擎中求值),guardian 直接复用同一份源码
|
|
108
155
|
|
|
109
156
|
### 5.2 运行时
|
|
110
157
|
|
|
111
|
-
在后台线程创建一个
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
158
|
+
在后台线程创建一个 JS 运行时,求值 guardian 源码,调用导出的函数,用完销毁。全同步,毫秒级。
|
|
159
|
+
|
|
160
|
+
**已定稿(2026-08-06):guardian 以纯文本 JS 源码分发与求值,不用 HBC。**
|
|
161
|
+
Hermes 字节码格式不跨版本稳定——若分发 HBC,服务端覆盖包必须按宿主 Hermes
|
|
162
|
+
版本分桶,基线包也与构建期 hermesc 版本绑死,复杂度远超收益。源码求值走的
|
|
163
|
+
是慢速路径,但 guardian 每次冷启动只求值一次、代码量千行级,毫秒级完全可接
|
|
164
|
+
受。代价:宿主 libhermes 若编译时裁掉了源码编译器则不可用——这归入下面的链
|
|
165
|
+
接可行性验证。
|
|
166
|
+
|
|
167
|
+
**源码可移植性已验证(2026-08-07)**:`src/updateFlowCore.ts` 经
|
|
168
|
+
`bun build --format=cjs` 打成 6.4KB 单文件纯文本 JS(零依赖、零 require),
|
|
169
|
+
在三个裸引擎下对同一驱动脚本的输出**逐字节一致**且全部正确——macOS 系统
|
|
170
|
+
`jsc`(与 iOS JavaScriptCore 同源)、Hermes VM(RN 0.73 配套版本,源码直接
|
|
171
|
+
求值无需 HBC)、Node V8。函数调用边界(JSON 进出)即 §5.1 的三步接口,工作
|
|
172
|
+
正常。进一步用 ObjC 写了最小原生宿主实测通过:`JSContext` 求值源码 →
|
|
173
|
+
`callWithArguments` 调 `decideDownload` → 拿回决策 JSON——40 行代码、仅链
|
|
174
|
+
系统 Foundation + JavaScriptCore 两个框架,就是 iOS 编排器的最终形态。语法下限:产物含 `?.`/`??`/对象展开 → Hermes ≥0.7(RN 0.64+)、iOS
|
|
175
|
+
JSC ≥13.4;如需更老目标,guardian 构建加 es2015 降级即可。
|
|
176
|
+
|
|
177
|
+
**纯文本定稿的推论——三端不必用同一个引擎**:源码是共享物,引擎只是求值
|
|
178
|
+
器,选型可以按端就地取材:
|
|
179
|
+
|
|
180
|
+
| 端 | 引擎 | 风险 |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| iOS | 系统 JavaScriptCore 框架 | **零**——系统框架,零链接、零包体、零版本耦合,上面已实测同源引擎 |
|
|
183
|
+
| Harmony | 系统 JSVM-API(V8,API 11+,RNOH 本就要求 5.0+) | 低——系统能力,待落地时确认 API 细节 |
|
|
184
|
+
| Android | 开放问题:androidx.javascriptengine(系统 WebView 引擎、沙箱进程、异步)/ QuickJS(~200KB×4 ABI)/ ~~链接宿主 Hermes~~ | **剩余的全部 spike 范围**(Hermes 路线已被侦察排除,见下) |
|
|
185
|
+
|
|
186
|
+
**Android 链接宿主 Hermes 已排除(2026-08-07 对 hermes-android
|
|
187
|
+
250829098.0.10 / RN 0.85 预编译产物的符号侦察)**:prefab 里虽然带了
|
|
188
|
+
`hermes_abi/hermes_abi.h`(稳定 C ABI 头),但其入口 `get_hermes_abi_vtable`
|
|
189
|
+
**并未从 `libhermesvm.so` 导出**——只导出了 C++ 的
|
|
190
|
+
`facebook::hermes::makeHermesRuntime(RuntimeConfig const&)`,而走 C++ 路线
|
|
191
|
+
要求调用方与宿主版本的 `RuntimeConfig` 布局、libc++ ABI 精确匹配,且 JSI 符
|
|
192
|
+
号导出为零(消费方须自行编译与宿主一致版本的 jsi.cpp)。对跨 RN 版本分发的
|
|
193
|
+
预编译 `librnupdate.so`,这是不可控的版本矩阵;老 RN 的 `libhermes.so` 连库
|
|
194
|
+
名和导出面都不同。若未来官方开始导出稳定 C ABI,此路线可复活。当前 Android
|
|
195
|
+
实际候选就两个:javascriptengine(零包体、依赖 WebView provider、沙箱进程异
|
|
196
|
+
步——对冷启动后台任务可接受,需真机验证)与 QuickJS(确定可行、包体代价)。
|
|
197
|
+
|
|
198
|
+
原生编排层(HTTP、下载、状态)仍放 `cpp/` 与 `patch_core` / `state_core` 同级三端共享;求值器作为注入接口由各端实现,与 HTTP 客户端同一地位。
|
|
199
|
+
|
|
200
|
+
**剩余风险收窄为 Android 单点**:直接链接 libhermes 的 C++ API 在 RN 各版本间会变,且 `librnupdate.so` 是预编译产物(4 个 ABI)。若三个 Android 候选都不干净,**仅 Android 一端退回方案 B(决策逻辑 C++ 重写)也是可接受的混合形态**——三端源码一致性只在 iOS/Harmony 保持,Android 以 updateFlowCore 为参照实现并靠共享测试向量对齐。
|
|
118
201
|
|
|
119
202
|
### 5.3 分发与覆盖
|
|
120
203
|
|
|
204
|
+
> **裁决(2026-08-07):远程覆盖通道不做,guardian 只带随 binary 打包的基线。**
|
|
205
|
+
> 覆盖通道买的保险是"决策纯函数出 bug 时不发 binary 就能修",但决策层是全
|
|
206
|
+
> 链路最可测的部分(纯函数、单测、与 JS 侧同一份源码),且已有两条兜底:
|
|
207
|
+
> JS 侧 checkUpdate 全功能回退(决策 bug 最坏退回现状,不是新增灾难)、服
|
|
208
|
+
> 务端塑造响应/重绑版本本身就是一条远程修复通道(决策层消费的是服务端数
|
|
209
|
+
> 据)。代价却是全系统最敏感的安全面——启动最早期执行、有权决定装什么版本
|
|
210
|
+
> 的服务端下发代码——加上 §5.4/§5.5 的全部工程量,而覆盖机制自身是原生代
|
|
211
|
+
> 码,它出 bug 同样无法远程修。保费高于风险敞口。
|
|
212
|
+
> 未来若决策层 bug 真在现场咬人,优先评估**搭现有热更通道便车**(ppk 附带
|
|
213
|
+
> guardian.js,复用既有 hash 校验与下发权限),不自建通道。§5.4/§5.5 保留
|
|
214
|
+
> 作为那时的设计输入;"救砖"能力来自 §8 第 4 步的原生编排,不受本裁决影响。
|
|
215
|
+
|
|
121
216
|
| | 来源 | 作用 |
|
|
122
217
|
|---|---|---|
|
|
123
218
|
| 基线 | 随 binary 打包(asset / rawfile) | 永远存在,已随发版验证过 |
|
|
@@ -174,13 +269,28 @@ app 侧的 `client.ts` / `UpdateProvider` 仍然负责**交互**:更新提示
|
|
|
174
269
|
|
|
175
270
|
## 8. 分期
|
|
176
271
|
|
|
177
|
-
1.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
272
|
+
1. ~~**bundleHash 迁移**~~ **已解除**(2026-08-03)—— Phase 0/1/2 已上线、
|
|
273
|
+
判定开关双端开启、buildTime 永久保留作 fallback 定稿,checkUpdate 的
|
|
274
|
+
wire protocol 已稳定;剩余的 Phase 3(SyncBinaryVersion 迁移)是客户端
|
|
275
|
+
本地状态变更,不动协议,与本方案只需在落地顺序上错开(都动原生启动路
|
|
276
|
+
径与状态 schema),不再构成前置
|
|
277
|
+
2. ~~**纯函数抽取**~~ **已完成**(2026-08-07)—— `src/updateFlowCore.ts`:
|
|
278
|
+
`buildCheckRequestBody` / `resolveCheckResult` / `decideDownload` /
|
|
279
|
+
`isInRollout` / `joinUrls` / `orderEndpointCandidates`,无 IO、无
|
|
280
|
+
react-native 依赖、无模块级状态(身份/随机数均参数注入),import 闭包
|
|
281
|
+
为纯;client.ts / provider.tsx / endpoint.ts 已原地改用,单测覆盖
|
|
282
|
+
3. ~~**运行时选型**~~ **已关闭:改判方案 B**(2026-08-07,裁决与推理链见
|
|
283
|
+
§4)。`cpp/update_flow_core` 已实现(flow_json + 七个决策函数的 1:1 移
|
|
284
|
+
植),金标向量集在本机与 CI(ASan/UBSan)全过
|
|
285
|
+
4. **原生编排** —— 三端 HTTP + 调用 `update_flow_core` + 复用现有下载/
|
|
286
|
+
patch/state;endpoint 执行引擎为顺序回退(§5.1),不移植 hedged race。
|
|
287
|
+
Android 侧 `update_flow_core` 进 `librnupdate.so`(协议演进从此绑定
|
|
288
|
+
`.so` 重编,走 build-android-so.sh + CI + verify-android-so.js 的 16KB
|
|
289
|
+
对齐断言)
|
|
290
|
+
5. ~~**guardian 分发通道**~~ **已裁决不做**(2026-08-07,见 §5.3)——随
|
|
291
|
+
§4 改判 B,整个 guardian 路线关闭;砖机救援能力在第 4 步,不受影响
|
|
292
|
+
|
|
293
|
+
剩余工作只有第 4 步:原生编排。决策层已就位(TS 参照 + C++ 移植 + 向量契约),编排层是纯 IO 胶水。
|
|
184
294
|
|
|
185
295
|
## 9. 已写代码的处置
|
|
186
296
|
|
|
@@ -190,3 +300,123 @@ app 侧的 `client.ts` / `UpdateProvider` 仍然负责**交互**:更新提示
|
|
|
190
300
|
- `autoReset` 遥测事件 —— 无 reset 动作后失去意义,撤回
|
|
191
301
|
|
|
192
302
|
`unconfirmedBoots` / 前台闸门 / `markBootHealthy` / `PROVIDER_REQUIRED` / `CONTENT_APPEARED` 全部撤回。
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 10. 原生编排设计(§8 第 4 步)
|
|
307
|
+
|
|
308
|
+
### 10.1 标定(provisioning)——设计到此才暴露的缺口
|
|
309
|
+
|
|
310
|
+
appKey、server endpoints、更新策略今天只活在 JS 的 `ClientOptions` 里,而原
|
|
311
|
+
生检测跑在冷启动、任何 JS 之前。解决:**JS 是唯一配置源,原生只消费落盘副
|
|
312
|
+
本。** 每次 `setOptions`(含构造)后 JS 调新的原生方法 `syncNativeConfig`,
|
|
313
|
+
持久化:
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
{ appKey, packageVersion, endpoints: server.main, queryUrls,
|
|
317
|
+
afterDownload: 'none' | 'setNeedUpdate',
|
|
318
|
+
disabled?: boolean, rnu, rn }
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
- `afterDownload` 由 updateStrategy 折算:`silentAndLater` / `silentAndNow`
|
|
322
|
+
→ `setNeedUpdate`(原生的作用面本来就是"下次启动");alert 类策略 → 只下
|
|
323
|
+
载不激活,弹窗与确认永远归 JS(§6)
|
|
324
|
+
- **无配置 → 原生静默不跑**。首次安装首启、或从未升级到新 JS 的老接入,天
|
|
325
|
+
然回到现状,零行为变化——这就是灰度开关,不需要另设开关
|
|
326
|
+
- 刻意不做 Info.plist / AndroidManifest 注入:配置双源必然漂移
|
|
327
|
+
- 砖机场景自洽:设备能被坏热更砖掉,说明它至少健康跑过一次 JS,配置早已落盘
|
|
328
|
+
|
|
329
|
+
### 10.2 流程
|
|
330
|
+
|
|
331
|
+
冷启动 + 延迟 5s(R5),后台线程,每次冷启动至多一轮:
|
|
332
|
+
|
|
333
|
+
```text
|
|
334
|
+
读 config(无则退出)+ 原生 state(currentVersion / rolledBackVersion /
|
|
335
|
+
packageVersion / buildTime / uuid / supportedDiffVersion / bundleHash 缓存)
|
|
336
|
+
→ BuildCheckRequestBody(bundleHash 同步读缓存,缺省省略字段)
|
|
337
|
+
→ OrderEndpointCandidates(endpoints, 原生随机数)
|
|
338
|
+
→ 顺序请求,单个 connect/read 超时 10s、whole-call 15s、整轮 HTTP 最多
|
|
339
|
+
8 次;全失败 → 拉 queryUrls
|
|
340
|
+
(任一成功即用)合并新候选,排除已失败的,再顺序一轮;仍失败 → 本轮放弃
|
|
341
|
+
→ HandleCheckResponse(响应原文, identity, isDev=false)(已实现,含 info 透出)
|
|
342
|
+
→ action=download:按 attempts 顺序走现有下载器(diff→pdiff→full,
|
|
343
|
+
testUrls 语义由原生逐个尝试实现);diff/pdiff 共享 600s 绝对 deadline,
|
|
344
|
+
full 另有 600s 救砖预算;同 hash 完整版本在任务真正开跑时再次跳过,失败
|
|
345
|
+
清理不得删除已有 `.pushy-complete` 安装;成功 → setLocalHashInfo(info 的
|
|
346
|
+
name/description/metaInfo) → 按 afterDownload 决定是否 setNeedUpdate
|
|
347
|
+
→ 原生处理完成后,将响应原文 + **响应到达时刻** + 请求/配置指纹落盘
|
|
348
|
+
(§10.3;下载耗时不得让旧响应获得新的时间戳)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 10.3 与 JS 的去重(§6 的落地形态)
|
|
352
|
+
|
|
353
|
+
首版**原生只写缓存**:响应原文 + 响应到达时间戳 + 请求/配置指纹落到固定文件。紧
|
|
354
|
+
随其后的 JS 小改动:`checkUpdate` 先读该缓存,时间戳新鲜(暂定 2 分钟)
|
|
355
|
+
则直接复用不发请求。改造前的过渡期是双检查——多一次网络请求,服务端有
|
|
356
|
+
缓存,无害。
|
|
357
|
+
|
|
358
|
+
### 10.4 失败策略
|
|
359
|
+
|
|
360
|
+
- 整轮静默失败:无重试风暴、无退避状态机,下次冷启动天然重试
|
|
361
|
+
- 下载/patch 失败不拉黑版本、不计数(本地熔断的教训:多记会毁好版本);
|
|
362
|
+
每次启动至多重试一轮,行为有界
|
|
363
|
+
- 不引入任何新的回退/降级路径;apk 过期(expired)响应原生不处理,留给 JS UI
|
|
364
|
+
|
|
365
|
+
### 10.5 安全面
|
|
366
|
+
|
|
367
|
+
与 JS 路径同一协议、同一 HTTPS endpoints、同一下载器 hash 校验,无新增
|
|
368
|
+
面。`flow_json` 解析网络数据已做深度上限 + 畸形输入回归(ASan/UBSan)。
|
|
369
|
+
|
|
370
|
+
### 10.6 分平台落地顺序
|
|
371
|
+
|
|
372
|
+
iOS(NSURLSession,下载/patch/state 全现成,纯增量)→ Android(OkHttp +
|
|
373
|
+
librnupdate.so 进 update_flow_core,绑一次 .so 重编)→ Harmony。e2e 用例
|
|
374
|
+
沿用 Example/e2etest 既有基建,验证"坏 bundle 下原生仍能拉到修复版"的端到
|
|
375
|
+
端场景。
|
|
376
|
+
|
|
377
|
+
### 10.7 forceBoot——策略的按版本远程覆盖(救砖的最后一环,2026-08-08 实现)
|
|
378
|
+
|
|
379
|
+
§10.1 的 `afterDownload` 折算暴露了一个洞:alert 类策略(默认策略)下原生只
|
|
380
|
+
下载不激活,而砖机的 JS 永远不会跑——修复版躺在磁盘上永不生效,救援在它存
|
|
381
|
+
在的理由上失效。
|
|
382
|
+
|
|
383
|
+
解法是把激活决策做成**客户端默认 + 服务端按版本覆盖**:版本 `config` 增加
|
|
384
|
+
`forceBoot: true`(控制台按版本勾选,语义是"强制以该版本启动",不是 UX 层
|
|
385
|
+
面的"静默")。激活谓词收敛为纯层的 `shouldActivateAfterDownload(info,
|
|
386
|
+
afterDownload)`:本地 silent 策略 或 响应标记 forceBoot 即激活。
|
|
387
|
+
`HandleCheckResponse` 增参 `afterDownload` 并在 download 决策中直接给出
|
|
388
|
+
`activate` 布尔——三端编排器各自只读这一个字段,零判断逻辑。
|
|
389
|
+
|
|
390
|
+
刻意的语义边界:
|
|
391
|
+
- **仅作用于原生**。JS 侧交互策略不感知不受影响——健康设备该弹窗还弹窗,
|
|
392
|
+
用户点"取消"只是"这次不切",下次冷启动仍会进入标记版本(原生分不出砖机
|
|
393
|
+
与健康设备,这正是显式标记版本想要的触达)。
|
|
394
|
+
- **本机 `rolledBack` 黑名单赢过 forceBoot**(守卫在谓词之前):本机有崩溃
|
|
395
|
+
证据的版本不会被重装,开发者应重绑到别的版本。
|
|
396
|
+
- **first_time 崩溃保护对强制版本依然生效**:强制启动的版本若也是坏的,
|
|
397
|
+
下次启动照常回滚,不存在"强制进入坏版本且无法回头"。
|
|
398
|
+
|
|
399
|
+
**服务端存储位置的裁决(2026-08-09,推翻初稿):forceBoot 存 `bindings.config`,
|
|
400
|
+
不存 `versions.config`。** 两个 config 的意图沿革必须记清,防止再犯:
|
|
401
|
+
|
|
402
|
+
- **`versions.config` 属旧灰度设计,已弃用且在被主动清洗**。旧设计把
|
|
403
|
+
rollout 存在版本上(`config.rollout[packageVersion]`);新设计把 rollout
|
|
404
|
+
搬到 `bindings.rollout` 列,客户端协议里的 `config.rollout` 形状由服务端
|
|
405
|
+
**从绑定数据合成**。绑定事务里的 `removePackageRolloutConfig` 每次重绑都
|
|
406
|
+
会从 versions.config 清掉对应的 legacy rollout 键——决策层对
|
|
407
|
+
versions.config 刻意不读,响应里的 config 一律合成,这是现行铁律。
|
|
408
|
+
- **初稿曾把 forceBoot 放进 versions.config 并让绑定路径透传它,已否决**:
|
|
409
|
+
透传会把未被清洗的 legacy rollout 连带泄漏回响应、让双 config 源复活、
|
|
410
|
+
与清洗机制逆行。相应改动在四个仓库均已回退。
|
|
411
|
+
- **`bindings.config` 是新设计预留的"这次投放"配置位**(upsert API 全线
|
|
412
|
+
打通、快照本就 select 它),forceBoot 正是投放属性:救砖 = 把包重绑到正
|
|
413
|
+
常版本这一动作。存绑定还带来正确的生命周期——重绑即替换绑定,救援结束
|
|
414
|
+
后标记自动消失,不会像挂在版本上那样永久残留;粒度也收敛到单个
|
|
415
|
+
packageVersion。
|
|
416
|
+
- **客户端协议不变**:客户端仍读响应里 `info.config.forceBoot`,它从哪合成
|
|
417
|
+
客户端不感知。
|
|
418
|
+
|
|
419
|
+
已实施(四仓库,本地提交待推送):pushy-server / cresc-server 决策层从
|
|
420
|
+
`binding.config.forceBoot` 合成进下发 config(灰度与全量两分支),绑定列表
|
|
421
|
+
接口补 select config;pushy-admin / cresc-admin 发布菜单加"全量+强制启动
|
|
422
|
+
(救砖)",已绑定行显示⚡标记 + 切换项(重发同绑定翻转标记)。
|