refactor: 收口推荐 runtime 鉴权计划

This commit is contained in:
2026-06-04 04:38:01 +08:00
parent f9f22e5663
commit 4e23995347
7 changed files with 249 additions and 27 deletions

View File

@@ -24,6 +24,14 @@
- 验证方式:`npm run test -- src/components/platform-entry/barkBattleWorkCache.test.ts`、针对 Bark Battle Work Cache Module 与平台壳执行 ESLint、`npm run typecheck``npm run check:encoding`
- 关联文档:`docs/technical/【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md`
## 2026-06-04 Platform Recommend Runtime Auth Model 收口
- 背景:平台推荐 runtime 的 embedded 启动需要在匿名 Runtime Guest Token、已登录 background auth 和非 embedded 默认鉴权之间分流,拼图还额外维护 `isolated` / `default` runtime auth mode旧规则散在顶层 helper 与多个启动 callback。
- 决策:新增 `src/components/platform-entry/platformRecommendRuntimeAuthModel.ts`,以 `resolvePlatformRecommendRuntimeAuthPlan(input)``shouldUsePlatformRecommendRuntimeGuestAuth(input)` 收口纯鉴权计划。壳层仍负责读取 `getStoredAccessToken()`、申请 `ensureRuntimeGuestToken()`、拼装 request options 和写入拼图 runtime auth mode。
- 影响范围:推荐 Tab 内嵌 runtime 启动、拼图公开详情 isolated 入口、推荐运行态后续 action 的局部鉴权口径,以及后续新增可嵌入推荐 runtime 的玩法。
- 验证方式:`npm run test -- src/components/platform-entry/platformRecommendRuntimeAuthModel.test.ts`、针对新 Module 与平台壳执行 ESLint、`npm run typecheck``npm run check:encoding`
- 关联文档:`docs/technical/【前端架构】PlatformRecommendRuntimeAuthModel收口计划-2026-06-04.md`
## 2026-06-04 Platform Creation Launch Model 收口
- 背景:平台创作入口点击回调曾在 `PlatformEntryFlowShellImpl.tsx` 内联判断 `airp` 占位、隐藏的 `baby-object-match`、未知入口和各玩法工作台启动目标,壳层同时承接入口 ID 规则、启动前准备顺序和副作用。

View File

