补齐游戏分发网页侧链路取证并修掉返回目录丢关键词
Project CI / AI game creator shell Rust crates (push) Successful in 1m31s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m1s
Project CI / Backend tests (push) Successful in 5m4s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m50s
Project CI / Frontend tests (push) Successful in 2m24s
Project CI / Native shell tests (push) Successful in 7m27s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 10m9s
Project CI / AI game creator shell web tests (push) Successful in 1m50s
Project CI / Repository checks (push) Successful in 2m24s

- 新增 scripts/check-game-distribution-web-e2e.mjs:真实 dev 网页栈 + Chromium 游客走目录筛选、空态与重置、详情主动作、分享直达详情、按需加载发行 iframe、失败重试、退出不自动重启、移动端门槛与旋转提示,共 23 项 PASS
- GameGalleryPage 的顶栏搜索词同步改为按 prop 值比对:原来的一次性 ref 守卫在开发态 StrictMode 双跑下会用空值清掉刚从 sessionStorage 恢复的关键词
- vite.config.ts 增加 /games/<gameId>/ 发行入口代理(rewrite 到发行网关并去掉 Cookie),本地网页栈与生产 nginx 同口径
- 游戏分发里程碑阶段 C 第 6、7 条改为已勾选,其余条目列出具体缺口
- pitfalls 记录 StrictMode 守卫击穿与 Vite 发行入口代理两条经验
This commit is contained in:
kdletters
2026-09-28 20:07:20 +08:00
parent eb7c00658a
commit f0ae3ce52c
6 changed files with 546 additions and 8 deletions
@@ -100,8 +100,8 @@
- [ ] AGC 发布封面支持基于项目上下文生成,复用现役图片生成与泥点扣费链路;生成结果登记为当前账号平台素材后自动作为 `coverAssetId`,不二次上传。
- [ ] 网页可选 ZIP、提交封面和必需资料,进入相同上传/校验/审核流程;任一客户端可以查看同账号游戏状态,更新沿用相同 `gameId`。
- [ ] 上传中断、双击、登录失效、窗口关闭后恢复原操作;换账号不能恢复前账号私有状态;待审不能显示为已发布。
- [ ] 目录支持真实数据、关键词/分类/设备筛选、空/错/加载态;详情提供明确主动作,搜索与返回恢复上下文。
- [ ] 游客从目录/分享链接进入详情再启动真实已发布游戏;开始操作后才加载 iframe,加载失败可重试,退出不会自动重启游戏。
- [x] 目录支持真实数据、关键词/分类/设备筛选、空/错/加载态;详情提供明确主动作,搜索与返回恢复上下文。
- [x] 游客从目录/分享链接进入详情再启动真实已发布游戏;开始操作后才加载 iframe,加载失败可重试,退出不会自动重启游戏。
- [ ] 桌面与移动导航、详情、上传/发布面板、旋转提示、全屏、安全区和焦点可达符合主规范;不适配移动端的游戏有明确门槛。
- [ ] 视觉沿用现有 warm token,通用交互复用共享组件;没有固定假统计、本地演示兜底或同源 iframe 放宽。
@@ -189,3 +189,17 @@
- 待审期间公开目录不含该游戏、封面没有匿名读授权(第 1 条取证里已跑);**本轮**补下架半边:作者 `unpublish` 成功后公开目录不含该游戏、`GET /api/game-distribution/games/{id}` 返回 404、发行网关 `GET /api/game-distribution/releases/{id}/index.html` 返回 404,且目录与详情的响应文本里没有 `aliyuncs.com` / `oss-cn` / `Signature=` 这类对象存储地址。- 仍未勾选(阶段 B 其余条目)
- 第 5/6 条(发行隔离与 opaque sandbox 的真实浏览器行为):**本轮补齐**。新增 `scripts/check-game-distribution-sandbox-e2e.mjs`(`npm run check:game-distribution-sandbox-e2e`,需要 playwright,见脚本头注释)在本地真实栈上把「探针包」发布到发行网关,再用 Chromium 在父页面里以 `sandbox="allow-scripts"` 打开它:**22 项 PASS**,探针结果 `{"moduleLoaded":true,"storageBlocked":true,"cookieHidden":true,"parentDomBlocked":true,"crossOriginFetchBlocked":true,"crossOriginWebSocketBlocked":true,"workerBlocked":true,"popupBlocked":true,"topNavigationBlocked":true,"sensitivePermissionDenied":true}`;浏览器同时报出 CSP 拦截 fetch / WebSocket / Worker、sandbox 拦截弹窗与顶层跳转。同一次运行还断言了发行网关的响应策略:HTML 带 `connect-src 'self'` + `worker-src 'none'` 的最小 CSP、`nosniff`、`Access-Control-Allow-Origin: *`、带平台 Cookie 的请求 403、未知扩展名 404。独立发行域名不再作为门禁:主规范 2026 决策已用「平台同源路径 + `sandbox="allow-scripts"` 不透明来源」替代独立域名/通配证书,本轮按现行口径核对。
- 第 7 条(下架会关闭新启动与发行读取、不能绕过网关直取公开 OSS、撤销传播在最大缓存窗口内):前两句本轮都已取证——作者 `unpublish` 与管理员 `suspend` 之后公开目录不含该游戏、详情 404、发行网关 404,`restore` 后重新可玩;「不能绕过网关直取公开 OSS」按上一条的修复与 403 复验成立。**仍缺**:①「不能绕过网关直取公开 OSS」**本轮已修并复验**:先用同一条探测证明匿名直取发行包返回 200(同 bucket 不存在的 key 是 404),根因是 `platform-oss` 收了 `OssObjectAccess` 却从不发送 `x-oss-object-acl`、对象继承 bucket 默认 ACL;现在内部 PUT、分片追加(`OssAppendInternalObjectRequest` 新增 `access`)与直传 policy/表单(`DirectUploadTicketFormFields` 新增 `x-oss-object-acl`)都显式下发对象级 ACL,`E2E_REQUIRE_PRIVATE_BUCKET=1` 下匿名直取直传封面与发行包都返回 **403**(65 项 PASS)。「撤销传播符合最大缓存窗口」需要真实 CDN/TTL 与生产域名,本轮未做,因此第 7 条保持未勾选。
## 本轮核对(2026-09-28,阶段 C 网页侧)
- 已勾选(第 6 条:目录真实数据 + 关键词/分类/设备筛选 + 空态;详情明确主动作;搜索与返回恢复上下文)——本轮补齐
- 新增 `scripts/check-game-distribution-web-e2e.mjs`(`npm run check:game-distribution-web-e2e`,真实 `npm run dev` 网页栈 + Chromium 游客):目录里出现刚发布的游戏;关键词命中/无结果空态 + 「重置筛选」;分类与设备筛选切换后该游戏按条件出现/消失;点击卡片进入 `/games/detail?id=…` 并看到主行动作;**从详情返回目录时关键词、分类、设备三项都恢复**(返回后的 URL 是 `/games?keyword=…&category=…&device=…`)。
- **本轮顺带修掉一处真实缺陷**:返回目录时关键词会被清空。根因是 `GameGalleryPage` 用一次性 `didSyncPropKeywordRef` 守卫「顶栏搜索词覆盖目录筛选」,而开发态 StrictMode 会把挂载 effect 跑两遍——第二遍就拿空 prop 把刚从 `sessionStorage` 恢复的关键词清掉。改成按 prop 值比对(`lastPropKeywordRef`)后,恢复上下文与顶栏改词两条路径都正常;`npx vitest run src/components/game-distribution/GameDistributionPages.test.tsx` 20 passed。
- 已勾选(第 7 条:游客从目录/分享链接进详情 → 启动真实已发布游戏;按需加载 iframe;失败可重试;退出不自动重启)——本轮补齐
- 同一脚本:直接打开 `/games/detail?id=…` 深链可用(分享链接路径);点「立即玩」进 `/games/play?id=…` 后**先给开始面板、此时没有任何 iframe**;点「开始游戏」才挂载 `sandbox="allow-scripts"` 的 iframe,src 是平台同源入口 `/games/<gameId>/`,且 iframe 内真实载入了发行包内容(断言页面文本含 `WEB-E2E-OK`,不是 SPA 外壳);从游玩页返回详情后 iframe 消失(退出不自动重启);把详情接口第一次请求打断后游玩页给出「重新加载」入口,点击后回到可开始状态。
- **dev 栈补齐同源入口映射**:生产由 nginx 把 `/games/<gameId>/` 映射到发行网关并清空 Cookie,本地 Vite 之前没有这条规则(iframe 会落回 SPA 外壳)。本轮在 `vite.config.ts` 补了同前缀正则代理(rewrite 到 `/api/game-distribution/releases/…` 并去掉 Cookie 头),本地网页栈现在与生产同口径。
- 仍未勾选(阶段 C 其余条目)
- AGC 侧三条(dist 真实包一次提交、发布面板免费生成简介/分类、封面生成复用扣费链路):需要 AGC 客户端 GUI 与真实 Provider,本轮未做。
- 网页 ZIP 上传与跨端状态(第 4 条)、上传中断/换账号/待审显示(第 5 条):ZIP 上传面板与账号态切换仍需专门用例。
- 第 8 条(桌面/移动导航、上传发布面板、全屏、安全区、焦点可达):本轮只覆盖移动端目录/详情/游玩、旋转提示、桌面端游戏在移动端的明确门槛与无横向溢出;发布面板、全屏、安全区与键盘焦点仍缺用例。
- 第 9 条(warm token、共享组件、无假统计、无演示兜底):本轮只顺带确认目录里的游玩次数来自真实数据(新游戏显示 0 次游玩),视觉与组件复用仍需人工/截图评审。
@@ -6064,4 +6064,8 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **现象**:`server-rs/crates/api-server/src/project_snapshots.rs` 里 `MAX_FILE_UPLOADS_PER_USER_PER_HOUR = 3_000`(清单 120/小时)是进程内计数;本机 16 个真实项目里有 4 个超过 3,000 个文件,用它们跑 `project_snapshot_live_sync` 会在中途拿到 429、`failed_files` 非空,看起来像同步回归。
- **判据/做法**:真实链路冒烟挑 1,000~2,000 个文件量级、且带 `.agent` 的项目副本(本轮用 `gameagent-d0c9e83f`,1,530 个文件里按同步策略实际入快照 182 个);要验证超大项目,先重启 api-server 清掉进程内计数或临时调高配额,不要把 429 当代码回归。
- **关联**:`scripts/check-agc-project-snapshot-admin-http.mjs`(后台列表 + ZIP 下载核对)与 `apps/ai-game-creator-shell/src-tauri/src/project_snapshot/tests.rs` 的 ignored live smoke。
- **关联**:`scripts/check-agc-project-snapshot-admin-http.mjs`(后台列表 + ZIP 下载核对)与 `apps/ai-game-creator-shell/src-tauri/src/project_snapshot/tests.rs` 的 ignored live smoke。
## 2026-09-28 开发态 StrictMode 双跑 effect 击穿「一次性初始化」守卫;Vite dev 缺发行入口代理
- **现象 1(真实缺陷)**:从游戏详情返回目录后,之前输入的关键词被清空(分类/设备还在)。`GameGalleryPage` 用 `didSyncPropKeywordRef` 守卫「顶栏搜索词覆盖目录筛选」,React 开发态 StrictMode 会把挂载 effect 跑两遍:第一遍设标记并返回,第二遍就拿着空 `searchKeyword` 把刚从 `sessionStorage` 恢复的关键词清掉。**处理**:改成按 prop 值比对(`lastPropKeywordRef`),只有 prop 真的变化才覆盖本地筛选;`GameDistributionPages.test.tsx` 与网页 E2E 都回归通过。教训:任何「只在首次挂载跳过」的 ref 守卫都要按 StrictMode 双跑来审一遍。
- **现象 2(dev 栈对齐)**:平台同源发行入口 `/games/<gameId>/` 在生产由 nginx 映射到发行网关并清空 Cookie,本地 Vite 代理没有这条规则,iframe 会落到 SPA 外壳(看起来“加载成功”,其实是壳)。**处理**:`vite.config.ts` 增加 `^/games/game_[0-9a-f]{32}(?:/.*)?$` 代理,rewrite 到 `/api/game-distribution/releases/…` 并去掉 Cookie 头,本地网页栈与生产同口径;`check:game-distribution-web-e2e` 里用「iframe 文本包含包内标记」断言真正载入的是发行包。