diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index 9c280b36..4e2aec4d 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -44,7 +44,8 @@ - 背景:Match3D、SquareHole、Puzzle、Jump Hop 等 runtime client 重复手写 path segment 编码、JSON header / body、runtime guest token、auth options 和 retry options,新增玩法容易遗漏同一请求骨架。 - 决策:新增 `src/services/runtimeRequest.ts`,以 `buildRuntimeApiPath` 统一 runtime path 编码,以 `requestRuntimeJson` 统一 JSON 请求、runtime guest auth 和 retry 合并。Match3D 与 SquareHole runtime client 已先迁移,保留原导出函数名、错误文案、返回契约和重试常量。 -- 影响范围:`src/services/runtimeRequest.ts`、Match3D runtime client、SquareHole runtime client、后续 Puzzle / Jump Hop / Visual Novel / Bark Battle / Big Fish runtime client 迁移。 +- 追加决策:Big Fish 与 Bark Battle runtime client 也迁入 `runtimeRequest.ts`;玩法专属 payload 归一化(如 Bark Battle start / finish 自动补 `workId`、`runId`)仍留在各玩法 client,通用 Module 只承接请求骨架。 +- 影响范围:`src/services/runtimeRequest.ts`、Match3D / SquareHole / Big Fish / Bark Battle runtime client、后续 Puzzle / Jump Hop / Visual Novel runtime client 迁移。 - 验证方式:`npm run test -- src/services/runtimeRequest.test.ts src/services/recommendedRuntimeGuestLaunch.test.ts src/services/match3d-runtime/match3dRuntimeAdapter.test.ts`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。 - 关联文档:`docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md`。 diff --git a/docs/README.md b/docs/README.md index 39054240..36579f3f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -43,7 +43,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_ 创作中心作品架打开动作由 `CreationWorkShelfItem.actions.open` 统一承载,Hub 不再按玩法 `kind` 分发,规则见 [【前端架构】WorkShelfModule收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91WorkShelfModule%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 -小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D 与 SquareHole 已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 +小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish 与 Bark Battle 已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 公开作品分类、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicGalleryViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 diff --git a/docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md b/docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md index 4c055fc3..51ef3431 100644 --- a/docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md +++ b/docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md @@ -11,7 +11,7 @@ - `buildRuntimeApiPath(basePath, ...segments)`:统一对 runtime path segment 执行 `encodeURIComponent`。 - `requestRuntimeJson(params)`:统一设置 method、JSON body、`Content-Type`、runtime guest `Authorization`、auth options 和 retry options。 -`match3dRuntimeClient.ts` 与 `squareHoleRuntimeClient.ts` 已迁入此 **Module**,并保留原有导出函数名、错误文案、返回契约和重试常量。点击 / 投入等玩法专属返回映射仍留在各自 client 内,避免把领域规则塞进通用请求 **Implementation**。 +`match3dRuntimeClient.ts`、`squareHoleRuntimeClient.ts`、`bigFishRuntimeClient.ts` 与 `barkBattleRuntimeClient.ts` 已迁入此 **Module**,并保留原有导出函数名、错误文案、返回契约和重试常量。点击 / 投入 / 成绩提交等玩法专属 payload 归一化仍留在各自 client 内,避免把领域规则塞进通用请求 **Implementation**。 ## 约定 @@ -22,7 +22,7 @@ ## 后续深化 -下一批可迁移 Puzzle、Jump Hop、Visual Novel、Bark Battle 与 Big Fish runtime client。迁移顺序以测试覆盖和请求形状接近度为准,优先迁移已有 guest launch 或 client 单测覆盖的函数。 +下一批可迁移 Puzzle、Jump Hop 与 Visual Novel runtime client。迁移顺序以测试覆盖和请求形状接近度为准,优先迁移已有 guest launch 或 client 单测覆盖的函数。 ## 验证 diff --git a/src/services/bark-battle-runtime/barkBattleRuntimeClient.ts b/src/services/bark-battle-runtime/barkBattleRuntimeClient.ts index 211cfdad..c9fe5d91 100644 --- a/src/services/bark-battle-runtime/barkBattleRuntimeClient.ts +++ b/src/services/bark-battle-runtime/barkBattleRuntimeClient.ts @@ -5,15 +5,11 @@ import type { BarkBattleRunStartResponse, BarkBattleRuntimeConfig, } from '../../../packages/shared/src/contracts/barkBattle'; -import { - type ApiRetryOptions, - requestJson, -} from '../apiClient'; -import { - buildRuntimeGuestAuthOptions, - buildRuntimeGuestHeaders, - type RuntimeGuestRequestOptions, -} from '../runtimeGuestAuth'; +import { type ApiRetryOptions } from '../apiClient'; +import { type RuntimeGuestRequestOptions } from '../runtimeGuestAuth'; +import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest'; + +const BARK_BATTLE_RUNTIME_API_BASE = '/api/runtime/bark-battle'; const BARK_BATTLE_RUNTIME_READ_RETRY: ApiRetryOptions = { maxRetries: 1, @@ -34,16 +30,17 @@ export function getBarkBattleRuntimeConfig( workId: string, options: BarkBattleRuntimeRequestOptions = {}, ) { - const requestOptions = buildRuntimeGuestAuthOptions(options); - return requestJson( - `/api/runtime/bark-battle/works/${encodeURIComponent(workId)}/config`, - { method: 'GET', headers: buildRuntimeGuestHeaders(options) }, - '读取汪汪声浪大作战配置失败', - { - retry: BARK_BATTLE_RUNTIME_READ_RETRY, - ...requestOptions, - }, - ); + return requestRuntimeJson({ + url: buildRuntimeApiPath( + BARK_BATTLE_RUNTIME_API_BASE, + 'works', + workId, + 'config', + ), + fallbackMessage: '读取汪汪声浪大作战配置失败', + retry: BARK_BATTLE_RUNTIME_READ_RETRY, + requestOptions: options, + }); } export function startBarkBattleRun( @@ -51,39 +48,34 @@ export function startBarkBattleRun( payload: Partial = {}, options: BarkBattleRuntimeRequestOptions = {}, ) { - const requestOptions = buildRuntimeGuestAuthOptions(options); - return requestJson( - `/api/runtime/bark-battle/works/${encodeURIComponent(workId)}/runs`, - { - method: 'POST', - headers: buildRuntimeGuestHeaders(options, { 'Content-Type': 'application/json' }), - body: JSON.stringify({ - ...payload, - workId: payload.workId ?? workId, - }), + return requestRuntimeJson({ + url: buildRuntimeApiPath( + BARK_BATTLE_RUNTIME_API_BASE, + 'works', + workId, + 'runs', + ), + method: 'POST', + jsonBody: { + ...payload, + workId: payload.workId ?? workId, }, - '启动汪汪声浪大作战正式局失败', - { - retry: BARK_BATTLE_RUNTIME_WRITE_RETRY, - ...requestOptions, - }, - ); + fallbackMessage: '启动汪汪声浪大作战正式局失败', + retry: BARK_BATTLE_RUNTIME_WRITE_RETRY, + requestOptions: options, + }); } export function getBarkBattleRun( runId: string, options: BarkBattleRuntimeRequestOptions = {}, ) { - const requestOptions = buildRuntimeGuestAuthOptions(options); - return requestJson( - `/api/runtime/bark-battle/runs/${encodeURIComponent(runId)}`, - { method: 'GET', headers: buildRuntimeGuestHeaders(options) }, - '读取汪汪声浪大作战单局失败', - { - retry: BARK_BATTLE_RUNTIME_READ_RETRY, - ...requestOptions, - }, - ); + return requestRuntimeJson({ + url: buildRuntimeApiPath(BARK_BATTLE_RUNTIME_API_BASE, 'runs', runId), + fallbackMessage: '读取汪汪声浪大作战单局失败', + retry: BARK_BATTLE_RUNTIME_READ_RETRY, + requestOptions: options, + }); } export function finishBarkBattleRun( @@ -91,21 +83,20 @@ export function finishBarkBattleRun( payload: BarkBattleRunFinishRequest, options: BarkBattleRuntimeRequestOptions = {}, ) { - const requestOptions = buildRuntimeGuestAuthOptions(options); - return requestJson( - `/api/runtime/bark-battle/runs/${encodeURIComponent(runId)}/finish`, - { - method: 'POST', - headers: buildRuntimeGuestHeaders(options, { 'Content-Type': 'application/json' }), - body: JSON.stringify({ - ...payload, - runId: payload.runId ?? runId, - }), + return requestRuntimeJson({ + url: buildRuntimeApiPath( + BARK_BATTLE_RUNTIME_API_BASE, + 'runs', + runId, + 'finish', + ), + method: 'POST', + jsonBody: { + ...payload, + runId: payload.runId ?? runId, }, - '提交汪汪声浪大作战成绩失败', - { - retry: BARK_BATTLE_RUNTIME_WRITE_RETRY, - ...requestOptions, - }, - ); + fallbackMessage: '提交汪汪声浪大作战成绩失败', + retry: BARK_BATTLE_RUNTIME_WRITE_RETRY, + requestOptions: options, + }); } diff --git a/src/services/big-fish-runtime/bigFishRuntimeClient.ts b/src/services/big-fish-runtime/bigFishRuntimeClient.ts index 16b02528..39407713 100644 --- a/src/services/big-fish-runtime/bigFishRuntimeClient.ts +++ b/src/services/big-fish-runtime/bigFishRuntimeClient.ts @@ -4,16 +4,11 @@ import type { SubmitBigFishInputRequest, } from '../../../packages/shared/src/contracts/bigFish'; import type { BigFishWorksResponse } from '../../../packages/shared/src/contracts/bigFishWorkSummary'; -import { - type ApiRetryOptions, - requestJson, -} from '../apiClient'; -import { - buildRuntimeGuestAuthOptions, - buildRuntimeGuestHeaders, - type RuntimeGuestRequestOptions, -} from '../runtimeGuestAuth'; +import { type ApiRetryOptions } from '../apiClient'; +import { type RuntimeGuestRequestOptions } from '../runtimeGuestAuth'; +import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest'; +const BIG_FISH_RUNTIME_API_BASE = '/api/runtime/big-fish'; const BIG_FISH_RUNTIME_WRITE_RETRY: ApiRetryOptions = { maxRetries: 1, baseDelayMs: 120, @@ -30,51 +25,44 @@ export function recordBigFishPlay( payload: RecordBigFishPlayRequest, options: BigFishRuntimeRequestOptions = {}, ) { - const requestOptions = buildRuntimeGuestAuthOptions(options); - return requestJson( - `/api/runtime/big-fish/sessions/${encodeURIComponent(sessionId)}/play`, - { - method: 'POST', - headers: buildRuntimeGuestHeaders(options, { - 'Content-Type': 'application/json', - }), - body: JSON.stringify(payload), - }, - '记录大鱼吃小鱼游玩失败', - { - retry: BIG_FISH_RUNTIME_WRITE_RETRY, - ...requestOptions, - }, - ); + return requestRuntimeJson({ + url: buildRuntimeApiPath( + BIG_FISH_RUNTIME_API_BASE, + 'sessions', + sessionId, + 'play', + ), + method: 'POST', + jsonBody: payload, + fallbackMessage: '记录大鱼吃小鱼游玩失败', + retry: BIG_FISH_RUNTIME_WRITE_RETRY, + requestOptions: options, + }); } export function startBigFishRun( sessionId: string, options: BigFishRuntimeRequestOptions = {}, ) { - const requestOptions = buildRuntimeGuestAuthOptions(options); - return requestJson( - `/api/runtime/big-fish/sessions/${encodeURIComponent(sessionId)}/runs`, - { - method: 'POST', - headers: buildRuntimeGuestHeaders(options), - }, - '启动大鱼吃小鱼玩法失败', - { - retry: BIG_FISH_RUNTIME_WRITE_RETRY, - ...requestOptions, - }, - ); + return requestRuntimeJson({ + url: buildRuntimeApiPath( + BIG_FISH_RUNTIME_API_BASE, + 'sessions', + sessionId, + 'runs', + ), + method: 'POST', + fallbackMessage: '启动大鱼吃小鱼玩法失败', + retry: BIG_FISH_RUNTIME_WRITE_RETRY, + requestOptions: options, + }); } export function getBigFishRun(runId: string) { - return requestJson( - `/api/runtime/big-fish/runs/${encodeURIComponent(runId)}`, - { - method: 'GET', - }, - '读取大鱼吃小鱼玩法失败', - ); + return requestRuntimeJson({ + url: buildRuntimeApiPath(BIG_FISH_RUNTIME_API_BASE, 'runs', runId), + fallbackMessage: '读取大鱼吃小鱼玩法失败', + }); } export function submitBigFishInput( @@ -82,20 +70,12 @@ export function submitBigFishInput( payload: SubmitBigFishInputRequest, options: BigFishRuntimeRequestOptions = {}, ) { - const requestOptions = buildRuntimeGuestAuthOptions(options); - return requestJson( - `/api/runtime/big-fish/runs/${encodeURIComponent(runId)}/input`, - { - method: 'POST', - headers: buildRuntimeGuestHeaders(options, { - 'Content-Type': 'application/json', - }), - body: JSON.stringify(payload), - }, - '同步大鱼吃小鱼输入失败', - { - retry: BIG_FISH_RUNTIME_WRITE_RETRY, - ...requestOptions, - }, - ); + return requestRuntimeJson({ + url: buildRuntimeApiPath(BIG_FISH_RUNTIME_API_BASE, 'runs', runId, 'input'), + method: 'POST', + jsonBody: payload, + fallbackMessage: '同步大鱼吃小鱼输入失败', + retry: BIG_FISH_RUNTIME_WRITE_RETRY, + requestOptions: options, + }); } diff --git a/src/services/recommendedRuntimeGuestLaunch.test.ts b/src/services/recommendedRuntimeGuestLaunch.test.ts index eb5307c0..514f00c2 100644 --- a/src/services/recommendedRuntimeGuestLaunch.test.ts +++ b/src/services/recommendedRuntimeGuestLaunch.test.ts @@ -13,8 +13,8 @@ vi.mock('./apiClient', async () => { }; }); -import { startBigFishRun } from './big-fish-runtime/bigFishRuntimeClient'; import { startBarkBattleRun } from './bark-battle-runtime/barkBattleRuntimeClient'; +import { startBigFishRun } from './big-fish-runtime/bigFishRuntimeClient'; import { startJumpHopRuntimeRun } from './jump-hop/jumpHopClient'; import { startMatch3DRun } from './match3d-runtime/match3dRuntimeClient'; import {