react-native-update 10.53.1 → 10.54.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 (105) hide show
  1. package/README-CN.md +31 -31
  2. package/README.md +31 -31
  3. package/android/jni/HDiffPatch/.travis.yml +33 -0
  4. package/android/jni/HDiffPatch/builds/xcode/bestParams.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  5. package/android/jni/HDiffPatch/builds/xcode/hdiffz.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  6. package/android/jni/HDiffPatch/builds/xcode/hdiffz.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  7. package/android/jni/HDiffPatch/builds/xcode/hpatchz.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  8. package/android/jni/HDiffPatch/builds/xcode/lz4.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  9. package/android/jni/HDiffPatch/builds/xcode/lzma.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  10. package/android/jni/HDiffPatch/builds/xcode/testHashClash.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  11. package/android/jni/HDiffPatch/builds/xcode/testHashClash.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  12. package/android/jni/HDiffPatch/builds/xcode/unitTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  13. package/android/jni/HDiffPatch/builds/xcode/zstd.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  14. package/android/jni/lzma/DOC/7zC.txt +187 -0
  15. package/android/jni/lzma/DOC/7zFormat.txt +469 -0
  16. package/android/jni/lzma/DOC/Methods.txt +173 -0
  17. package/android/jni/lzma/DOC/installer.txt +166 -0
  18. package/android/jni/lzma/DOC/lzma-history.txt +446 -0
  19. package/android/jni/lzma/DOC/lzma-sdk.txt +357 -0
  20. package/android/jni/lzma/DOC/lzma-specification.txt +1176 -0
  21. package/android/jni/lzma/DOC/lzma.txt +328 -0
  22. package/android/jni/lzma/bin/7zS2.sfx +0 -0
  23. package/android/jni/lzma/bin/7zS2con.sfx +0 -0
  24. package/android/jni/lzma/bin/7zSD.sfx +0 -0
  25. package/android/jni/lzma/bin/7zdec.exe +0 -0
  26. package/android/jni/lzma/bin/7zr.exe +0 -0
  27. package/android/jni/lzma/bin/installer/config.txt +5 -0
  28. package/android/jni/lzma/bin/installer/cr.bat +5 -0
  29. package/android/jni/lzma/bin/lzma.exe +0 -0
  30. package/android/jni/lzma/bin/x64/7zr.exe +0 -0
  31. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  32. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  33. package/android/lib/x86/librnupdate.so +0 -0
  34. package/android/lib/x86_64/librnupdate.so +0 -0
  35. package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +44 -0
  36. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +124 -40
  37. package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +125 -0
  38. package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +43 -11
  39. package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java +2 -0
  40. package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +90 -0
  41. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +79 -19
  42. package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +21 -0
  43. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +33 -2
  44. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +1 -0
  45. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
  46. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
  47. package/cpp/patch_core/archive_limits.h +38 -0
  48. package/cpp/patch_core/install_record.h +36 -0
  49. package/cpp/update_flow_core/flow_json.h +1 -0
  50. package/cpp/update_flow_core/update_flow_core.cpp +41 -7
  51. package/cpp/update_flow_core/update_flow_core.h +9 -0
  52. package/cpp/update_flow_core/update_flow_jni.cpp +12 -0
  53. package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +2 -0
  54. package/harmony/pushy/src/main/cpp/pushy.cpp +37 -0
  55. package/harmony/pushy/src/main/ets/ArchiveLimits.ts +83 -0
  56. package/harmony/pushy/src/main/ets/DownloadTask.ts +126 -30
  57. package/harmony/pushy/src/main/ets/InstallRecord.ts +129 -0
  58. package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +24 -2
  59. package/harmony/pushy/src/main/ets/NativePatchCore.ts +5 -0
  60. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +12 -0
  61. package/harmony/pushy/src/main/ets/UpdateContext.ts +78 -15
  62. package/harmony/pushy.har +0 -0
  63. package/ios/RCTPushy/RCTPushy.mm +384 -25
  64. package/ios/RCTPushy/RCTPushyDownloader.mm +32 -0
  65. package/package.json +25 -2
  66. package/src/NativePushy.ts +13 -0
  67. package/src/client.ts +160 -43
  68. package/src/core.ts +17 -3
  69. package/src/error.ts +3 -0
  70. package/src/index.ts +8 -0
  71. package/src/locales/en.ts +3 -0
  72. package/src/locales/zh.ts +2 -0
  73. package/src/metadata.ts +166 -0
  74. package/src/provider.tsx +52 -17
  75. package/src/type.ts +22 -0
  76. package/src/updateFlowCore.ts +95 -4
  77. package/src/utils.ts +31 -19
  78. package/.gitmodules +0 -6
  79. package/BUNDLEHASH_MIGRATION.md +0 -355
  80. package/NATIVE_CHECKUPDATE_DESIGN.md +0 -539
  81. package/NATIVE_CHECK_FOLLOWUPS.md +0 -514
  82. package/android/jni/HDiffPatch/test/_private_searchBestParams.cpp +0 -301
  83. package/android/jni/HDiffPatch/test/hpatchz_test.cpp +0 -292
  84. package/android/jni/HDiffPatch/test/testHashClash.cpp +0 -436
  85. package/android/jni/HDiffPatch/test/unit_test.cpp +0 -417
  86. package/biome.json +0 -59
  87. package/cpp/patch_core/tests/fixtures/rawstored.lzma2label.patch.bin +0 -0
  88. package/cpp/patch_core/tests/fixtures/rawstored.new.bin +0 -0
  89. package/cpp/patch_core/tests/fixtures/rawstored.old.bin +0 -0
  90. package/cpp/patch_core/tests/fixtures/v96.hbc +0 -0
  91. package/cpp/patch_core/tests/fixtures/v96.meta.json +0 -1
  92. package/cpp/patch_core/tests/fixtures/v96.streampatch.bin +0 -0
  93. package/cpp/patch_core/tests/fixtures/v96.t.hbc +0 -0
  94. package/cpp/patch_core/tests/fixtures/v96.tpatch.bin +0 -0
  95. package/cpp/patch_core/tests/fixtures/v96.tstreampatch.bin +0 -0
  96. package/cpp/patch_core/tests/fixtures/v96b.hbc +0 -0
  97. package/cpp/patch_core/tests/fixtures/v98.hbc +0 -0
  98. package/cpp/patch_core/tests/fixtures/v98.t.hbc +0 -0
  99. package/cpp/patch_core/tests/fixtures/v98b.hbc +0 -0
  100. package/cpp/patch_core/tests/fixtures/v98b.t.hbc +0 -0
  101. package/cpp/patch_core/tests/hbc_transform_test.cpp +0 -384
  102. package/cpp/patch_core/tests/patch_core_test.cpp +0 -970
  103. package/cpp/update_flow_core/tests/flow_vectors.json +0 -1401
  104. package/cpp/update_flow_core/tests/update_flow_core_test.cpp +0 -317
  105. package/scripts/check-expo-version.js +0 -92
