§7.5 路径回退到 writeToFile:撤销 evaluateJavaScript 路径
CLAUDE.md 原则 A 升级为第一准则(H5 端零修改不可妥协)后,重新评估 上一轮 commit246f215引入的 evaluateJavaScript 路径: - WKUserScript(.atDocumentStart) 注入会被 H5 自带 var app_xxx 声明 覆盖回退 - WKUserScript(.atDocumentEnd) 又太晚,H5 业务顶层 <script> 已经 读过默认值 - 没有"在 <script src> 之后、业务 <script> 之前"的精确时机 - 任何"让 H5 改一行 / 改一个文件"的妥协方案违反原则 A 第一准则 - 详细分析见 CLAUDE.md「典型案例 3」 撤销方案:回到原 msext writeToFile 路径,原生在 loadFileURL 之前写 4 个 app_*.js 文件到沙盒,H5 启动时 <script src> 同步引入即可读到 实际值,时序 100% 等价 msext。 Design §7.5 全面回退(-285 / +126 行): - §7.5 头部:标题 / 简介改回写文件路径;中间走过两次弯路的完整决策 链记录在头部(commitf68b0db→246f215→ 本次 commit) - §7.5.2 注入时机表 → 4 个文件写入时机表,附"为什么不要照搬 WKUserScript 注入"警告段 - §7.5.3 删除 AppDataInjector + .legacy 标注;AppDataWriter 升回主路径 (保留 logger debug 输出 / msext 字面对齐细节 / 转义 / 大小写) - §7.5.4 接入点:writeInitial + writeBattery + writeNetwork 三阶段, addObserver 持续监听 - §7.5.5 大厅 / 子游戏 / 弹层差异表回到 "app_data.js / app_gamesname.js / app_battery.js / app_network.js" 维度 - §7.5.6 与 msext 差异表回到"writeToFile 等价化"对比 - §7.5.7 与 §3.4.1 polyfill 关系:§7.5 文件预写为主路径; WKUserScript + evaluateJavaScript "探索后撤回" - §7.5.8 联调路径恢复 cat 文件路径,加 4 种现象诊断表 Contract §4.2 同步: - 头部说明改回 "原生 writeToFile 写文件" + 撤销中间 evaluateJavaScript 路径的复盘段 - 子节标题回到 "写到 {gamedir}/{gamestart}/app_*.js" - 子节"更新时机"段回到 loadFileURL 前 / addObserver / 前台切换 - 删除"H5 团队 zip 自带 + 默认占位"约定(不再适用,原生重新负责写入) - 修订记录新增一行"路径再次调整(最终)" Contract §10 验收清单同步: - "H5 zip 自带 4 个 .js" → "沙盒下 4 个 .js 存在(xcrun simctl + cat 验证)" - 删"原生 WKUserScript 覆盖生效"项 → 加"值是实际渠道值而非占位" - "阶段 B 动态覆盖" → "battery/network 变化重写文件" Plan §6.4 里程碑加两行:CLAUDE.md A 升级 + §7.5 最终回退。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
3039daf903
commit
13cc24b463
+24
-32
@@ -690,22 +690,16 @@ _webView.scrollView.showsHorizontalScrollIndicator = NO;
|
||||
- LocalStorage 由 H5 自管(WKWebView 默认走 WebsiteDataStore)
|
||||
- 加载方式:**`loadFileURL:fileURL allowingReadAccessToURL:dirURL`**(file://),不走 HTTP
|
||||
|
||||
### 4.2 JS 注入(**2026-06-22 重大路径调整:原生不写文件,只覆盖 `window.app_xxx` 全局变量**)
|
||||
### 4.2 JS 注入(最终路径:原生 `writeToFile` 写文件,与 msext 1:1 等价)
|
||||
|
||||
H5 业务通过 `<script src="app_*.js">` 同步引入 4 个 `.js` 文件,文件内是 `var app_xxx = "<默认占位>"` 形式的全局变量声明。
|
||||
H5 业务通过 `<script src="app_*.js">` 同步引入 4 个 `.js` 文件,文件内是 `var app_xxx = "..."` 形式的全局变量声明。**原生在 `loadFileURL` 之前用 `writeToFile`/ `String.write(to:)` 把这 4 个文件写到 `{gamedir}/{gamestart}/`,H5 启动时 `<script src>` 同步引入直接读到实际值**。
|
||||
|
||||
**契约调整**(与原 msext 行为差异):
|
||||
> ⚠️ **2026-06-22 路径决策最终落定**(中间曾尝试改为"H5 自带 + 原生 `evaluateJavaScript` 覆盖 `window.app_xxx`"路径,commit `246f215`,已撤销)。撤销原因:`WKUserScript(.atDocumentStart)` 注入会被 H5 自带 `var app_xxx` 声明覆盖回退;`.atDocumentEnd` 又太晚,H5 业务顶层 `<script>` 已经读过默认值。任何"H5 配合改动"的妥协方案违反 CLAUDE.md 原则 A 第一准则。正确解法:回到 msext 老路写文件,时序 100% 等价。详细分析见 CLAUDE.md「典型案例 3」。
|
||||
|
||||
| 阶段 | msext 旧契约 | 新契约 |
|
||||
|---|---|---|
|
||||
| `app_*.js` 文件来源 | 原生 `NewRootVC.initJSdata` 用 `writeToFile:` 每次启动时写入沙盒 | **H5 团队随 `gamehall.zip` 一起打包**(带默认占位值),原生不写 |
|
||||
| `app_xxx` 全局变量赋值 | H5 引入文件后由文件内的 `var` 声明赋值 | H5 引入文件后默认占位生效;原生用 `WKUserScript(.atDocumentEnd)` + `evaluateJavaScript` 覆盖 `window.app_xxx` 为实际值 |
|
||||
| 路径变量 (`{gamedir}/{gamestart}/`) | 原生需知道写入路径 | 由 H5 zip 包结构自带,原生不感知 |
|
||||
|
||||
新契约下原生侧的工作:
|
||||
1. 在 `WKWebViewConfiguration` 装配阶段 addUserScript 一段 `(function(){ window.app_version=1; window.app_gameconfig='...'; ... })()`,injectionTime `.atDocumentEnd`
|
||||
2. battery / network 变化时用 `webView.evaluateJavaScript("window.app_getbattery=...")` 单条覆盖
|
||||
3. 不再 `writeToFile`,不再关心文件路径
|
||||
原生侧的工作(与原 msext `NewRootVC.initJSdata` 等价):
|
||||
1. **`loadFileURL` 之前**用 `String.write(to:)` 写 4 个 `.js` 文件到 `{gamedir}/{gamestart}/` 沙盒目录
|
||||
2. battery / network 变化时**重写**对应 `.js` 文件(H5 下次 reload 读到新值;业务期间的实时变化由 §3.2 [2][3]反向 callback 推送)
|
||||
3. 文件内容用单引号包裹字符串(与 msext 字面对齐),数值字段无引号
|
||||
|
||||
**⚠️ 命名约束:文件名 ≠ 变量名**(沿用 msext 历史)
|
||||
|
||||
@@ -740,42 +734,39 @@ var app_invitationcode = "<tuiguang_id>"; // 推广码 / 邀请码
|
||||
> - `app_gamedir`:大厅 = `self.gamedir`(即 BundleConfig.gameDir)/ 子游戏 = `self.gamefilepath`(子游戏目录)
|
||||
> - `app_gamestart` + `app_gamename`:大厅 = `self.gamestart` / 子游戏 = `self.game_name`(子游戏名)
|
||||
|
||||
#### `app_battery.js`(H5 zip 包内,含 `app_getbattery` 默认占位)
|
||||
#### `app_battery.js`(写到 `{gamedir}/{gamestart}/app_battery.js`)
|
||||
|
||||
H5 端默认形式:
|
||||
```js
|
||||
var app_getbattery = 0.85; // ⚠️ 变量名带 get!msext "var app_getbattery=%lf" double 字面
|
||||
```
|
||||
|
||||
原生覆盖时机(每次写一行 `window.app_getbattery = N;`):
|
||||
- 首次 `webView didFinish` 后补一次(保证页面就绪即可读到当前电量)
|
||||
- `UIDevice.batteryLevelDidChangeNotification` 触发
|
||||
更新时机:
|
||||
- `loadFileURL` 之前补一次(保证 H5 启动就能读到当前电量)
|
||||
- `UIDevice.batteryLevelDidChangeNotification` 触发重写
|
||||
- `UIApplication.willEnterForegroundNotification`(前台切换补 retroact)
|
||||
|
||||
#### `app_network.js`(H5 zip 包内,含 `app_getnetwork` 默认占位)
|
||||
#### `app_network.js`(写到 `{gamedir}/{gamestart}/app_network.js`)
|
||||
|
||||
H5 端默认形式:
|
||||
```js
|
||||
var app_getnetwork = 2; // ⚠️ 变量名带 get!msext "var app_getnetwork=%d" int 字面:1 无网 / 2 WiFi / 3 蜂窝
|
||||
```
|
||||
|
||||
原生覆盖时机:
|
||||
- 首次 `webView didFinish` 后补一次
|
||||
更新时机:
|
||||
- `loadFileURL` 之前补一次
|
||||
- `NWPathMonitor` 状态变化(msext 用 AFNetworkReachability,新外壳用 NWPathMonitor)
|
||||
- 前台切换
|
||||
|
||||
#### `app_gamesname.js`(H5 zip 包内,含 `app_gamesname` 默认占位)
|
||||
#### `app_gamesname.js`(写到 `{gamedir}/{gamestart}/app_gamesname.js`)
|
||||
|
||||
H5 端默认形式:
|
||||
```js
|
||||
var app_gamesname = new Array('game1','game2',...); // ⚠️ var 后面两个空格,沿用 msext AppDelegate.m:259 字面
|
||||
```
|
||||
|
||||
原生覆盖时机(合并进首次 documentEnd UserScript,即阶段 A):
|
||||
- 首次启动覆盖
|
||||
- 新装一个子游戏后下次进入大厅时覆盖(msext `gameController.m:197` 历史上注释掉了实际写入,仅 `AppDelegate` 首次有效;新外壳每次进入大厅都覆盖一次,更稳)
|
||||
更新时机:
|
||||
- `AppDelegate.applicationDidFinishLaunching`(首次启动写入)
|
||||
- 子游戏 `gameController.viewInit`(新装一个子游戏后追加;msext `gameController.m:197` 历史上注释掉了实际写入逻辑,仅 `AppDelegate` 首次写入是活的)
|
||||
|
||||
> ⚠️ **新外壳约定**:H5 团队保证 `gamehall.zip` 内 4 个 `app_*.js` 都存在,并提供合理默认占位(即使原生注入失败,H5 至少能读到默认值不崩)。原生不再保证"loadFileURL 之前文件已存在"——这是 H5 端打包责任。
|
||||
> H5 模板里 `index.html` 通过 `<script src="app_data.js">` 等同步引入这 4 个文件。新外壳必须保证这些文件**在 loadFileURL 之前**就写好,否则 H5 启动时变量未定义直接崩。
|
||||
|
||||
#### 修订记录
|
||||
|
||||
@@ -784,7 +775,8 @@ var app_gamesname = new Array('game1','game2',...); // ⚠️ var 后面两
|
||||
| 2026-06-22 | 删 `app_gameid` / `app_compareCode`:原项目 grep `var app_` 在 `NewRootVC.m:1204` / `gameController.m:2160` 字面字符串里没有这两项 | daoqi 原项目代码全面 grep 验证 |
|
||||
| 2026-06-22 | `app_battery` 改为 `app_getbattery`、`app_network` 改为 `app_getnetwork`:变量名带 `get` 前缀,与文件名(不带 get)不一致 | daoqi `gameController.m:1191/1198`、`NewRootVC.m:1706/1714` |
|
||||
| 2026-06-22 | 新增 `app_version` / `app_Launchtype` / `app_getwifisignalLevel` / `app_gamename` / `app_invitationcode` / `app_gamesname` 共 6 项 | daoqi `NewRootVC.m:1204`、`gameController.m:2160`、`AppDelegate.m:259` |
|
||||
| 2026-06-22 | **路径调整**:从"原生 `writeToFile` 写 4 个 `.js` 文件"改为"H5 团队 zip 包自带 + 原生 `WKUserScript(.atDocumentEnd)` / `evaluateJavaScript` 覆盖 `window.app_xxx` 全局变量值"。原生不再感知文件路径与字符串包裹符 | H5 团队 / 项目方约定 |
|
||||
| 2026-06-22 | **路径调整**:从"原生 `writeToFile` 写 4 个 `.js` 文件"改为"H5 团队 zip 包自带 + 原生 `WKUserScript(.atDocumentEnd)` / `evaluateJavaScript` 覆盖 `window.app_xxx` 全局变量值" | H5 团队 / 项目方约定 |
|
||||
| 2026-06-22 | **路径再次调整(最终)**:撤销上一行的 evaluateJavaScript 路径,回到原 msext "原生 `writeToFile` 写文件"路径。原因:WKUserScript 时序无法 1:1 等价 msext(`.atDocumentStart` 被 H5 自带 var 覆盖,`.atDocumentEnd` 太晚 H5 业务已读默认值),任何要求 H5 配合改动的方案违反 CLAUDE.md 原则 A 第一准则。Design §7.5 + 本节同步回退 | CLAUDE.md 原则 A 升级为第一准则后的重新评估 |
|
||||
|
||||
### 4.3 WKUIDelegate(H5 alert / confirm)
|
||||
|
||||
@@ -1004,12 +996,12 @@ Library/Caches/
|
||||
- [ ] 重装 + 热启动:< 1 秒进入大厅
|
||||
- [ ] 渠道注入:把 `msext/channel/xxx` 目录名改成 `yyy`,重打包后大厅显示的渠道 ID 变为 `yyy`
|
||||
- [ ] iOS 18 / 19 / 26 上启动不卡死
|
||||
- [ ] **`app_*.js` 4 个文件由 H5 zip 包内自带**:解压 `gamehall.zip` 后 `app_data.js` / `app_battery.js` / `app_network.js` / `app_gamesname.js` 全部存在(H5 团队打包责任,原生不写)
|
||||
- [ ] **原生 `WKUserScript(.atDocumentEnd)` 覆盖生效**:在 Safari Web Inspector 读 `window.app_channel` 等 13 项启动期变量,必须是**实际渠道值**(来自 `ChannelConfig.plist`),**不是** H5 zip 包里 `app_data.js` 的默认占位值
|
||||
- [ ] **`app_*.js` 4 个文件在沙盒**:`{gamedir}/{gamestart}/` 下 `app_data.js` / `app_battery.js` / `app_network.js` / `app_gamesname.js` 全部存在、loadFileURL 前已落盘(`xcrun simctl get_app_container booted <bundle>` + `cat` 验证)
|
||||
- [ ] **`app_*` 12 + 1 + 1 + 1 = 15 个全局变量命名 100% 正确**(参 §4.2):H5 console 检查 `app_version` / `app_gameconfig` / `app_gamedir` / `app_gamestart` / `app_agent` / `app_appversion` / `app_market` / `app_channel` / `app_Launchtype` / `app_getwifisignalLevel` / `app_gamename` / `app_invitationcode` / `app_getbattery` / `app_getnetwork` / `app_gamesname` 全部 `!== undefined`;尤其大小写 `app_Launchtype`(L 大写)/ `app_getwifisignalLevel`(wifi 小写 + signal/Level 区分)/ `app_getbattery`、`app_getnetwork`(带 get 前缀)必须精确
|
||||
- [ ] **值是实际渠道值而非占位**:H5 console 读 `app_channel` 应为 `ChannelConfig.plist` 的 channel 值(不是 H5 团队 zip 内可能预留的占位字符串)
|
||||
- [ ] **大厅 vs 子游戏字面差异**:大厅 H5 内 `app_Launchtype === 0`(数字 0,非字符串 `"0"`)、子游戏 H5 内 `app_Launchtype === 1`
|
||||
- [ ] **不要存在已撤销的变量**:H5 console 检查 `app_gameid` / `app_compareCode` 应为 `undefined`(早期 Contract §4.2 误列,原项目代码字面字符串里不写)
|
||||
- [ ] **阶段 B 动态覆盖**:模拟器 Features → Battery State 切换电量;Web Inspector 读 `window.app_getbattery` 应实时跟随;同样切换 Network Link Conditioner 验证 `window.app_getnetwork`
|
||||
- [ ] **battery / network 变化重写**:模拟器 Features → Battery State 切换电量;reload 后 H5 读 `app_getbattery` 应跟随;同样切换 Network Link Conditioner 验证 `app_getnetwork`(业务期间实时变化由 §3.2 [2][3]反向 callback 推送,不依赖文件重写)
|
||||
|
||||
### B. 桥接
|
||||
- [ ] H5 调 `accreditlogin` → 微信弹授权页 → 同意后 H5 收到 `sharelogin` 回调,7 字段完整
|
||||
|
||||
Reference in New Issue
Block a user