Files
Genarrative/src/services/puzzle-runtime/puzzleRuntimeClient.ts
kdletters 7dd53e95d8 统一推荐页游客运行态与切换队列
统一推荐页各玩法正式 runtime 的游客鉴权透传。

收口推荐页首页展示队列和嵌入运行态切换队列。

补齐未登录读档、签名资产和个人数据读取的游客态处理。

新增运行态 HUD 小尺寸 logo 资源并更新拼图与抓鹅展示。

补充推荐切换、runtime guest 启动和客户端请求回归测试。

更新玩法链路、后端契约和团队记忆文档。
2026-06-10 22:00:19 +08:00

187 lines
5.3 KiB
TypeScript

import type {
AdvancePuzzleNextLevelRequest,
DragPuzzlePieceRequest,
PuzzleRunResponse,
StartPuzzleRunRequest,
SubmitPuzzleLeaderboardRequest,
SwapPuzzlePiecesRequest,
UpdatePuzzleRuntimePauseRequest,
UsePuzzleRuntimePropRequest,
} from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
import { type ApiRetryOptions } from '../apiClient';
import { type RuntimeGuestRequestOptions } from '../runtimeGuestAuth';
import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest';
const PUZZLE_RUNTIME_API_BASE = '/api/runtime/puzzle/runs';
const PUZZLE_RUNTIME_READ_RETRY: ApiRetryOptions = {
maxRetries: 1,
baseDelayMs: 120,
maxDelayMs: 360,
};
const PUZZLE_RUNTIME_WRITE_RETRY: ApiRetryOptions = {
maxRetries: 1,
baseDelayMs: 120,
maxDelayMs: 360,
retryUnsafeMethods: true,
};
const PUZZLE_RUNTIME_LEADERBOARD_RETRY: ApiRetryOptions = {
maxRetries: 0,
};
type PuzzleRuntimeRequestOptions = RuntimeGuestRequestOptions;
/**
* 从某个已发布拼图作品开始一次 run。
*/
export async function startPuzzleRun(
payload: StartPuzzleRunRequest,
options: PuzzleRuntimeRequestOptions = {},
) {
return requestRuntimeJson<PuzzleRunResponse>({
url: PUZZLE_RUNTIME_API_BASE,
method: 'POST',
jsonBody: payload,
fallbackMessage: '启动拼图玩法失败',
retry: PUZZLE_RUNTIME_WRITE_RETRY,
requestOptions: options,
});
}
/**
* 读取拼图运行态快照。
*/
export async function getPuzzleRun(
runId: string,
options: PuzzleRuntimeRequestOptions = {},
) {
return requestRuntimeJson<PuzzleRunResponse>({
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId),
fallbackMessage: '读取拼图运行快照失败',
retry: PUZZLE_RUNTIME_READ_RETRY,
requestOptions: options,
});
}
/**
* 提交两块交换请求。
*/
export async function swapPuzzlePieces(
runId: string,
payload: SwapPuzzlePiecesRequest,
options: PuzzleRuntimeRequestOptions = {},
) {
return requestRuntimeJson<PuzzleRunResponse>({
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'swap'),
method: 'POST',
jsonBody: payload,
fallbackMessage: '交换拼图块失败',
retry: PUZZLE_RUNTIME_WRITE_RETRY,
requestOptions: options,
});
}
/**
* 提交拖拽拼图块或已合并拼图组后的目标格。
*/
export async function dragPuzzlePieceOrGroup(
runId: string,
payload: DragPuzzlePieceRequest,
options: PuzzleRuntimeRequestOptions = {},
) {
return requestRuntimeJson<PuzzleRunResponse>({
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'drag'),
method: 'POST',
jsonBody: payload,
fallbackMessage: '拖动拼图块失败',
retry: PUZZLE_RUNTIME_WRITE_RETRY,
requestOptions: options,
});
}
/**
* 进入当前 run 的下一关。
*/
export async function advancePuzzleNextLevel(
runId: string,
payload: AdvancePuzzleNextLevelRequest = {},
options: PuzzleRuntimeRequestOptions = {},
) {
const targetProfileId = payload.targetProfileId?.trim() ?? '';
const requestPayload = {
...(targetProfileId ? { targetProfileId } : {}),
};
const hasRequestPayload = Object.keys(requestPayload).length > 0;
return requestRuntimeJson<PuzzleRunResponse>({
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'next-level'),
method: 'POST',
...(hasRequestPayload ? { jsonBody: requestPayload } : {}),
fallbackMessage: '进入下一关失败',
retry: PUZZLE_RUNTIME_WRITE_RETRY,
requestOptions: options,
});
}
/**
* 提交通关成绩并读取真实排行榜。
*/
export async function submitPuzzleLeaderboard(
runId: string,
payload: SubmitPuzzleLeaderboardRequest,
options: PuzzleRuntimeRequestOptions = {},
) {
return requestRuntimeJson<PuzzleRunResponse>({
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'leaderboard'),
method: 'POST',
jsonBody: payload,
fallbackMessage: '提交拼图排行榜失败',
retry: PUZZLE_RUNTIME_LEADERBOARD_RETRY,
requestOptions: options,
});
}
/**
* 暂停或恢复正式拼图运行态计时。
*/
export async function updatePuzzleRunPause(
runId: string,
payload: UpdatePuzzleRuntimePauseRequest,
options: PuzzleRuntimeRequestOptions = {},
) {
return requestRuntimeJson<PuzzleRunResponse>({
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'pause'),
method: 'POST',
jsonBody: payload,
fallbackMessage: '更新拼图计时状态失败',
retry: PUZZLE_RUNTIME_WRITE_RETRY,
requestOptions: options,
});
}
/**
* 使用正式拼图道具,服务端负责扣除泥点并更新运行态。
*/
export async function usePuzzleRuntimeProp(
runId: string,
payload: UsePuzzleRuntimePropRequest,
options: PuzzleRuntimeRequestOptions = {},
) {
return requestRuntimeJson<PuzzleRunResponse>({
url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'props'),
method: 'POST',
jsonBody: payload,
fallbackMessage: '使用拼图道具失败',
retry: PUZZLE_RUNTIME_WRITE_RETRY,
requestOptions: options,
});
}
export const puzzleRuntimeClient = {
advanceNextLevel: advancePuzzleNextLevel,
drag: dragPuzzlePieceOrGroup,
getRun: getPuzzleRun,
submitLeaderboard: submitPuzzleLeaderboard,
startRun: startPuzzleRun,
swap: swapPuzzlePieces,
updatePause: updatePuzzleRunPause,
useProp: usePuzzleRuntimeProp,
};