refactor: 收口 runtime client 请求骨架
This commit is contained in:
@@ -40,6 +40,14 @@
|
|||||||
- 验证方式:`npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。
|
- 验证方式:`npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。
|
||||||
- 关联文档:`docs/technical/【前端架构】WorkShelfModule收口计划-2026-06-03.md`。
|
- 关联文档:`docs/technical/【前端架构】WorkShelfModule收口计划-2026-06-03.md`。
|
||||||
|
|
||||||
|
## 2026-06-03 Runtime Client Family 请求骨架收口
|
||||||
|
|
||||||
|
- 背景: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 迁移。
|
||||||
|
- 验证方式:`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`。
|
||||||
|
|
||||||
## 2026-06-03 最近创作只复用创作模板入口
|
## 2026-06-03 最近创作只复用创作模板入口
|
||||||
|
|
||||||
- 背景:底部加号创作入口的“最近创作”最初由真实作品架摘要驱动,但页面曾按作品标题、摘要和生成状态渲染独立最近创作卡,和其它模板页签的卡片样式及点击语义不一致。
|
- 背景:底部加号创作入口的“最近创作”最初由真实作品架摘要驱动,但页面曾按作品标题、摘要和生成状态渲染独立最近创作卡,和其它模板页签的卡片样式及点击语义不一致。
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ 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 已先迁移,规则见 [【前端架构】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)。
|
||||||
|
|
||||||
## 推荐阅读顺序
|
## 推荐阅读顺序
|
||||||
|
|
||||||
1. 先看 [经验沉淀](./experience/README.md),快速建立这个项目的开发共识。
|
1. 先看 [经验沉淀](./experience/README.md),快速建立这个项目的开发共识。
|
||||||
|
|||||||
32
docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md
Normal file
32
docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 【前端架构】Runtime Client Family 收口计划
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
多个小游戏 runtime client 都重复实现路径编码、JSON header / body、runtime guest token、认证影响策略和重试参数。重复逻辑分散在各玩法文件后,新增玩法容易遗漏 guest auth 或 retry 语义,也让测试必须逐玩法检查同一请求骨架。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
新增 `src/services/runtimeRequest.ts`,作为 Runtime Client Family 的请求 **Module**。其 **Interface** 包含:
|
||||||
|
|
||||||
|
- `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**。
|
||||||
|
|
||||||
|
## 约定
|
||||||
|
|
||||||
|
- Runtime client 不再手写 `encodeURIComponent` 拼 path;应优先使用 `buildRuntimeApiPath`。
|
||||||
|
- Runtime JSON 请求不再手写 `Content-Type`、guest `Authorization` 和 `buildRuntimeGuestAuthOptions` 合并;应优先使用 `requestRuntimeJson`。
|
||||||
|
- 玩法专属 payload 归一化、返回值适配和中文错误文案仍属于各玩法 client。
|
||||||
|
- 每迁移一个 client,必须保留原导出函数名与原调用方契约。
|
||||||
|
|
||||||
|
## 后续深化
|
||||||
|
|
||||||
|
下一批可迁移 Puzzle、Jump Hop、Visual Novel、Bark Battle 与 Big Fish runtime client。迁移顺序以测试覆盖和请求形状接近度为准,优先迁移已有 guest launch 或 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
|
||||||
@@ -10,14 +10,11 @@ import type {
|
|||||||
} from '../../../packages/shared/src/contracts/match3dRuntime';
|
} from '../../../packages/shared/src/contracts/match3dRuntime';
|
||||||
import {
|
import {
|
||||||
type ApiRetryOptions,
|
type ApiRetryOptions,
|
||||||
requestJson,
|
|
||||||
} from '../apiClient';
|
} from '../apiClient';
|
||||||
import {
|
import { type RuntimeGuestRequestOptions } from '../runtimeGuestAuth';
|
||||||
buildRuntimeGuestAuthOptions,
|
import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest';
|
||||||
buildRuntimeGuestHeaders,
|
|
||||||
type RuntimeGuestRequestOptions,
|
|
||||||
} from '../runtimeGuestAuth';
|
|
||||||
|
|
||||||
|
const MATCH3D_RUNTIME_API_BASE = '/api/runtime/match3d';
|
||||||
const MATCH3D_RUNTIME_READ_RETRY: ApiRetryOptions = {
|
const MATCH3D_RUNTIME_READ_RETRY: ApiRetryOptions = {
|
||||||
maxRetries: 1,
|
maxRetries: 1,
|
||||||
baseDelayMs: 120,
|
baseDelayMs: 120,
|
||||||
@@ -74,39 +71,30 @@ export function startMatch3DRun(
|
|||||||
profileId: string,
|
profileId: string,
|
||||||
options: Match3DRuntimeRequestOptions = {},
|
options: Match3DRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
|
||||||
const payload: StartMatch3DRunRequest = {
|
const payload: StartMatch3DRunRequest = {
|
||||||
profileId,
|
profileId,
|
||||||
itemTypeCountOverride: options.itemTypeCountOverride ?? null,
|
itemTypeCountOverride: options.itemTypeCountOverride ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return requestJson<Match3DRunResponse>(
|
return requestRuntimeJson<Match3DRunResponse>({
|
||||||
`/api/runtime/match3d/works/${encodeURIComponent(profileId)}/runs`,
|
url: buildRuntimeApiPath(MATCH3D_RUNTIME_API_BASE, 'works', profileId, 'runs'),
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
jsonBody: payload,
|
||||||
headers: buildRuntimeGuestHeaders(options, {
|
fallbackMessage: '启动抓大鹅玩法失败',
|
||||||
'Content-Type': 'application/json',
|
retry: MATCH3D_RUNTIME_WRITE_RETRY,
|
||||||
}),
|
requestOptions: options,
|
||||||
body: JSON.stringify(payload),
|
});
|
||||||
},
|
|
||||||
'启动抓大鹅玩法失败',
|
|
||||||
{
|
|
||||||
retry: MATCH3D_RUNTIME_WRITE_RETRY,
|
|
||||||
...requestOptions,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取抓大鹅运行态快照。
|
* 读取抓大鹅运行态快照。
|
||||||
*/
|
*/
|
||||||
export function getMatch3DRun(runId: string) {
|
export function getMatch3DRun(runId: string) {
|
||||||
return requestJson<Match3DRunResponse>(
|
return requestRuntimeJson<Match3DRunResponse>({
|
||||||
`/api/runtime/match3d/runs/${encodeURIComponent(runId)}`,
|
url: buildRuntimeApiPath(MATCH3D_RUNTIME_API_BASE, 'runs', runId),
|
||||||
{ method: 'GET' },
|
fallbackMessage: '读取抓大鹅运行快照失败',
|
||||||
'读取抓大鹅运行快照失败',
|
retry: MATCH3D_RUNTIME_READ_RETRY,
|
||||||
{ retry: MATCH3D_RUNTIME_READ_RETRY },
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,19 +104,16 @@ export async function clickMatch3DItem(
|
|||||||
runId: string,
|
runId: string,
|
||||||
payload: Match3DClickItemRequest,
|
payload: Match3DClickItemRequest,
|
||||||
) {
|
) {
|
||||||
const response = await requestJson<Match3DClickResponse>(
|
const response = await requestRuntimeJson<Match3DClickResponse>({
|
||||||
`/api/runtime/match3d/runs/${encodeURIComponent(runId)}/click`,
|
url: buildRuntimeApiPath(MATCH3D_RUNTIME_API_BASE, 'runs', runId, 'click'),
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
jsonBody: {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
...payload,
|
||||||
body: JSON.stringify({
|
runId: payload.runId ?? runId,
|
||||||
...payload,
|
|
||||||
runId: payload.runId ?? runId,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
'确认抓大鹅点击失败',
|
fallbackMessage: '确认抓大鹅点击失败',
|
||||||
{ retry: MATCH3D_RUNTIME_WRITE_RETRY },
|
retry: MATCH3D_RUNTIME_WRITE_RETRY,
|
||||||
);
|
});
|
||||||
|
|
||||||
return mapClickConfirmation(payload, response.confirmation);
|
return mapClickConfirmation(payload, response.confirmation);
|
||||||
}
|
}
|
||||||
@@ -142,40 +127,37 @@ export function stopMatch3DRun(
|
|||||||
clientActionId: `match3d-stop-${Date.now()}`,
|
clientActionId: `match3d-stop-${Date.now()}`,
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return requestJson<Match3DRunResponse>(
|
return requestRuntimeJson<Match3DRunResponse>({
|
||||||
`/api/runtime/match3d/runs/${encodeURIComponent(runId)}/stop`,
|
url: buildRuntimeApiPath(MATCH3D_RUNTIME_API_BASE, 'runs', runId, 'stop'),
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
jsonBody: payload,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
fallbackMessage: '停止抓大鹅玩法失败',
|
||||||
body: JSON.stringify(payload),
|
retry: MATCH3D_RUNTIME_WRITE_RETRY,
|
||||||
},
|
});
|
||||||
'停止抓大鹅玩法失败',
|
|
||||||
{ retry: MATCH3D_RUNTIME_WRITE_RETRY },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于当前 run 重开一局。
|
* 基于当前 run 重开一局。
|
||||||
*/
|
*/
|
||||||
export function restartMatch3DRun(runId: string) {
|
export function restartMatch3DRun(runId: string) {
|
||||||
return requestJson<Match3DRunResponse>(
|
return requestRuntimeJson<Match3DRunResponse>({
|
||||||
`/api/runtime/match3d/runs/${encodeURIComponent(runId)}/restart`,
|
url: buildRuntimeApiPath(MATCH3D_RUNTIME_API_BASE, 'runs', runId, 'restart'),
|
||||||
{ method: 'POST' },
|
method: 'POST',
|
||||||
'重新开始抓大鹅玩法失败',
|
fallbackMessage: '重新开始抓大鹅玩法失败',
|
||||||
{ retry: MATCH3D_RUNTIME_WRITE_RETRY },
|
retry: MATCH3D_RUNTIME_WRITE_RETRY,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前端倒计时归零后通知后端确认失败状态。
|
* 前端倒计时归零后通知后端确认失败状态。
|
||||||
*/
|
*/
|
||||||
export function finishMatch3DTimeUp(runId: string) {
|
export function finishMatch3DTimeUp(runId: string) {
|
||||||
return requestJson<Match3DRunResponse>(
|
return requestRuntimeJson<Match3DRunResponse>({
|
||||||
`/api/runtime/match3d/runs/${encodeURIComponent(runId)}/time-up`,
|
url: buildRuntimeApiPath(MATCH3D_RUNTIME_API_BASE, 'runs', runId, 'time-up'),
|
||||||
{ method: 'POST' },
|
method: 'POST',
|
||||||
'同步抓大鹅倒计时失败',
|
fallbackMessage: '同步抓大鹅倒计时失败',
|
||||||
{ retry: MATCH3D_RUNTIME_WRITE_RETRY },
|
retry: MATCH3D_RUNTIME_WRITE_RETRY,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const match3dRuntimeClient = {
|
export const match3dRuntimeClient = {
|
||||||
|
|||||||
88
src/services/runtimeRequest.test.ts
Normal file
88
src/services/runtimeRequest.test.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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 {
|
||||||
|
buildRuntimeApiPath,
|
||||||
|
requestRuntimeJson,
|
||||||
|
} from './runtimeRequest';
|
||||||
|
|
||||||
|
describe('runtimeRequest', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
apiClientMocks.requestJson.mockResolvedValue({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds encoded runtime api paths', () => {
|
||||||
|
expect(buildRuntimeApiPath('/api/runtime/demo/', 'work/a b', 'run/1')).toBe(
|
||||||
|
'/api/runtime/demo/work%2Fa%20b/run%2F1',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends json runtime requests with guest auth and retry options', async () => {
|
||||||
|
const retry = { maxRetries: 1, retryUnsafeMethods: true };
|
||||||
|
|
||||||
|
await requestRuntimeJson({
|
||||||
|
url: '/api/runtime/demo/runs',
|
||||||
|
method: 'POST',
|
||||||
|
jsonBody: { profileId: 'profile-1' },
|
||||||
|
fallbackMessage: '启动失败',
|
||||||
|
retry,
|
||||||
|
requestOptions: {
|
||||||
|
runtimeGuestToken: 'runtime-guest-token',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
|
||||||
|
'/api/runtime/demo/runs',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: 'Bearer runtime-guest-token',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ profileId: 'profile-1' }),
|
||||||
|
},
|
||||||
|
'启动失败',
|
||||||
|
{
|
||||||
|
retry,
|
||||||
|
authImpact: undefined,
|
||||||
|
skipAuth: true,
|
||||||
|
skipRefresh: true,
|
||||||
|
notifyAuthStateChange: undefined,
|
||||||
|
clearAuthOnUnauthorized: undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('omits empty headers and body for plain runtime reads', async () => {
|
||||||
|
await requestRuntimeJson({
|
||||||
|
url: '/api/runtime/demo/runs/run-1',
|
||||||
|
fallbackMessage: '读取失败',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
|
||||||
|
'/api/runtime/demo/runs/run-1',
|
||||||
|
{ method: 'GET' },
|
||||||
|
'读取失败',
|
||||||
|
{
|
||||||
|
authImpact: undefined,
|
||||||
|
skipAuth: undefined,
|
||||||
|
skipRefresh: undefined,
|
||||||
|
notifyAuthStateChange: undefined,
|
||||||
|
clearAuthOnUnauthorized: undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
62
src/services/runtimeRequest.ts
Normal file
62
src/services/runtimeRequest.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import {
|
||||||
|
type ApiRetryOptions,
|
||||||
|
requestJson,
|
||||||
|
} from './apiClient';
|
||||||
|
import {
|
||||||
|
buildRuntimeGuestAuthOptions,
|
||||||
|
buildRuntimeGuestHeaders,
|
||||||
|
type RuntimeGuestRequestOptions,
|
||||||
|
} from './runtimeGuestAuth';
|
||||||
|
|
||||||
|
export type RuntimeJsonRequestParams = {
|
||||||
|
url: string;
|
||||||
|
method?: string;
|
||||||
|
jsonBody?: unknown;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
fallbackMessage: string;
|
||||||
|
retry?: ApiRetryOptions;
|
||||||
|
requestOptions?: RuntimeGuestRequestOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function buildRuntimeApiPath(
|
||||||
|
basePath: string,
|
||||||
|
...segments: string[]
|
||||||
|
) {
|
||||||
|
const normalizedBasePath = basePath.endsWith('/')
|
||||||
|
? basePath.slice(0, -1)
|
||||||
|
: basePath;
|
||||||
|
return [
|
||||||
|
normalizedBasePath,
|
||||||
|
...segments.map((segment) => encodeURIComponent(segment)),
|
||||||
|
].join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requestRuntimeJson<T>(params: RuntimeJsonRequestParams) {
|
||||||
|
const {
|
||||||
|
fallbackMessage,
|
||||||
|
headers = {},
|
||||||
|
jsonBody,
|
||||||
|
method = 'GET',
|
||||||
|
requestOptions = {},
|
||||||
|
retry,
|
||||||
|
url,
|
||||||
|
} = params;
|
||||||
|
const hasJsonBody = jsonBody !== undefined;
|
||||||
|
const requestHeaders = buildRuntimeGuestHeaders(requestOptions, {
|
||||||
|
...(hasJsonBody ? { 'Content-Type': 'application/json' } : {}),
|
||||||
|
...headers,
|
||||||
|
});
|
||||||
|
const init: RequestInit = {
|
||||||
|
method,
|
||||||
|
...(Object.keys(requestHeaders).length > 0
|
||||||
|
? { headers: requestHeaders }
|
||||||
|
: {}),
|
||||||
|
...(hasJsonBody ? { body: JSON.stringify(jsonBody) } : {}),
|
||||||
|
};
|
||||||
|
const authOptions = buildRuntimeGuestAuthOptions(requestOptions);
|
||||||
|
|
||||||
|
return requestJson<T>(url, init, fallbackMessage, {
|
||||||
|
...(retry ? { retry } : {}),
|
||||||
|
...authOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,14 +6,11 @@ import type {
|
|||||||
} from '../../../packages/shared/src/contracts/squareHoleRuntime';
|
} from '../../../packages/shared/src/contracts/squareHoleRuntime';
|
||||||
import {
|
import {
|
||||||
type ApiRetryOptions,
|
type ApiRetryOptions,
|
||||||
requestJson,
|
|
||||||
} from '../apiClient';
|
} from '../apiClient';
|
||||||
import {
|
import { type RuntimeGuestRequestOptions } from '../runtimeGuestAuth';
|
||||||
buildRuntimeGuestAuthOptions,
|
import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest';
|
||||||
buildRuntimeGuestHeaders,
|
|
||||||
type RuntimeGuestRequestOptions,
|
|
||||||
} from '../runtimeGuestAuth';
|
|
||||||
|
|
||||||
|
const SQUARE_HOLE_RUNTIME_API_BASE = '/api/runtime/square-hole';
|
||||||
const SQUARE_HOLE_RUNTIME_READ_RETRY: ApiRetryOptions = {
|
const SQUARE_HOLE_RUNTIME_READ_RETRY: ApiRetryOptions = {
|
||||||
maxRetries: 1,
|
maxRetries: 1,
|
||||||
baseDelayMs: 120,
|
baseDelayMs: 120,
|
||||||
@@ -34,34 +31,30 @@ export function startSquareHoleRun(
|
|||||||
profileId: string,
|
profileId: string,
|
||||||
options: SquareHoleRuntimeRequestOptions = {},
|
options: SquareHoleRuntimeRequestOptions = {},
|
||||||
) {
|
) {
|
||||||
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
return requestRuntimeJson<SquareHoleRunResponse>({
|
||||||
return requestJson<SquareHoleRunResponse>(
|
url: buildRuntimeApiPath(
|
||||||
`/api/runtime/square-hole/works/${encodeURIComponent(profileId)}/runs`,
|
SQUARE_HOLE_RUNTIME_API_BASE,
|
||||||
{
|
'works',
|
||||||
method: 'POST',
|
profileId,
|
||||||
headers: buildRuntimeGuestHeaders(options, {
|
'runs',
|
||||||
'Content-Type': 'application/json',
|
),
|
||||||
}),
|
method: 'POST',
|
||||||
body: JSON.stringify({ profileId }),
|
jsonBody: { profileId },
|
||||||
},
|
fallbackMessage: '启动方洞挑战失败',
|
||||||
'启动方洞挑战失败',
|
retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY,
|
||||||
{
|
requestOptions: options,
|
||||||
retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY,
|
});
|
||||||
...requestOptions,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取方洞挑战运行态快照。
|
* 读取方洞挑战运行态快照。
|
||||||
*/
|
*/
|
||||||
export function getSquareHoleRun(runId: string) {
|
export function getSquareHoleRun(runId: string) {
|
||||||
return requestJson<SquareHoleRunResponse>(
|
return requestRuntimeJson<SquareHoleRunResponse>({
|
||||||
`/api/runtime/square-hole/runs/${encodeURIComponent(runId)}`,
|
url: buildRuntimeApiPath(SQUARE_HOLE_RUNTIME_API_BASE, 'runs', runId),
|
||||||
{ method: 'GET' },
|
fallbackMessage: '读取方洞挑战运行快照失败',
|
||||||
'读取方洞挑战运行快照失败',
|
retry: SQUARE_HOLE_RUNTIME_READ_RETRY,
|
||||||
{ retry: SQUARE_HOLE_RUNTIME_READ_RETRY },
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,19 +64,21 @@ export function dropSquareHoleShape(
|
|||||||
runId: string,
|
runId: string,
|
||||||
payload: DropSquareHoleShapeRequest,
|
payload: DropSquareHoleShapeRequest,
|
||||||
) {
|
) {
|
||||||
return requestJson<SquareHoleDropResponse>(
|
return requestRuntimeJson<SquareHoleDropResponse>({
|
||||||
`/api/runtime/square-hole/runs/${encodeURIComponent(runId)}/drop`,
|
url: buildRuntimeApiPath(
|
||||||
{
|
SQUARE_HOLE_RUNTIME_API_BASE,
|
||||||
method: 'POST',
|
'runs',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
runId,
|
||||||
body: JSON.stringify({
|
'drop',
|
||||||
...payload,
|
),
|
||||||
runId: payload.runId ?? runId,
|
method: 'POST',
|
||||||
}),
|
jsonBody: {
|
||||||
|
...payload,
|
||||||
|
runId: payload.runId ?? runId,
|
||||||
},
|
},
|
||||||
'确认方洞挑战投入失败',
|
fallbackMessage: '确认方洞挑战投入失败',
|
||||||
{ retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY },
|
retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,40 +90,47 @@ export function stopSquareHoleRun(
|
|||||||
clientActionId: `square-hole-stop-${Date.now()}`,
|
clientActionId: `square-hole-stop-${Date.now()}`,
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return requestJson<SquareHoleRunResponse>(
|
return requestRuntimeJson<SquareHoleRunResponse>({
|
||||||
`/api/runtime/square-hole/runs/${encodeURIComponent(runId)}/stop`,
|
url: buildRuntimeApiPath(SQUARE_HOLE_RUNTIME_API_BASE, 'runs', runId, 'stop'),
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
jsonBody: payload,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
fallbackMessage: '停止方洞挑战失败',
|
||||||
body: JSON.stringify(payload),
|
retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY,
|
||||||
},
|
});
|
||||||
'停止方洞挑战失败',
|
|
||||||
{ retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于当前 run 重开一局。
|
* 基于当前 run 重开一局。
|
||||||
*/
|
*/
|
||||||
export function restartSquareHoleRun(runId: string) {
|
export function restartSquareHoleRun(runId: string) {
|
||||||
return requestJson<SquareHoleRunResponse>(
|
return requestRuntimeJson<SquareHoleRunResponse>({
|
||||||
`/api/runtime/square-hole/runs/${encodeURIComponent(runId)}/restart`,
|
url: buildRuntimeApiPath(
|
||||||
{ method: 'POST' },
|
SQUARE_HOLE_RUNTIME_API_BASE,
|
||||||
'重新开始方洞挑战失败',
|
'runs',
|
||||||
{ retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY },
|
runId,
|
||||||
);
|
'restart',
|
||||||
|
),
|
||||||
|
method: 'POST',
|
||||||
|
fallbackMessage: '重新开始方洞挑战失败',
|
||||||
|
retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前端倒计时归零后通知后端确认失败状态。
|
* 前端倒计时归零后通知后端确认失败状态。
|
||||||
*/
|
*/
|
||||||
export function finishSquareHoleTimeUp(runId: string) {
|
export function finishSquareHoleTimeUp(runId: string) {
|
||||||
return requestJson<SquareHoleRunResponse>(
|
return requestRuntimeJson<SquareHoleRunResponse>({
|
||||||
`/api/runtime/square-hole/runs/${encodeURIComponent(runId)}/time-up`,
|
url: buildRuntimeApiPath(
|
||||||
{ method: 'POST' },
|
SQUARE_HOLE_RUNTIME_API_BASE,
|
||||||
'同步方洞挑战倒计时失败',
|
'runs',
|
||||||
{ retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY },
|
runId,
|
||||||
);
|
'time-up',
|
||||||
|
),
|
||||||
|
method: 'POST',
|
||||||
|
fallbackMessage: '同步方洞挑战倒计时失败',
|
||||||
|
retry: SQUARE_HOLE_RUNTIME_WRITE_RETRY,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const squareHoleRuntimeClient = {
|
export const squareHoleRuntimeClient = {
|
||||||
|
|||||||
Reference in New Issue
Block a user