@@ -69,6 +69,8 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
Bark Battle 草稿三图完整性、生成状态归一、发布快照 / 发布回包资产兜底和草稿 / 已发布作品进入 runtime 前的 `BarkBattlePublishedConfig` 映射收口到 `src/components/platform-entry/barkBattleWorkCache.ts`,规则见 [【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md](./technical/【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md)。
平台首页推荐 runtime 的匿名 Runtime Guest Token、已登录 background auth、非 embedded no-op 和拼图 isolated/default auth mode 计划收口到 `src/components/platform-entry/platformRecommendRuntimeAuthModel.ts`,规则见 [【前端架构】PlatformRecommendRuntimeAuthModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformRecommendRuntimeAuthModel收口计划-2026-06-04.md)。
RPG Agent 结果页发布门禁展示和预览来源 label 收口到 `src/components/platform-entry/platformRpgAgentResultPreviewModel.ts`,壳层只保留 session/profile 编排和结果页 props 传递,规则见 [【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md)。
平台入口创作生成通知、pending 作品架占位、失败覆盖、拼图稳定 ID 和草稿 Tab 未读点收口到 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,规则见 [【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91DraftGenerationShelfModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。

View File

@@ -0,0 +1,36 @@
# 【前端架构】Platform Recommend Runtime Auth Model 收口计划
## 背景
平台首页推荐流会以 embedded runtime 方式启动跳一跳、抓大鹅、方洞挑战、拼图、敲木鱼、视觉小说、大鱼吃小鱼和汪汪声浪等玩法。旧规则散在 `PlatformEntryFlowShellImpl.tsx` 顶层 helper 与多个启动 callback匿名访客应申请 Runtime Guest Token已登录或已有 access token 时应走 background auth非 embedded 正常启动则不改普通鉴权。拼图还额外维护 `isolated` / `default` runtime auth mode容易与通用推荐流口径漂移。
## 决策
新增 `src/components/platform-entry/platformRecommendRuntimeAuthModel.ts`,以纯 **Module** 收口推荐 runtime 鉴权计划:
- `resolvePlatformRecommendRuntimeAuthPlan(input)`:返回 `requestKind``none``background``runtime-guest`,并给出拼图 runtime 应落到 `default` 还是 `isolated`
- `shouldUsePlatformRecommendRuntimeGuestAuth(input)`:只判断当前用户状态和是否允许 guest auth不读取本地 token。
`PlatformEntryFlowShellImpl.tsx` 继续作为 **Adapter**:它读取 `getStoredAccessToken()`、调用 `ensureRuntimeGuestToken()`、拼装具体 request options并在启动拼图时写入 `setPuzzleRuntimeAuthMode(...)`
## Interface 约束
- 非 embedded 且未显式允许 runtime guest auth 时,计划为 `none`
- embedded 推荐 runtime 若无登录用户且无本地 access token计划为 `runtime-guest`
- embedded 推荐 runtime 若已有登录用户或本地 access token计划为 `background`
- 拼图公开详情要求 `authMode='isolated'` 时,匿名状态应返回 `runtime-guest``puzzleRuntimeAuthMode='isolated'`
- 拼图公开详情要求 `authMode='isolated'` 但已登录或已有 access token 时,应回到 `default`,避免把账号态伪装成匿名 isolated guest。
## Depth / Leverage / Locality
- **Depth**:壳层传入 embedded、是否允许 guest、用户 ID 与本地 token 布尔值,即得 request 计划和拼图 runtime auth mode。
- **Leverage**:所有推荐 runtime 启动复用同一鉴权矩阵;新增玩法只需消费计划,不再重写匿名 / 已登录分支。
- **Locality**guest token 选择规则集中在纯测试面,具体 token 获取和 request options 仍留在壳层副作用 Adapter。
## 验收
- `npm run test -- src/components/platform-entry/platformRecommendRuntimeAuthModel.test.ts`
- `npx eslint --max-warnings 0 src/components/platform-entry/platformRecommendRuntimeAuthModel.ts src/components/platform-entry/platformRecommendRuntimeAuthModel.test.ts`
- `npx eslint src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
- `npm run typecheck`
- `npm run check:encoding`

View File

@@ -171,7 +171,7 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
删除等破坏性动作当前未接入 jump-hop 删除 API如果后续要在作品架提供删除入口必须先补齐后端/SpacetimeDB/前端整条删除链路,再开放按钮。
推荐页匿名游玩不再限定为跳一跳。移动端一级 `推荐` Tab 是内嵌运行态刷卡流,会自动选择推荐作品并启动对应玩法;桌面端首页不启动这套移动推荐运行态,而是渲染桌面发现壳,展示 `今日游戏``推荐``作品分类` 等桌面内容。断点事实统一走 `platformEntryResponsive.ts``usePlatformDesktopLayout()`,平台壳和首页视图必须共用同一个判断,避免桌面发现页与移动推荐页同时挂载、重复触发请求或启动运行态。推荐页嵌入运行态启动时按真实身份分流:已登录用户或本地已有 access token 时继续使用账号 Bearer但请求选项必须是 local auth impact避免单卡 401 清空整站登录态;只有确认为匿名访客时才申请短期 Runtime Guest Token并只把它作为局部请求头传给运行态客户端不写入全局登录态、不触发 refresh也不把匿名流量伪装成普通用户。当前覆盖矩阵为跳一跳、视觉小说、抓大鹅 Match3D、方洞挑战、拼图、敲木鱼、大鱼吃小鱼、汪汪声浪。每个模板的启动请求、推荐页内后续运行态动作以及需要上报的 play/finish/leaderboard/next-level 类请求都必须继续按该身份分流公开读取入口仍可匿名读取创作、个人作品、删除、发布、Remix 等账号/所有权动作仍保持普通用户鉴权。
推荐页匿名游玩不再限定为跳一跳。移动端一级 `推荐` Tab 是内嵌运行态刷卡流,会自动选择推荐作品并启动对应玩法;桌面端首页不启动这套移动推荐运行态,而是渲染桌面发现壳,展示 `今日游戏``推荐``作品分类` 等桌面内容。断点事实统一走 `platformEntryResponsive.ts``usePlatformDesktopLayout()`,平台壳和首页视图必须共用同一个判断,避免桌面发现页与移动推荐页同时挂载、重复触发请求或启动运行态。推荐页嵌入运行态启动时按真实身份分流:已登录用户或本地已有 access token 时继续使用账号 Bearer但请求选项必须是 local auth impact避免单卡 401 清空整站登录态;只有确认为匿名访客时才申请短期 Runtime Guest Token并只把它作为局部请求头传给运行态客户端不写入全局登录态、不触发 refresh也不把匿名流量伪装成普通用户。当前覆盖矩阵为跳一跳、视觉小说、抓大鹅 Match3D、方洞挑战、拼图、敲木鱼、大鱼吃小鱼、汪汪声浪。每个模板的启动请求、推荐页内后续运行态动作以及需要上报的 play/finish/leaderboard/next-level 类请求都必须继续按该身份分流公开读取入口仍可匿名读取创作、个人作品、删除、发布、Remix 等账号/所有权动作仍保持普通用户鉴权。推荐 runtime 的 `none` / `background` / `runtime-guest` 请求计划和拼图 `default` / `isolated` runtime auth mode 由 `platformRecommendRuntimeAuthModel.ts` 统一判定,平台壳只负责读取 token、申请 Runtime Guest Token 和传递 request options。
## 敲木鱼

View File

@@ -585,6 +585,10 @@ import {
buildPuzzleResultProfileId,
buildPuzzleResultWorkId,
} from './platformPuzzleIdentityModel';
import {
type PlatformPuzzleRuntimeAuthMode,
resolvePlatformRecommendRuntimeAuthPlan,
} from './platformRecommendRuntimeAuthModel';
import {
buildPlatformRpgAgentResultPublishGateView,
resolvePlatformRpgAgentResultPreviewSourceLabel,
@@ -624,7 +628,7 @@ type PuzzleRuntimeReturnStage =
| 'puzzle-gallery-detail'
| 'work-detail'
| 'platform';
type PuzzleRuntimeAuthMode = 'default' | 'isolated';
type PuzzleRuntimeAuthMode = PlatformPuzzleRuntimeAuthMode;
type PuzzleOnboardingDraft = {
promptText: string;
@@ -686,6 +690,10 @@ const RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS =
BACKGROUND_AUTH_REQUEST_OPTIONS;
const RECOMMEND_PUZZLE_BACKGROUND_AUTH_OPTIONS =
RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS;
type RecommendRuntimeAuthUi =
| { user?: { id?: string } | null }
| null
| undefined;
async function buildRecommendRuntimeGuestOptions() {
const { token } = await ensureRuntimeGuestToken();
return {
@@ -693,25 +701,36 @@ async function buildRecommendRuntimeGuestOptions() {
runtimeGuestToken: token,
};
}
function shouldUseRecommendRuntimeGuestAuth(
authUi: { user?: { id?: string } | null } | null | undefined,
function resolveCurrentRecommendRuntimeAuthPlan(
authUi: RecommendRuntimeAuthUi,
input: { embedded?: boolean; allowRuntimeGuestAuth?: boolean } = {},
) {
return !authUi?.user?.id?.trim() && !getStoredAccessToken();
return resolvePlatformRecommendRuntimeAuthPlan({
embedded: input.embedded,
allowRuntimeGuestAuth: input.allowRuntimeGuestAuth,
authUserId: authUi?.user?.id ?? null,
hasStoredAccessToken: Boolean(getStoredAccessToken()),
});
}
async function buildRecommendRuntimeAuthOptions(
authUi: { user?: { id?: string } | null } | null | undefined,
embedded?: boolean,
async function buildRecommendRuntimeOptionsFromAuthPlan(
plan: ReturnType<typeof resolvePlatformRecommendRuntimeAuthPlan>,
) {
if (!embedded) {
return {};
}
if (shouldUseRecommendRuntimeGuestAuth(authUi)) {
if (plan.requestKind === 'runtime-guest') {
return buildRecommendRuntimeGuestOptions();
}
if (plan.requestKind === 'background') {
return RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS;
}
return {};
}
async function buildRecommendRuntimeAuthOptions(
authUi: RecommendRuntimeAuthUi,
embedded?: boolean,
) {
return buildRecommendRuntimeOptionsFromAuthPlan(
resolveCurrentRecommendRuntimeAuthPlan(authUi, { embedded }),
);
}
const PUZZLE_DRAFT_GENERATION_POINT_COST = 2;
const MATCH3D_DRAFT_GENERATION_POINT_COST = 10;
const BARK_BATTLE_DRAFT_GENERATION_POINT_COST = 3;
@@ -8106,17 +8125,20 @@ export function PlatformEntryFlowShellImpl({
profileId: item.profileId,
levelId: normalizedLevelId || null,
};
const canUseRuntimeGuestAuth =
options.embedded || options.authMode === 'isolated';
const useRuntimeGuestAuth =
canUseRuntimeGuestAuth && shouldUseRecommendRuntimeGuestAuth(authUi);
const runtimeGuestOptions = useRuntimeGuestAuth
const authPlan = resolveCurrentRecommendRuntimeAuthPlan(authUi, {
embedded: options.embedded,
allowRuntimeGuestAuth:
options.embedded || options.authMode === 'isolated',
});
const runtimeGuestOptions =
authPlan.requestKind === 'runtime-guest'
? await buildRecommendRuntimeGuestOptions()
: {};
const authMode = useRuntimeGuestAuth ? 'isolated' : 'default';
const runtimeAuthOptions = useRuntimeGuestAuth
const authMode = authPlan.puzzleRuntimeAuthMode;
const runtimeAuthOptions =
authPlan.requestKind === 'runtime-guest'
? runtimeGuestOptions
: canUseRuntimeGuestAuth
: authPlan.requestKind === 'background'
? RECOMMEND_PUZZLE_BACKGROUND_AUTH_OPTIONS
: {};
const { run } =

View File

@@ -0,0 +1,96 @@
import { expect, test } from 'vitest';
import {
resolvePlatformRecommendRuntimeAuthPlan,
shouldUsePlatformRecommendRuntimeGuestAuth,
} from './platformRecommendRuntimeAuthModel';
test('uses runtime guest auth for anonymous embedded recommendation runtime', () => {
expect(
resolvePlatformRecommendRuntimeAuthPlan({
embedded: true,
authUserId: null,
hasStoredAccessToken: false,
}),
).toEqual({
requestKind: 'runtime-guest',
puzzleRuntimeAuthMode: 'isolated',
});
});
test('uses background auth for signed-in embedded recommendation runtime', () => {
expect(
resolvePlatformRecommendRuntimeAuthPlan({
embedded: true,
authUserId: 'user-1',
hasStoredAccessToken: false,
}),
).toEqual({
requestKind: 'background',
puzzleRuntimeAuthMode: 'default',
});
});
test('uses background auth when embedded runtime has only a stored access token', () => {
expect(
resolvePlatformRecommendRuntimeAuthPlan({
embedded: true,
authUserId: null,
hasStoredAccessToken: true,
}),
).toEqual({
requestKind: 'background',
puzzleRuntimeAuthMode: 'default',
});
});
test('does not alter auth for non-embedded runtime launches by default', () => {
expect(
resolvePlatformRecommendRuntimeAuthPlan({
embedded: false,
authUserId: null,
hasStoredAccessToken: false,
}),
).toEqual({
requestKind: 'none',
puzzleRuntimeAuthMode: 'default',
});
});
test('uses isolated guest auth for anonymous puzzle isolated launch', () => {
expect(
resolvePlatformRecommendRuntimeAuthPlan({
embedded: false,
allowRuntimeGuestAuth: true,
authUserId: null,
hasStoredAccessToken: false,
}),
).toEqual({
requestKind: 'runtime-guest',
puzzleRuntimeAuthMode: 'isolated',
});
});
test('falls back to default puzzle auth when isolated launch has account auth', () => {
expect(
resolvePlatformRecommendRuntimeAuthPlan({
embedded: false,
allowRuntimeGuestAuth: true,
authUserId: 'user-1',
hasStoredAccessToken: false,
}),
).toEqual({
requestKind: 'none',
puzzleRuntimeAuthMode: 'default',
});
});
test('guest auth decision trims user id before treating account as signed in', () => {
expect(
shouldUsePlatformRecommendRuntimeGuestAuth({
allowRuntimeGuestAuth: true,
authUserId: ' ',
hasStoredAccessToken: false,
}),
).toBe(true);
});

View File

@@ -0,0 +1,58 @@
export type PlatformRecommendRuntimeRequestKind =
| 'none'
| 'background'
| 'runtime-guest';
export type PlatformPuzzleRuntimeAuthMode = 'default' | 'isolated';
export type PlatformRecommendRuntimeAuthPlan = {
requestKind: PlatformRecommendRuntimeRequestKind;
puzzleRuntimeAuthMode: PlatformPuzzleRuntimeAuthMode;
};
export type PlatformRecommendRuntimeAuthInput = {
embedded?: boolean;
allowRuntimeGuestAuth?: boolean;
authUserId?: string | null;
hasStoredAccessToken?: boolean;
};
function hasAccountAuth(input: {
authUserId?: string | null;
hasStoredAccessToken?: boolean;
}) {
return Boolean(input.authUserId?.trim() || input.hasStoredAccessToken);
}
export function shouldUsePlatformRecommendRuntimeGuestAuth(
input: Pick<
PlatformRecommendRuntimeAuthInput,
'allowRuntimeGuestAuth' | 'authUserId' | 'hasStoredAccessToken'
>,
) {
return Boolean(input.allowRuntimeGuestAuth) && !hasAccountAuth(input);
}
export function resolvePlatformRecommendRuntimeAuthPlan(
input: PlatformRecommendRuntimeAuthInput,
): PlatformRecommendRuntimeAuthPlan {
const embedded = Boolean(input.embedded);
const allowRuntimeGuestAuth = input.allowRuntimeGuestAuth ?? embedded;
const useRuntimeGuestAuth = shouldUsePlatformRecommendRuntimeGuestAuth({
allowRuntimeGuestAuth,
authUserId: input.authUserId,
hasStoredAccessToken: input.hasStoredAccessToken,
});
if (useRuntimeGuestAuth) {
return {
requestKind: 'runtime-guest',
puzzleRuntimeAuthMode: 'isolated',
};
}
return {
requestKind: embedded ? 'background' : 'none',
puzzleRuntimeAuthMode: 'default',
};
}