@@ -1,514 +0,0 @@
1
- # 原生冷启动检测:遗留改进项
2
-
3
- > 来源:`agent/harden-native-check-update` 分支两轮评审
4
- > (f679e11 初评 10 项 → 181952a 修 4 缓解 1;181952a 复评又出 10 项,
5
- > 其中 6 项为修复自身引入,已并入下文)。
6
- > 本文最初只记录开放项。2026-08-10 已完成代码项复核与修复;下文保留
7
- > 原始问题描述用于追溯,处理结论以紧随其后的状态表为准。发版动作仍保留在
8
- > 文末清单中,不因代码合入而自动视为完成。
9
-
10
- ## 2026-08-10 处理结论
11
-
12
- | 项目 | 结论 | 落地方式 |
13
- |---|---|---|
14
- | P1 Android/Harmony 重复下载 | 已修复 | 任务真正开始时复查 `.pushy-complete` + bundle;失败清理检测到完整安装时不删目录。iOS 注册表同时升级为按 hash + artifact type 合流/排队 |
15
- | P2 缓存 `ts` 锚点 | 已修复 | 三端在 check 响应到达时捕获时间,下载、补丁和激活结束后仍写该时间 |
16
- | P2 下载轮次 deadline | 已修复 | 三端统一为 diff/pdiff 共享 600s、full 独享 600s;发起前检查绝对 deadline,下载任务在真正开跑时按剩余预算设置 whole-call timeout |
17
- | P2 JS 配置回退竞态 | 已修复 | 在途写入期间仍记录与 `synced` 相同的最新期望值;B 完成后会继续把 A 写回;删除同步 throw 路径的无效递归 |
18
- | P2 iOS 合流语义 | 已修复 | 同类型下载共享结果并向所有 join 者广播进度;不同类型按 hash 排队,避免把 diff 的失败错误归给 full |
19
- | P3 `.pushy-complete` 迁移 | 接受一次性重下 | 不把仅有 bundle 的旧目录推断为完整安装,避免把半解压目录误标为成功;10.51.0 发布说明仍需明确这一流量成本 |
20
- | P3 状态解析异常不调度 | 已修复 | 三端启动解析改为 finally 调度;正常路径保留最终 rollback 快照,异常路径使用空快照 |
21
- | P3 无 hash 死弹窗 | 已修复 | Provider 将无 hash 的 update 降级为开发者日志/遥测与 `upToDate`,不再展示不可执行的确认按钮 |
22
- | P3 `noArtifact` 静默 | 已修复 | 保持终端用户无感,同时恢复 `errorUpdate` 报告并携带目标 hash |
23
- | P4 bundleHash 窗口 | 接受并记录 | `readNativeCheckCache` 已注明首启预取未完成时有意 cache miss,不为 hash 阻塞检查 |
24
- | P4 缓存冗余解析 | 已修复 | 直接传入现成 `fetchBody`/native config 对象,只解析缓存中的字符串一侧 |
25
- | P4 endpoint 斜杠重复 | 已修复 | 三端首轮请求也先查规范化后的 `tried` 集合,重复项不计入 8 次上限 |
26
- | P4 Harmony 整请求超时 | 已修复 | check HTTP 增加 15s whole-call cap;更新下载增加绝对 deadline 并在超时后销毁请求 |
27
-
28
- 代码验证基线:JS 完整回归 173 项、Biome/TypeScript/Harmony strict 类型检查、
29
- 77 项 flow core ASan/UBSan、29 项 patch core、Harmony debug HAR、Android
30
- Release Java 编译、iOS Release simulator 静态库构建均通过。
31
-
32
- ---
33
-
34
- ## 2026-08-10 第三轮评审(283dfd4)开放项
35
-
36
- 上表 13 项的修复经复评确认全部属实;以下为修复自身引入/暴露的新开放项
37
- (详情与逐条修法见评审面板)。
38
-
39
- 复核后处理结论:
40
-
41
- | 项 | 结论 | 落地方式 |
42
- |---|---|---|
43
- | 1 iOS joiner 预算 | 已修复 | 注册表记录 owner 的单调时钟 deadline;预算更长的 waiter 观察当前进度但 deferred,owner 成功时由完成标记立即命中,失败时以自己的完整预算重启 |
44
- | 2 Harmony 外层时限 | 已修复 | `performAttempts` 用绝对单调 deadline 包住排队、HTTP、解压和 hpatch 的完整 Promise;底层串行任务即使晚结束也不再阻止编排器落响应缓存 |
45
- | 3 壁钟 deadline | 已修复并修正文档结论 | iOS 改用 `systemUptime`,Harmony 改用 `systemDateTime.getUptime`;full 预算进入 full 阶段才创建,因此原文“增量阶段校时会同时耗尽尚未创建的 full 预算”不成立 |
46
- | 4 Android full 判定 | 已修复 | 与分发逻辑及 iOS/Harmony 一致,非 diff/pdiff 统一视为 full;上游当前只生成三种合法类型,此项属于防御性收口 |
47
- | 5 坏发布遥测膨胀 | 已修复 | 缺 hash 与 noArtifact 共用按 appKey/reason/hash 的进程内去重,保留一次服务端可见的坏发布信号 |
48
- | 6 有 hash 无产物弹窗 | 已修复 | Provider 在展示/静默下载前复用 `decideDownload`,noArtifact 降级为 `upToDate` 与一次开发者遥测 |
49
- | 7 deferred UX/deadline | 已修复 | deferred waiter 订阅当前同 hash 进度;旧 deadline 在重新注册前校验,过期的编排器请求不会成为 owner 或结算后来的 JS 请求 |
50
- | 8 owner-only 进度事件 | 不采纳 | 支持路径在 JS 已按 hash 维持单一原生监听;常见 join 是无监听器的冷启动 engine + JS bridge。限制为 owner 发事件会在 engine 先成为 owner 时丢失 JS 进度 |
51
- | 9 iOS 完成判定重复 | 已修复 | 抽取 `PushyHasCompletedVersionAtPath`,预检与冷启动编排器共用同一 bundle+marker 判定 |
52
-
53
- **P2(三端时限模型的二阶问题,建议一并收口)**
54
- 1. **iOS 合流者继承 owner 剩余预算**:JS 同 hash 同类型合流到编排器下载时,
55
- 共享会话带的是编排器所剩阶段预算(可能只剩几十秒),其超时会结算全部
56
- 合流者——修改前 JS 独立下载固定 600s 的不变量对合流路径失效。
57
- 2. **Harmony await 无外层时限**:统一预算只管住 HTTP 流;解压/hpatch 卡死
58
- 或串行链被长任务占用时 `await context.downloadX` 永不返回,救援轮次挂满
59
- 进程生命周期,响应缓存也不落盘。修法:performAttempts 层对每个 await 包
60
- Promise.race 绝对 deadline。
61
- 3. **壁钟 vs 单调钟**:iOS/Harmony 的 deadline 锚壁钟(epoch/Date.now),
62
- Android 锚 nanoTime——首次联网触发 NTP 前跳(恰是救援检测时刻)会让
63
- iOS/Harmony 全部预算(含 full 保底)瞬时过期。修法:iOS 用
64
- systemUptime/mach 时基,Harmony 用相对定时器。
65
-
66
- **P3**
67
- 4. **Android 按字面 "full" 判保底预算**,分发 else 分支却把任意非 diff/pdiff
68
- 类型当 full 下载;iOS/Harmony 用排除法——三端对"谁享有 full 保底"不再
69
- 一致,Android 改排除法即齐。
70
- 5. **noArtifact 遥测重复膨胀**:映射到 download_fail 聚合且每次
71
- downloadUpdate 调用都上报(静默策略下每个检查周期一条),需按 hash
72
- 会话内去重或换不入聚合的事件类型。
73
- 6. **有 hash 无产物的死弹窗变体**:provider 只降级了无 hash 的 update;
74
- 有 hash 但无任何产物 URL 的坏发布仍弹确认框、确认后静默。
75
- 7. **iOS deferred 的 UX 与陈旧 deadline**:异类型 deferred 请求无进度、
76
- 延迟开始(UI 冻结);重启时携带编排器的旧 deadlineAt,排队期间过期则
77
- 瞬时失败并连带结算 JS 合流者。JS 发起的请求不应携带编排器 deadline。
78
-
79
- **P4**
80
- 8. **iOS 合流者进度块重复发事件**(N 合流者 = N 倍 RN 事件,事件发送应只由
81
- owner 承担)。
82
- 9. **完成判定(bundle+marker)在 iOS 两处手写**,抽 PushyHasCompletedVersion
83
- helper 防漂移。
84
-
85
-
86
- ---
87
-
88
- ## P1 — Android/Harmony 重复下载竞态,失败路径可删除已安装版本
89
-
90
- **现状**:iOS 在 f679e11 引入了进程级在途下载注册表
91
- (`PushyRegisterDownload`/`PushyFinishDownload`),同一 hash 的并发下载合流。
92
- Android 与 Harmony 只有任务串行化,没有按 hash 去重,也不保护已完成目录。
93
-
94
- **风险**:JS 自动下载版本 H 进行中(单线程 executor 排队),原生检测 5 秒后
95
- `hasCompletedVersion(H)` 为 false,再排入第二个 H 的下载任务。JS 任务完成、
96
- 写入 `.pushy-complete` 并 `setNeedUpdate(H)` 后,排队的重复任务重新下载 H
97
- 且中途失败(断网)时,`cleanUpAfterFailure` 删除 `rootDir/H`(含
98
- `index.bundlejs` 与标记)。下次冷启动解析到 H 但 bundle 缺失 → 回滚——
99
- **已成功安装的更新被静默丢失**。
100
-
101
- **建议修法**(三选一,或组合):
102
- 1. 移植 iOS 的按 hash 在途注册表(Android 用
103
- `ConcurrentHashMap<String, List<Listener>>`,Harmony 用模块级 Map);
104
- 2. 更小的止血:`cleanUpAfterFailure` 里若目标目录已有
105
- `.pushy-complete` 标记则不删(失败的是重复下载,不是这份安装);
106
- 3. DownloadTask 开跑前再查一次 `hasCompletedVersion(hash)`,已完成即直接
107
- 走成功回调。
108
- 方案 2+3 组合改动最小且互补;方案 1 语义最完整(还能省一次重复下载)。
109
-
110
- ---
111
-
112
- ## P2 — 响应缓存的 `ts` 锚在持久化时刻,而非响应到达时刻
113
-
114
- **现状**:`persistResponseCache` 在原生下载/激活全部结束后才落盘,`ts` 取
115
- 当时时间。181952a 给 Android 加了整轮 600s deadline,把最坏偏移压到
116
- ~12 分钟,但锚点本身未改;iOS 仍是逐 URL 600s、Harmony 依赖下载任务自身
117
- 超时,无整轮上限。
118
-
119
- **风险**:版本发布几分钟后被运营撤下/回滚时,一个"响应早已过时但 ts 很新"
120
- 的缓存会让 JS 在 120s 窗口内把已撤回的响应当新鲜结果复用,继续下载/激活
121
- 已撤回版本。
122
-
123
- **建议修法**:响应到达时捕获 `responseAtSeconds`,作为参数传入
124
- `persistResponseCache` 写进 `ts`(三端各一行改动,JS 读侧无需变)。
125
-
126
- **复评补充(181952a 引入/暴露)——下载轮次时限应作为一个整体设计统一三端**:
127
- 1. 整轮 600s deadline 只落了 Android;iOS 仍逐 URL 600s(上界
128
- attempts×urls×600s,可达 90 分钟),Harmony 完全没有(慢滴 CDN 在每个
129
- 60 秒窗口内返回至少一个字节即可绕过下载任务的不活动看门狗,轮次可跑数小时);
130
- 2. Android 的 deadline 在下一个下载**已发起后**才检查——超时轮次仍会
131
- 多启动一个孤儿下载,浪费带宽且可能与 JS 重试并发写版本文件
132
- (修法:发起前先查);
133
- 3. 整轮预算会被 diff/pdiff 的失败耗尽,挤压救砖最后手段 full 的时间
134
- (原先 full 独享 600s)——建议 full 单独保底额度。
135
-
136
- ---
137
-
138
- ## P2 — JS 配置同步的回退竞态(181952a 引入)
139
-
140
- **现状**:`syncNativeConfig` 的去重把当前配置与**最后一次已完成**的写入
141
- (`syncedNativeConfigJson`)比较。
142
-
143
- **风险**:已同步 A → setOptions 改为 B(写入在途)→ B 完成前又回退为 A:
144
- 回退因等于 `synced`(仍是 A)被直接丢弃,随后 B 完成、`synced=B`、pending
145
- 为空——原生 KV 永久持有错误的策略/endpoints(如 app 已切回 alert 却按
146
- silent 激活),直到未来某次不同值的 setOptions 才被纠正。
147
-
148
- **建议修法**(改动极小):去重时把在途/待写值一并纳入比较,或写入完成回调里
149
- 与"最新期望值"复核不符则重新入队。顺手删掉 `flushNativeConfig` 同步抛
150
- catch 里的死代码递归"重试"(pending 已清空,必然早退,误导维护者)。
151
-
152
- ---
153
-
154
- ## P2 — iOS 合流下载的两个语义代价
155
-
156
- **现状**:JS 的下载调用合流到冷启动引擎的在途下载后:
157
- 1. **收不到进度事件**——事件只由持有下载的实例发出,引擎实例
158
- `hasListeners=NO`,用户看到进度条冻在 0;
159
- 2. **继承异类 attempt 的失败**——引擎的 diff 尝试 404 时,JS 本可成功的
160
- full 下载调用被判失败(JS 策略链会继续下一策略+重试回退,最终多能成功,
161
- 但单次调用的失败归因是错的)。
162
-
163
- **建议修法**:注册表回调列表旁存进度回调,引擎下载的 NSURLSession 进度
164
- 透传给所有 join 者(解决 1);join 时携带请求类型,类型不同不合流、改为
165
- 排队串行(解决 2,代价是偶发的一次串行等待)。若认为 JS 策略链的自愈已
166
- 足够,可显式拍板只修 1。
167
-
168
- ---
169
-
170
- ## P3 — `.pushy-complete` 无迁移,存量已下载版本升级后重下一次
171
-
172
- **现状**:旧版本库下载的版本目录没有标记文件;升级到新库后
173
- `hasCompletedVersion` 判 false,原生检测整包重下一遍(下载完成后写标记,
174
- 自愈,每台受影响设备一次)。
175
-
176
- **风险**:一次性流量成本,仅影响"已下载未激活"(alert 类策略)的存量设备。
177
-
178
- **建议**:二选一显式拍板——(a) 接受成本,在 CHANGELOG 注明;(b) 加迁移:
179
- 首次运行时对"有 `index.bundlejs` 且 mtime 早于本次库安装"的目录补写标记
180
- (无法区分半解压目录,有误判风险,故 (a) 可能更稳)。
181
-
182
- ---
183
-
184
- ## P3 — 启动状态解析抛异常时,救援检测永不调度
185
-
186
- **现状**:f679e11 把 `schedule` 从 `getBundleUrl`/`+bundleURL` 顶部移到
187
- 各出口(为携带回滚快照),但若 `runStateCore`/回滚循环自身抛异常,所有
188
- 出口都到不了。
189
-
190
- **风险**:持久化状态损坏(恰是救援机制存在的场景)时,救援检测在后续所有
191
- 启动中都不运行。
192
-
193
- **建议修法**:状态解析段包 try/finally,finally 里以"空快照"调度
194
- (rolledBackVersion 传 null 即可,宁可少守卫不可不调度——与"判定失败不
195
- 计数"同一取舍方向);三端同改。
196
-
197
- ---
198
-
199
- ## P3 — 无 hash 的灰度条目从"静默忽略"变成"死弹窗"(181952a 语义变更的副作用)
200
-
201
- **现状**:`resolveCheckResult` 的非空 hash 守卫移除了旧的
202
- `undefined === undefined` 等价:服务端误配出无 hash 的灰度条目时,内置包
203
- 设备(currentVersion 为空)从静默 upToDate 变为返回 `update:true` 且无
204
- hash——已被再生成的金标向量固化。
205
-
206
- **风险**:alert 策略下每次检查都弹"发现新版本",点确认后 `decideDownload`
207
- 因 `!hash` 判 noUpdate——死弹窗,按钮无效,每次检查重现。
208
-
209
- **建议修法**:若新语义有意(暴露服务端错配),在 provider 层把无 hash 的
210
- update 降级为日志 + 遥测而非弹窗;若无意,恢复"无 hash 条目不视为可更新"。
211
- 与下一条(noArtifact)同属"坏发布该以遥测暴露而非 UI 弹窗/静默"。
212
-
213
- ---
214
-
215
- ## P3 — `noArtifact` 分支静默返回,丢失 errorUpdate 遥测(181952a 引入)
216
-
217
- **现状**:decideDownload 新增的 noArtifact 拒绝让 `downloadUpdate` 无日志、
218
- 无错误事件、无遥测地返回;旧的空 attempts 路径会上报
219
- `{type:'errorUpdate'}`。
220
-
221
- **风险**:服务端发出 update:true 有 hash 但无任何产物 URL 的坏发布,从
222
- "控制台可见"退化为"客户端静默无事发生",坏发布隐形。
223
-
224
- **建议修法**:noArtifact 分支恢复 report({type:'errorUpdate', ...}),保持
225
- 用户无感但平台可见。
226
-
227
- ---
228
-
229
- ## P4 — bundleHash 未就绪窗口的请求指纹失配(记录即可)
230
-
231
- **现状**:JS 预取 bundleHash 未 settle 时构造的请求体缺 `bundleHash` 键,
232
- 与原生缓存的请求(原生同步计算,总带该键)键集不同 → 指纹不命中,走网络。
233
- 窗口极窄(通常仅首启,且该时刻缓存多半尚不存在);`overridePackageVersion`
234
- 的失配已在 181952a 通过配置携带 `packageVersion` 修复。
235
-
236
- **建议**:在 `readNativeCheckCache` 注释里写明这是已知且有意的失配面,
237
- 不做代码改动。
238
-
239
- ---
240
-
241
- ## P4 — `readNativeCheckCache` 冗余解析(cleanup)
242
-
243
- **现状**:调用方刚 stringify 的请求体被立即 parse 回来做结构比较,外加对
244
- 当前 config JSON 的一次重复解析——每次 release 路径 checkUpdate 的固定
245
- 开销。
246
-
247
- **建议**:把现成的 `fetchBody` 对象传入,只 parse `entry.request` 一侧。
248
-
249
- ---
250
-
251
- ## P4 — 若干小项(复评新增)
252
-
253
- - **斜杠变体 endpoint 重复请求**:尾斜杠归一化发生在 C++ 去重之后,且首轮
254
- 循环只 add 不查 `tried`——`https://u.example.com` 与 `…com/` 会对同一
255
- URL POST 两次,还白占 8 次 HTTP 上限中的两次。修法:归一化提前进纯层
256
- (`orderEndpointCandidates` 前),或首轮也查 `tried`。
257
- - **Harmony 检查请求缺整请求封顶**:只有 connect/read 超时,没有 Android
258
- `callTimeout(15s)` / iOS 超时取消的对应物,慢滴响应可拖长每次尝试。
259
-
260
- ---
261
-
262
- ## 2026-08-11 第四轮评审(60f5fd2..5f5d0cb)开放项
263
-
264
- 上一轮 9 项闭环全部属实(第 8 项"owner-only 进度事件"维护者明确不采纳,理由
265
- 成立)。以下为仍开放项;**前三条建议合入前处理**,其余可随小版本。
266
-
267
- ### 合并阻断 —— 已于 2026-08-11 修复(见下方"修复说明")
268
-
269
- #### 原始问题描述
270
-
271
- 1. **原生检测无视 `checkStrategy`,并能撞销 `resetToPackagedBundle`**
272
- (CI 已红)。`getNativeConfig` 只用 `updateStrategy` 折算 `afterDownload`,
273
- 从不读 `checkStrategy`;三端也没有 reset↔检测 的任何联动(无 generation、
274
- 无取消,reset 也不清 `nativeCheckResp`)。e2e app 明写 `checkStrategy: null`
275
- 却仍被原生自动下载+激活;`resetToPackagedBundle` 可被在飞的检测撤销。
276
- **证据**:e2e-ios 在 `0f4651e` 与 `5f5d0cb` 两次运行均挂在 `beforeEach`
277
- 的 `bundleLabel: BINARY_BASE`,且两次挂的是不同用例(竞态签名);失败态
278
- `currentHash: e2e-full-v1` 恰是"reset 后从零检查会拿到的第一个版本";
279
- master 上 e2e-ios 为绿 → 本分支引入的回归。本分支新增的
280
- `hasCompletedVersion` 快路径(版本已落盘则跳过下载直接 switchVersion)
281
- 把竞态窗口从"下载完"压到"5 秒后瞬间",是这轮才炸的原因。
282
- **修法**:`afterDownload` 计算纳入 `checkStrategy`(为 null 时降为
283
- `'none'`——只下载不激活,`forceBoot` 仍可救砖);`resetToPackagedBundle`
284
- bump 进程级 generation,编排器在 `switchVersion`/落缓存前比对,变了就
285
- 放弃,并清掉响应缓存。
286
-
287
- 2. **iOS 同类型 join 分支实际不可达**。`deadlineUptime > ownerDeadline` 是
288
- 严格大于,而 JS 发起的请求总晚于 owner 的计算时刻,因此永远走 deferred:
289
- P1"一个 hash 共享一次下载"的意图失效,退化为串行重下;CDN 黑洞时用户
290
- 弹窗可转近 20 分钟(改前是合流后一次失败并回退下一候选 URL)。修法:比较
291
- 加容忍阈值,或 JS 发起的请求不参与 deadline 比较。
292
-
293
- 3. **Harmony 用 `TimeType.STARTUP`**(计深度睡眠),而 iOS `systemUptime` /
294
- Android `nanoTime` 睡眠时停走——"三端统一单调钟"不成立。锁屏休眠数分钟
295
- 即让预算过期、救援中止,同网络的另两端能续传完成。一行改
296
- `TimeType.ACTIVE`。
297
-
298
- #### 修复说明(2026-08-11)
299
-
300
- 1. **checkStrategy + reset 竞态**:`getNativeConfig` 的 `afterDownload` 现在
301
- 要求 `checkStrategy != null` —— 关掉自动检查的应用不会再被塞一次它没要
302
- 过的版本切换;检测本身照跑(救砖能力不变),`forceBoot` 仍可激活。另加
303
- **reset 代数守卫**:`resetToPackagedBundle` 递增进程级计数并清掉响应缓存,
304
- 编排器在开跑前采样、在激活与落缓存前比对,不一致即整轮丢弃。三端同构
305
- (iOS `std::atomic<uint64_t>`、Android `AtomicLong`、Harmony 静态计数)。
306
- 2. **iOS join 分支不可达**:比较从"绝对 deadline 严格大于"改为"剩余预算",
307
- 仅当 owner 剩余不足新来者的一半时才 defer —— 正常情况(JS 晚几秒发起)
308
- 恢复合流,只有真正濒临耗尽的 owner 才让位。
309
- 3. **Harmony 单调钟**:`TimeType.STARTUP` → `TimeType.ACTIVE`,与 iOS
310
- `systemUptime` / Android `nanoTime` 的"睡眠时停走"语义对齐。
311
-
312
- **补强(2026-08-11,CodeRabbit 复核后)**:初版守卫是 compare-and-act,且
313
- `hash_<hash>` 元信息写在守卫之前——reset 若落在"比对通过"与"写入"之间仍
314
- 可被覆盖。现改为**一次原子提交**:版本元信息 + 激活 + 响应缓存合并为
315
- `commitNativeCheckResult(expectedGeneration, ...)`,内部先复核代数再落全部
316
- 写入;`resetToPackagedBundle` 与之互斥并**先失效代数再清状态**。互斥手段按
317
- 端选取:iOS 复用既有 `PushyWithStateLock`(为此把 switchVersion 拆出无锁核
318
- `PushySwitchVersionLocked`,避免不可重入死锁)、Android 用共享
319
- `commitLock`、Harmony 是 ArkTS 单线程且提交内无 await,天然原子(已注释说
320
- 明)。三处落点(元信息/激活/缓存)与三个调用点(nothing-to-do、未下载、成功)
321
- 全部走同一入口。
322
-
323
- 验证:JS 178 项(新增 2 项 checkStrategy 折算用例)、Biome/tsc/DevEco
324
- strict、77 金标向量 + ASan/UBSan、`.so` 符号与 16KB 对齐、iOS clang
325
- (DEBUG=0/1)、Android javac(main+oldarch)、OHOS 工具链语法。本轮不触
326
- `cpp/`,无需重生成向量或重编 `.so`。
327
-
328
- ### 后续小版本
329
-
330
- 4. Harmony 超时只放弃编排器的 `await`,卡死的 DownloadTask 仍占 `taskChain`,
331
- 后续 attempt 只是排队并空烧自己的新鲜预算(有效 full 产物 + 完整预算都在
332
- 却仍救不回)。修法:超时真正取消底层任务,或救援轮次用独立链。
333
- 5. iOS/Harmony 的 `switchVersion` 仍可激活半完成安装(Android 本轮加了无标记
334
- 拒绝守卫,三端 parity 分歧)。
335
- 6. iOS deferred 异类型 waiter 订阅了 owner 的进度流,收到的是另一种产物的
336
- 字节数(进度条先到 100% 再回 0%)。修法:只向同类型合流者广播。
337
- 7. 缺 hash 守卫新加的 `!info.expired` 让 `{expired:true, update:true}` 且无
338
- hash 的畸形响应既不降级也不上报,却仍把 `update:true` 发给业务侧。
339
- 8. `getNativeConfigJson() ?? '{"disabled":true}'` 会把瞬时为空的
340
- `appKey`/`server.main` 变成持久 disabled 写入,覆盖上一份可用配置并长期
341
- 关掉救砖能力。修法:仅在确实不可用(web/旧原生)时写 disabled。
342
- 9. `noArtifact` 上报位置从 `client.downloadUpdate`(实际尝试下载)移到
343
- `provider.checkUpdate`(只要检查就发),映射到服务端 `download_fail` 聚合
344
- 后会把一次坏发布放大成全量设备级的健康下降。
345
- 10. 两处 cleanup:Android `switchVersion` 重写了同文件已有的
346
- `hasCompletedVersion` 谓词;provider 为一个是非问题重跑完整
347
- `decideDownload`(分配 URL 计划后丢弃)。
348
-
349
- ### 累计
350
-
351
- 四轮共 40 条发现:27 修复、2 显式接受、1 不采纳、10 开放(其中 3 条建议合入
352
- 前处理)。
353
-
354
- ---
355
-
356
- ## 发版清单(非代码缺陷,勿遗漏)
357
-
358
- - [ ] e2e:坏 bundle → 原生拉修复版 → 下次启动复活的端到端用例
359
- (沿用 Example/e2etest 基建;harmony 走 hdc/uitest 链路)
360
- - [ ] README / README-CN / CHANGELOG:原生冷启动检测、forceBoot、
361
- 绑定依赖硬校验(RN 版本一致、rnu 不降级)
362
- - [ ] 客户端 10.51.0 发版(admin 的 forceBoot 门槛与之对齐)
363
- - [ ] 服务端已推送未激活:pushy 主机 `install-npm-release.sh` +
364
- `service-cli.sh restart all`(验 serverVersion);cresc 跑
365
- `update-cloudrun.sh`;两个 admin 走各自 CI
366
-
367
- ## 2026-08-12 救砖实测:能救什么、救不了什么(Android 真机 + 生产服务端)
368
-
369
- 两轮实测,同一套 forceBoot 通道,结论截然相反,差别只在崩溃时刻。
370
-
371
- **能救(已验证)**:markSuccess 之后 10s 才崩的砖。
372
- 时间线:proc_start → 9.4s 原生检测 commit `set for next launch` → 10.0s JS 崩溃 →
373
- 下次启动进入修复版并 markSuccess。救援仅领先崩溃点 567ms。
374
-
375
- **救不了(已验证)**:markSuccess 后写 storage 标记、下次启动读到标记即崩的砖
376
- (用户提出的理想场景,崩溃点约 proc_start+230ms)。连续 4 次冷启动,原生检测
377
- 一次都没能走到 commit,设备停留在 "keeps stopping"。
378
-
379
- **根因(不是延迟参数问题)**:5s 固定延迟只是其一,检查+下载本身实测还需 ~4.4s。
380
- 即使把延迟归零也远超 230ms。更本质的是原生检测线程与 JS 同进程,JS 崩溃时
381
- 进程整体被杀,该轮下载进度全部丢弃,下次启动从零开始——无论重启多少次都无法收敛。
382
-
383
- **可行方向(未实施,需三端评估)**:
384
- 1. 下载断点续传 + 跨启动累积:每次启动补几百毫秒,多次启动后凑齐并激活(治本,改动最大)
385
- 2. 原生检测移入独立进程:JS 崩溃不再连坐(Android 可行;iOS 无此模型)
386
- 3. 上次启动异常检测 → 本次启动同步阻塞式检查(牺牲启动速度换救援,需谨慎)
387
-
388
- 在此之前,产品口径应明确:forceBoot 救的是"能活过启动阶段、之后才出问题"的砖,
389
- 不覆盖"启动即刻崩"的场景。文档与卖点文案不应过度承诺。
390
-
391
- ## 2026-08-12 缺口闭环(10.52.0):crash-handler hold + 断点续传
392
-
393
- 上面三个方向重新评审后全部改判,最终方案见设计文档 §11:
394
-
395
- - **主机制 = 崩溃时刻扣留进程**(维护者提出):uncaught handler 里 hold 住
396
- 垂死进程数秒完成检查+下载。崩溃即信号,零误判、零检测态、首崩即救,
397
- 且修复在垂死会话末尾 commit,下次启动零阻塞直接进修复版。方向 2(独立
398
- 进程)与方向 3(启动阻塞)被它整体取代;方向 1(续传)保留为地基。
399
- - **断点续传三端落地**:sidecar(url/验证器/总长)+ Range/If-Range,
400
- 206/200/416 全处理;归档与 sidecar 同生共死;下载阶段失败保留 partial,
401
- patch 阶段失败判定归档有毒连 sidecar 一起删。
402
- - **round-incomplete 标记**:轮次开始落盘、结束清除;残留 = 上个进程死于
403
- 轮中,下次启动跳过 5s 延迟立即续传。
404
- - **救援轮强制激活** + hashInfo `crashRescue` 标记(与 forceBootRescue 对
405
- 称);本机 rolledBack 黑名单与 first_time 保护语义不变。
406
- - **边界**:只逮 exception 形态崩溃(native signal/ANR/OOM 逃逸);鸿蒙本
407
- 版无 crash hold(errorManager 同步回调驱动不了 ArkTS 异步 IO),只有续
408
- 传+零延迟。
409
-
410
- - [x] 10.52.0 发版后按本节上方同一实验复测 crash-on-launch 砖(见下)
411
-
412
- ### 2026-08-12 实测:crash-on-launch 砖被救回(Android 真机 emu + 生产服务端)
413
-
414
- 10.52.0 发布到 npm,testHotUpdate 升级并出 release APK(rnu 10.52.0)。
415
- 复刻 crash-on-launch 砖:砖包活过一次启动并 markSuccess(first_time 保护
416
- 解除),之后每次启动读到标记即在 JS 线程抛错。fix 版本绑定到同一原生版本
417
- 1.87.1,砖包磁盘上的 fix 预下载被删除,逼救援在崩溃窗口内完整走
418
- 检查+下载+commit。
419
-
420
- 冷启动时序(同一进程 PID 11498):
421
-
422
- ```
423
- 22:59:52.365 JS 线程抛 BRICK(mqt_v_native),进程存活 122ms
424
- 22:59:52.367 crash rescue: holding process for up to 10000ms (uptime 122ms)
425
- 22:59:56.645 native check: downloaded -IxhPnHy(fix) and set for next launch
426
- 22:59:56.651 前任 handler 接管,进程被 SIG9 终止
427
- ```
428
-
429
- 崩溃到 commit 全程 **4.28s**,落在 10s(后台线程)预算内。下次冷启动直接
430
- 进 fix 版本(hot-fix-1052),不再崩溃;fix 立即把标记 disarm。
431
-
432
- 遥测闭环:fix 的 currentVersionInfo 带 `crashRescue: true`,markSuccess 后
433
- JS 紧跟着上报一条 `crashRescue` 回执(logcat 实测 23:00:16.180 markSuccess
434
- → 23:00:16.181 crashRescue)。
435
-
436
- 对比 2026-08-12 上午同一台设备的旧结论:230ms 砖连续 4 次冷启动不收敛。
437
- 10.52.0 下**一次崩溃即救回**——崩溃本身触发 hold,进度不再随进程死亡丢弃。
438
- 鸿蒙真机复测待补(只有续传+零延迟无 hold)。
439
-
440
- ### 2026-08-12 iOS 模拟器实测:同一场景救回(Release-iphonesimulator + 本地服务端)
441
-
442
- e2e 基建复用:本地 rnu 副本刷到 10.52.0 源码 → pod install → Release 模拟器
443
- 构建(e2e-ios 的常态形态,`#if !DEBUG` 门控激活);Detox 逮不住 JS 起不来的
444
- app,绕开它直接 simctl + 文件系统/崩溃报告观察。实验脚手架已入库:
445
- `e2e/entry.brick.ts` / `e2e/entry.fix.ts` / `scripts/ios-rescue-server.ts`
446
- (端口 31337,链:base→brick(带 forceBoot 投放)→fix(**刻意不带 forceBoot**))。
447
-
448
- 砖状态与 Android 同构:brick 经 forceBoot 装上、首启健康 markSuccess
449
- (isFirstTime 消费、isFirstLoadOK=true)、8s 后 arm 标记;磁盘上预下载的
450
- fix 版本目录删除,逼救援窗口真下载。
451
-
452
- armed 冷启动结果(procLaunch 23:24:44.233,崩溃报告 .ips 为证):
453
-
454
- - JS fatal 重抛在 **com.meta.react.turbomodulemanager.queue**(后台线程 →
455
- 10s 预算档;此前对"JS 崩落在哪个线程"的分析在 bridgeless 下得到证实)
456
- - 救援窗口内完成整轮:服务端记录到 checkUpdate(hash=brick)→ 下发 fix →
457
- **fix.ppk 真实下载** → commit;本地服务端下全程 <0.9s(captureTime
458
- 23:24:45.156,远低于预算——4.4s 是生产网络的数字)
459
- - **强制激活的独立证据**:fix 响应无 forceBoot、app 配置
460
- checkStrategy:null/afterDownload:none——常规路径绝不会激活,而
461
- currentVersion 已切到 fix 且 hashInfo 带 `crashRescue:true`,只能来自
462
- crashRescueActive
463
- - 链式礼仪:hold 结束后前任 handler 照常执行,SIGABRT 崩溃报告正常生成
464
- (崩溃上报不丢)
465
- - 下次启动进 fix、disarm 标记、markSuccess
466
-
467
- **主线程 3.5s 预算档不做人工实测,理由记录**:iOS 上落主线程的 RCTFatal 只有
468
- bundle 加载失败一族(RCTInstance 先 RCTExecuteOnMainQueue 再 RCTFatal),而
469
- 加载失败的包永远跑不到 markSuccess,一律被既有 first_time 回滚接住,不构成
470
- 真实砖形态;JS 运行时错误(砖的实际形态)实证落在后台线程。另:主线程档
471
- 3.5s 是按 Android ANR 定的,iOS watchdog 上限 ~20s,未来可放宽。
472
-
473
- **iOS 覆盖边界补记**:若宿主 app 调用了 `RCTSetFatalHandler`,RCTFatal 走
474
- handler 分支不再抛异常,crash-rescue 不会触发(Android 无对应抢占点)。
475
- 文档口径需含此项。
476
-
477
- ## 2026-08-13 鸿蒙模拟器实测:两个实证 + 一个 10.51 以来的功能性断裂
478
-
479
- DevEco api20 模拟器(HarmonyOS 6.0 / arm64)+ harmony_use_pushy(RN 0.72 +
480
- RNOH 0.72.96)+ 本地 e2e 服务端。探针脚手架入库:
481
- `harmony_use_pushy/e2e/entry.crashprobe.ts`(+1s 未捕获 JS 错误)、
482
- `entry.diag.ts`(TM 方法表诊断)。
483
-
484
- **实证一(缺口 0 的分叉答案):RNOH 未捕获 JS 错误不杀进程。** +1s 抛错后
485
- 进程存活 40s+ 无恙,错误只落 #RNOH_JS/#RNOH_ARK 日志。Hermes 在 RNOH 自管
486
- 线程上,异常不重抛 ArkTS 主线程,errorManager 根本不参与。因此**鸿蒙不需要
487
- crash hold**:ets 编排器在 JS 死后照常跑完整轮。此前 §11.5 的
488
- errorManager/appRecovery 论述前提不成立,已改写。
489
-
490
- **实证二(端到端救砖):** crashprobe 作基座(每次启动 JS +1s 崩),服务端
491
- forceBoot 下发 v1:+5s 轮次照常检查→下载 v1.ppk→激活(currentVersion 写
492
- 入),重启直接运行 e2e-full-v1,砖消失。JS 崩溃对轮次零影响,无预算压力。
493
-
494
- **功能性断裂(已修,severity 高):`PushyTurboModule.cpp` 的 C++ 方法表漏
495
- 注册 10.50+ 的全部四个新方法**(syncNativeConfig / getNativeCheckCache /
496
- getBundleHash / resetToPackagedBundle)。RNOH 的 ArkTS TM 只有经 C++
497
- methodMap 注册的方法才对 JS 可见,缺失 = JS 侧 undefined → feature-detect
498
- 判"老原生"静默跳过 → **原生冷启动检测在鸿蒙自 10.51.0 从未生效**
499
- (nativeConfig 从未落盘,ets 编排器+本次的续传/零延迟全是死代码);
500
- resetToPackagedBundle(10.50 Phase 1)在鸿蒙 JS 侧也一直不可用。诊断链:
501
- 健康启动 preferences 无 nativeConfig → entry.diag 打 typeof=undefined +
502
- keys 只有 14 个老方法 → cpp 方法表对不上 ets 类。
503
-
504
- **为什么 CI 没逮住**:harmony e2e 套件 testMatch 只含 `e2e/harmony/**`
505
- (smoke + local-update),native-check.test.ts 从未在鸿蒙执行;而 JS 的
506
- feature-detect 把缺失当"老原生"优雅跳过——这是[[no-graceful-degradation]]
507
- 教训的又一实例:兼容旧原生的降级路径把新原生的接线断裂藏了三个版本。
508
-
509
- - [x] cpp 方法表补注册(含"新增 spec 方法必须同步注册"的醒目注释)
510
- - [ ] harmony e2e 补 native-check 场景(driver 是 hdc+uitest 非 detox,
511
- 需移植;顺带把 `hdc rport` 写进用例基建——模拟器 127.0.0.1 不通宿主)
512
- - [ ] 发版 10.52.1(鸿蒙 bridging 修复;Android/iOS 无变化)
513
- - [ ] Logger.ts isDebug 硬编码 false,轮次全程无日志可观测,排障全靠副作用
514
- (preferences/服务端流量);考虑 error 级关键节点或可配置开关