refactor: 迁移拼图与跳跃 runtime 请求骨架
This commit is contained in:
@@ -45,7 +45,8 @@
|
|||||||
- 背景:Match3D、SquareHole、Puzzle、Jump Hop 等 runtime client 重复手写 path segment 编码、JSON header / body、runtime guest token、auth options 和 retry options,新增玩法容易遗漏同一请求骨架。
|
- 背景: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`,以 `buildRuntimeApiPath` 统一 runtime path 编码,以 `requestRuntimeJson` 统一 JSON 请求、runtime guest auth 和 retry 合并。Match3D 与 SquareHole runtime client 已先迁移,保留原导出函数名、错误文案、返回契约和重试常量。
|
||||||
- 追加决策:Big Fish 与 Bark Battle runtime client 也迁入 `runtimeRequest.ts`;玩法专属 payload 归一化(如 Bark Battle start / finish 自动补 `workId`、`runId`)仍留在各玩法 client,通用 Module 只承接请求骨架。
|
- 追加决策: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 迁移。
|
- 追加决策:Puzzle 的 start / get / swap / drag / next-level / leaderboard 与 Jump Hop 的 start / jump / restart 也迁入 `runtimeRequest.ts`;Puzzle `pause` 与 `props` 仍保留原账号态 auth options,不直接接入 runtime guest auth。
|
||||||
|
- 影响范围:`src/services/runtimeRequest.ts`、Match3D / SquareHole / Big Fish / Bark Battle / Puzzle / Jump Hop runtime client、后续 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 通过。
|
- 验证方式:`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`。
|
- 关联文档:`docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.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)。
|
创作中心作品架打开动作由 `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、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)。
|
小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求与 Jump Hop 正式 run 请求已先迁移,规则见 [【前端架构】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)。
|
公开作品分类、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `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)。
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
- `buildRuntimeApiPath(basePath, ...segments)`:统一对 runtime path segment 执行 `encodeURIComponent`。
|
- `buildRuntimeApiPath(basePath, ...segments)`:统一对 runtime path segment 执行 `encodeURIComponent`。
|
||||||
- `requestRuntimeJson(params)`:统一设置 method、JSON body、`Content-Type`、runtime guest `Authorization`、auth options 和 retry options。
|
- `requestRuntimeJson(params)`:统一设置 method、JSON body、`Content-Type`、runtime guest `Authorization`、auth options 和 retry options。
|
||||||
|
|
||||||
`match3dRuntimeClient.ts`、`squareHoleRuntimeClient.ts`、`bigFishRuntimeClient.ts` 与 `barkBattleRuntimeClient.ts` 已迁入此 **Module**,并保留原有导出函数名、错误文案、返回契约和重试常量。点击 / 投入 / 成绩提交等玩法专属 payload 归一化仍留在各自 client 内,避免把领域规则塞进通用请求 **Implementation**。
|
`match3dRuntimeClient.ts`、`squareHoleRuntimeClient.ts`、`bigFishRuntimeClient.ts`、`barkBattleRuntimeClient.ts`、`puzzleRuntimeClient.ts` 的公开 / 推荐运行态请求,以及 `jumpHopClient.ts` 的正式 run 请求已迁入此 **Module**,并保留原有导出函数名、错误文案、返回契约和重试常量。点击 / 投入 / 成绩提交等玩法专属 payload 归一化仍留在各自 client 内,避免把领域规则塞进通用请求 **Implementation**。
|
||||||
|
|
||||||
## 约定
|
## 约定
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
## 后续深化
|
## 后续深化
|
||||||
|
|
||||||
下一批可迁移 Puzzle、Jump Hop 与 Visual Novel runtime client。迁移顺序以测试覆盖和请求形状接近度为准,优先迁移已有 guest launch 或 client 单测覆盖的函数。
|
下一批可迁移 Visual Novel runtime client,并评估 Puzzle `pause` / `props` 是否应继续保留账号态 auth options。迁移顺序以测试覆盖和请求形状接近度为准,优先迁移已有 guest launch 或 client 单测覆盖的函数。
|
||||||
|
|
||||||
## 验证
|
## 验证
|
||||||
|
|
||||||
|
|||||||
108
src/services/jump-hop/jumpHopClient.runtime.test.ts
Normal file
108
src/services/jump-hop/jumpHopClient.runtime.test.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
const apiClientMocks = vi.hoisted(() => ({
|
||||||
|
requestJson: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../apiClient', async () => {
|
||||||
|
const actual =
|
||||||
|
await vi.importActual<typeof import('../apiClient')>('../apiClient');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
requestJson: apiClientMocks.requestJson,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import {
|
||||||
|
restartJumpHopRuntimeRun,
|
||||||
|
startJumpHopRuntimeRun,
|
||||||
|
submitJumpHopJump,
|
||||||
|
} from './jumpHopClient';
|
||||||
|
|
||||||
|
describe('jumpHopClient runtime requests', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.spyOn(Date, 'now').mockReturnValue(1780000000000);
|
||||||
|
apiClientMocks.requestJson.mockResolvedValue({ runId: 'run-1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('starts runs through the shared runtime request skeleton', async () => {
|
||||||
|
await startJumpHopRuntimeRun('profile/1', {
|
||||||
|
runtimeGuestToken: 'runtime-guest-token',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
|
||||||
|
'/api/runtime/jump-hop/runs',
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer runtime-guest-token',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ profileId: 'profile/1' }),
|
||||||
|
}),
|
||||||
|
'启动跳一跳运行态失败',
|
||||||
|
expect.objectContaining({
|
||||||
|
skipAuth: true,
|
||||||
|
skipRefresh: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits jump input with a generated client event id', async () => {
|
||||||
|
await submitJumpHopJump(
|
||||||
|
'run/1',
|
||||||
|
{ chargeMs: 320 },
|
||||||
|
{ runtimeGuestToken: 'runtime-guest-token' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
|
||||||
|
'/api/runtime/jump-hop/runs/run%2F1/jump',
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer runtime-guest-token',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
chargeMs: 320,
|
||||||
|
clientEventId: 'jump-run/1-1780000000000',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'提交跳一跳起跳失败',
|
||||||
|
expect.objectContaining({
|
||||||
|
skipAuth: true,
|
||||||
|
skipRefresh: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('restarts runs with the same guest auth request skeleton', async () => {
|
||||||
|
await restartJumpHopRuntimeRun('run/1', {
|
||||||
|
runtimeGuestToken: 'runtime-guest-token',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
|
||||||
|
'/api/runtime/jump-hop/runs/run%2F1/restart',
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer runtime-guest-token',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
clientActionId: 'restart-run/1-1780000000000',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'重新开始跳一跳失败',
|
||||||
|
expect.objectContaining({
|
||||||
|
skipAuth: true,
|
||||||
|
skipRefresh: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,8 +12,8 @@ import type {
|
|||||||
JumpHopWorkDetailResponse,
|
JumpHopWorkDetailResponse,
|
||||||
JumpHopWorkMutationResponse,
|
JumpHopWorkMutationResponse,
|
||||||
JumpHopWorkProfileResponse,
|
JumpHopWorkProfileResponse,
|
||||||
JumpHopWorksResponse,
|
|
||||||
JumpHopWorkspaceCreateRequest,
|
JumpHopWorkspaceCreateRequest,
|
||||||
|
JumpHopWorksResponse,
|
||||||
JumpHopWorkSummaryResponse,
|
JumpHopWorkSummaryResponse,
|
||||||
} from '../../../packages/shared/src/contracts/jumpHop';
|
} from '../../../packages/shared/src/contracts/jumpHop';
|
||||||
import {
|
import {
|
||||||
@@ -21,11 +21,8 @@ import {
|
|||||||
requestJson,
|
requestJson,
|
||||||
} from '../apiClient';
|
} from '../apiClient';
|
||||||
import { createCreationAgentClient } from '../creation-agent';
|
import { createCreationAgentClient } from '../creation-agent';
|
||||||
import {
|
import { type RuntimeGuestRequestOptions } from '../runtimeGuestAuth';
|
||||||
buildRuntimeGuestAuthOptions,
|
import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest';
|
||||||
buildRuntimeGuestHeaders,
|
|
||||||
type RuntimeGuestRequestOptions,
|
|
||||||
} from '../runtimeGuestAuth';
|
|
||||||
|
|
||||||
const JUMP_HOP_API_BASE = '/api/creation/jump-hop/sessions';
|
const JUMP_HOP_API_BASE = '/api/creation/jump-hop/sessions';
|
||||||
const JUMP_HOP_WORKS_API_BASE = '/api/creation/jump-hop/works';
|
const JUMP_HOP_WORKS_API_BASE = '/api/creation/jump-hop/works';
|
||||||
@@ -51,8 +48,8 @@ export type {
|
|||||||
JumpHopWorkDetailResponse,
|
JumpHopWorkDetailResponse,
|
||||||
JumpHopWorkMutationResponse,
|
JumpHopWorkMutationResponse,
|
||||||
JumpHopWorkProfileResponse,
|
JumpHopWorkProfileResponse,
|
||||||
JumpHopWorksResponse,
|
|
||||||
JumpHopWorkspaceCreateRequest,
|
JumpHopWorkspaceCreateRequest,
|
||||||
|
JumpHopWorksResponse,
|
||||||
};
|
};
|
||||||
export type CreateJumpHopSessionRequest = {
|
export type CreateJumpHopSessionRequest = {
|
||||||
themeText: string;
|
themeText: string;
|
||||||
@@ -234,22 +231,13 @@ export async function startJumpHopRuntimeRun(
|
|||||||
profileId: string,
|
profileId: string,
|
||||||
options: JumpHopRuntimeRequestOptions = {},
|
options: JumpHopRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
return requestRuntimeJson<JumpHopRunResponse>({
|
||||||
return requestJson<JumpHopRunResponse>(
|
url: buildRuntimeApiPath(JUMP_HOP_RUNTIME_API_BASE, 'runs'),
|
||||||
`${JUMP_HOP_RUNTIME_API_BASE}/runs`,
|
method: 'POST',
|
||||||
{
|
jsonBody: { profileId },
|
||||||
method: 'POST',
|
fallbackMessage: '启动跳一跳运行态失败',
|
||||||
headers: {
|
requestOptions: options,
|
||||||
'content-type': 'application/json',
|
});
|
||||||
...buildRuntimeGuestHeaders(options),
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ profileId }),
|
|
||||||
},
|
|
||||||
'启动跳一跳运行态失败',
|
|
||||||
{
|
|
||||||
...requestOptions,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function submitJumpHopJump(
|
export async function submitJumpHopJump(
|
||||||
@@ -257,47 +245,38 @@ export async function submitJumpHopJump(
|
|||||||
payload: { chargeMs: number },
|
payload: { chargeMs: number },
|
||||||
options: JumpHopRuntimeRequestOptions = {},
|
options: JumpHopRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
|
||||||
const requestPayload = {
|
const requestPayload = {
|
||||||
chargeMs: payload.chargeMs,
|
chargeMs: payload.chargeMs,
|
||||||
clientEventId: `jump-${runId}-${Date.now()}`,
|
clientEventId: `jump-${runId}-${Date.now()}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return requestJson<JumpHopRunResponse>(
|
return requestRuntimeJson<JumpHopRunResponse>({
|
||||||
`${JUMP_HOP_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/jump`,
|
url: buildRuntimeApiPath(JUMP_HOP_RUNTIME_API_BASE, 'runs', runId, 'jump'),
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
jsonBody: requestPayload,
|
||||||
headers: {
|
fallbackMessage: '提交跳一跳起跳失败',
|
||||||
'content-type': 'application/json',
|
requestOptions: options,
|
||||||
...buildRuntimeGuestHeaders(options),
|
});
|
||||||
},
|
|
||||||
body: JSON.stringify(requestPayload),
|
|
||||||
},
|
|
||||||
'提交跳一跳起跳失败',
|
|
||||||
requestOptions,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function restartJumpHopRuntimeRun(
|
export async function restartJumpHopRuntimeRun(
|
||||||
runId: string,
|
runId: string,
|
||||||
options: JumpHopRuntimeRequestOptions = {},
|
options: JumpHopRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
return requestRuntimeJson<JumpHopRunResponse>({
|
||||||
return requestJson<JumpHopRunResponse>(
|
url: buildRuntimeApiPath(
|
||||||
`${JUMP_HOP_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/restart`,
|
JUMP_HOP_RUNTIME_API_BASE,
|
||||||
{
|
'runs',
|
||||||
method: 'POST',
|
runId,
|
||||||
headers: {
|
'restart',
|
||||||
'content-type': 'application/json',
|
),
|
||||||
...buildRuntimeGuestHeaders(options),
|
method: 'POST',
|
||||||
},
|
jsonBody: {
|
||||||
body: JSON.stringify({
|
clientActionId: `restart-${runId}-${Date.now()}`,
|
||||||
clientActionId: `restart-${runId}-${Date.now()}`,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
'重新开始跳一跳失败',
|
fallbackMessage: '重新开始跳一跳失败',
|
||||||
requestOptions,
|
requestOptions: options,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const jumpHopClient = {
|
export const jumpHopClient = {
|
||||||
|
|||||||
92
src/services/puzzle-runtime/puzzleRuntimeClient.test.ts
Normal file
92
src/services/puzzle-runtime/puzzleRuntimeClient.test.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
const apiClientMocks = vi.hoisted(() => ({
|
||||||
|
requestJson: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../apiClient', async () => {
|
||||||
|
const actual =
|
||||||
|
await vi.importActual<typeof import('../apiClient')>('../apiClient');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
requestJson: apiClientMocks.requestJson,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import {
|
||||||
|
getPuzzleRun,
|
||||||
|
swapPuzzlePieces,
|
||||||
|
updatePuzzleRunPause,
|
||||||
|
} from './puzzleRuntimeClient';
|
||||||
|
|
||||||
|
describe('puzzleRuntimeClient', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
apiClientMocks.requestJson.mockResolvedValue({ runId: 'run-1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reads runs through the shared encoded runtime path', async () => {
|
||||||
|
await getPuzzleRun('run/1');
|
||||||
|
|
||||||
|
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
|
||||||
|
'/api/runtime/puzzle/runs/run%2F1',
|
||||||
|
{ method: 'GET' },
|
||||||
|
'读取拼图运行快照失败',
|
||||||
|
expect.objectContaining({
|
||||||
|
retry: expect.objectContaining({ maxRetries: 1 }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits puzzle swaps through the shared json request skeleton', async () => {
|
||||||
|
await swapPuzzlePieces('run/1', {
|
||||||
|
firstPieceId: 'piece-a',
|
||||||
|
secondPieceId: 'piece-b',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
|
||||||
|
'/api/runtime/puzzle/runs/run%2F1/swap',
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
firstPieceId: 'piece-a',
|
||||||
|
secondPieceId: 'piece-b',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'交换拼图块失败',
|
||||||
|
expect.objectContaining({
|
||||||
|
retry: expect.objectContaining({ retryUnsafeMethods: true }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps pause requests on account auth options instead of guest auth', async () => {
|
||||||
|
await updatePuzzleRunPause(
|
||||||
|
'run/1',
|
||||||
|
{ paused: true },
|
||||||
|
{
|
||||||
|
authImpact: 'local',
|
||||||
|
runtimeGuestToken: 'runtime-guest-token',
|
||||||
|
skipRefresh: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const [, init, , options] = apiClientMocks.requestJson.mock.calls[0];
|
||||||
|
expect(init).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ paused: true }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(init.headers).not.toHaveProperty('Authorization');
|
||||||
|
expect(options).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
authImpact: 'local',
|
||||||
|
skipRefresh: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(options).not.toMatchObject({ skipAuth: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
DragPuzzlePieceRequest,
|
|
||||||
AdvancePuzzleNextLevelRequest,
|
AdvancePuzzleNextLevelRequest,
|
||||||
|
DragPuzzlePieceRequest,
|
||||||
PuzzleRunResponse,
|
PuzzleRunResponse,
|
||||||
StartPuzzleRunRequest,
|
StartPuzzleRunRequest,
|
||||||
SubmitPuzzleLeaderboardRequest,
|
SubmitPuzzleLeaderboardRequest,
|
||||||
@@ -12,11 +12,8 @@ import {
|
|||||||
type ApiRetryOptions,
|
type ApiRetryOptions,
|
||||||
requestJson,
|
requestJson,
|
||||||
} from '../apiClient';
|
} from '../apiClient';
|
||||||
import {
|
import { type RuntimeGuestRequestOptions } from '../runtimeGuestAuth';
|
||||||
buildRuntimeGuestAuthOptions,
|
import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest';
|
||||||
buildRuntimeGuestHeaders,
|
|
||||||
type RuntimeGuestRequestOptions,
|
|
||||||
} from '../runtimeGuestAuth';
|
|
||||||
|
|
||||||
const PUZZLE_RUNTIME_API_BASE = '/api/runtime/puzzle/runs';
|
const PUZZLE_RUNTIME_API_BASE = '/api/runtime/puzzle/runs';
|
||||||
const PUZZLE_RUNTIME_READ_RETRY: ApiRetryOptions = {
|
const PUZZLE_RUNTIME_READ_RETRY: ApiRetryOptions = {
|
||||||
@@ -42,38 +39,25 @@ export async function startPuzzleRun(
|
|||||||
payload: StartPuzzleRunRequest,
|
payload: StartPuzzleRunRequest,
|
||||||
options: PuzzleRuntimeRequestOptions = {},
|
options: PuzzleRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
return requestRuntimeJson<PuzzleRunResponse>({
|
||||||
return requestJson<PuzzleRunResponse>(
|
url: PUZZLE_RUNTIME_API_BASE,
|
||||||
PUZZLE_RUNTIME_API_BASE,
|
method: 'POST',
|
||||||
{
|
jsonBody: payload,
|
||||||
method: 'POST',
|
fallbackMessage: '启动拼图玩法失败',
|
||||||
headers: buildRuntimeGuestHeaders(options, {
|
retry: PUZZLE_RUNTIME_WRITE_RETRY,
|
||||||
'Content-Type': 'application/json',
|
requestOptions: options,
|
||||||
}),
|
});
|
||||||
body: JSON.stringify(payload),
|
|
||||||
},
|
|
||||||
'启动拼图玩法失败',
|
|
||||||
{
|
|
||||||
retry: PUZZLE_RUNTIME_WRITE_RETRY,
|
|
||||||
...requestOptions,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取拼图运行态快照。
|
* 读取拼图运行态快照。
|
||||||
*/
|
*/
|
||||||
export async function getPuzzleRun(runId: string) {
|
export async function getPuzzleRun(runId: string) {
|
||||||
return requestJson<PuzzleRunResponse>(
|
return requestRuntimeJson<PuzzleRunResponse>({
|
||||||
`${PUZZLE_RUNTIME_API_BASE}/${encodeURIComponent(runId)}`,
|
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId),
|
||||||
{
|
fallbackMessage: '读取拼图运行快照失败',
|
||||||
method: 'GET',
|
retry: PUZZLE_RUNTIME_READ_RETRY,
|
||||||
},
|
});
|
||||||
'读取拼图运行快照失败',
|
|
||||||
{
|
|
||||||
retry: PUZZLE_RUNTIME_READ_RETRY,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,18 +67,13 @@ export async function swapPuzzlePieces(
|
|||||||
runId: string,
|
runId: string,
|
||||||
payload: SwapPuzzlePiecesRequest,
|
payload: SwapPuzzlePiecesRequest,
|
||||||
) {
|
) {
|
||||||
return requestJson<PuzzleRunResponse>(
|
return requestRuntimeJson<PuzzleRunResponse>({
|
||||||
`${PUZZLE_RUNTIME_API_BASE}/${encodeURIComponent(runId)}/swap`,
|
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'swap'),
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
jsonBody: payload,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
fallbackMessage: '交换拼图块失败',
|
||||||
body: JSON.stringify(payload),
|
retry: PUZZLE_RUNTIME_WRITE_RETRY,
|
||||||
},
|
});
|
||||||
'交换拼图块失败',
|
|
||||||
{
|
|
||||||
retry: PUZZLE_RUNTIME_WRITE_RETRY,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,18 +83,13 @@ export async function dragPuzzlePieceOrGroup(
|
|||||||
runId: string,
|
runId: string,
|
||||||
payload: DragPuzzlePieceRequest,
|
payload: DragPuzzlePieceRequest,
|
||||||
) {
|
) {
|
||||||
return requestJson<PuzzleRunResponse>(
|
return requestRuntimeJson<PuzzleRunResponse>({
|
||||||
`${PUZZLE_RUNTIME_API_BASE}/${encodeURIComponent(runId)}/drag`,
|
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'drag'),
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
jsonBody: payload,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
fallbackMessage: '拖动拼图块失败',
|
||||||
body: JSON.stringify(payload),
|
retry: PUZZLE_RUNTIME_WRITE_RETRY,
|
||||||
},
|
});
|
||||||
'拖动拼图块失败',
|
|
||||||
{
|
|
||||||
retry: PUZZLE_RUNTIME_WRITE_RETRY,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,7 +100,6 @@ export async function advancePuzzleNextLevel(
|
|||||||
payload: AdvancePuzzleNextLevelRequest = {},
|
payload: AdvancePuzzleNextLevelRequest = {},
|
||||||
options: PuzzleRuntimeRequestOptions = {},
|
options: PuzzleRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
|
||||||
const targetProfileId = payload.targetProfileId?.trim() ?? '';
|
const targetProfileId = payload.targetProfileId?.trim() ?? '';
|
||||||
const preferSimilarWork = payload.preferSimilarWork === true;
|
const preferSimilarWork = payload.preferSimilarWork === true;
|
||||||
const requestPayload = {
|
const requestPayload = {
|
||||||
@@ -134,27 +107,14 @@ export async function advancePuzzleNextLevel(
|
|||||||
...(preferSimilarWork ? { preferSimilarWork: true } : {}),
|
...(preferSimilarWork ? { preferSimilarWork: true } : {}),
|
||||||
};
|
};
|
||||||
const hasRequestPayload = Object.keys(requestPayload).length > 0;
|
const hasRequestPayload = Object.keys(requestPayload).length > 0;
|
||||||
return requestJson<PuzzleRunResponse>(
|
return requestRuntimeJson<PuzzleRunResponse>({
|
||||||
`${PUZZLE_RUNTIME_API_BASE}/${encodeURIComponent(runId)}/next-level`,
|
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'next-level'),
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
...(hasRequestPayload ? { jsonBody: requestPayload } : {}),
|
||||||
...(hasRequestPayload
|
fallbackMessage: '进入下一关失败',
|
||||||
? {
|
retry: PUZZLE_RUNTIME_WRITE_RETRY,
|
||||||
headers: buildRuntimeGuestHeaders(options, {
|
requestOptions: options,
|
||||||
'Content-Type': 'application/json',
|
});
|
||||||
}),
|
|
||||||
body: JSON.stringify(requestPayload),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
headers: buildRuntimeGuestHeaders(options),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
'进入下一关失败',
|
|
||||||
{
|
|
||||||
retry: PUZZLE_RUNTIME_WRITE_RETRY,
|
|
||||||
...requestOptions,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,22 +125,14 @@ export async function submitPuzzleLeaderboard(
|
|||||||
payload: SubmitPuzzleLeaderboardRequest,
|
payload: SubmitPuzzleLeaderboardRequest,
|
||||||
options: PuzzleRuntimeRequestOptions = {},
|
options: PuzzleRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
return requestRuntimeJson<PuzzleRunResponse>({
|
||||||
return requestJson<PuzzleRunResponse>(
|
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'leaderboard'),
|
||||||
`${PUZZLE_RUNTIME_API_BASE}/${encodeURIComponent(runId)}/leaderboard`,
|
method: 'POST',
|
||||||
{
|
jsonBody: payload,
|
||||||
method: 'POST',
|
fallbackMessage: '提交拼图排行榜失败',
|
||||||
headers: buildRuntimeGuestHeaders(options, {
|
retry: PUZZLE_RUNTIME_LEADERBOARD_RETRY,
|
||||||
'Content-Type': 'application/json',
|
requestOptions: options,
|
||||||
}),
|
});
|
||||||
body: JSON.stringify(payload),
|
|
||||||
},
|
|
||||||
'提交拼图排行榜失败',
|
|
||||||
{
|
|
||||||
retry: PUZZLE_RUNTIME_LEADERBOARD_RETRY,
|
|
||||||
...requestOptions,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -192,7 +144,7 @@ export async function updatePuzzleRunPause(
|
|||||||
options: PuzzleRuntimeRequestOptions = {},
|
options: PuzzleRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
return requestJson<PuzzleRunResponse>(
|
return requestJson<PuzzleRunResponse>(
|
||||||
`${PUZZLE_RUNTIME_API_BASE}/${encodeURIComponent(runId)}/pause`,
|
buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'pause'),
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -218,7 +170,7 @@ export async function usePuzzleRuntimeProp(
|
|||||||
options: PuzzleRuntimeRequestOptions = {},
|
options: PuzzleRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
return requestJson<PuzzleRunResponse>(
|
return requestJson<PuzzleRunResponse>(
|
||||||
`${PUZZLE_RUNTIME_API_BASE}/${encodeURIComponent(runId)}/props`,
|
buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'props'),
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|||||||
Reference in New Issue
Block a user