import type { AdvancePuzzleNextLevelRequest, DragPuzzlePieceRequest, PuzzleRunResponse, StartPuzzleRunRequest, SubmitPuzzleLeaderboardRequest, SwapPuzzlePiecesRequest, UpdatePuzzleRuntimePauseRequest, UsePuzzleRuntimePropRequest, } from '../../../packages/shared/src/contracts/puzzleRuntimeSession'; import { type ApiRetryOptions, requestJson, } 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({ url: PUZZLE_RUNTIME_API_BASE, method: 'POST', jsonBody: payload, fallbackMessage: '启动拼图玩法失败', retry: PUZZLE_RUNTIME_WRITE_RETRY, requestOptions: options, }); } /** * 读取拼图运行态快照。 */ export async function getPuzzleRun(runId: string) { return requestRuntimeJson({ url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId), fallbackMessage: '读取拼图运行快照失败', retry: PUZZLE_RUNTIME_READ_RETRY, }); } /** * 提交两块交换请求。 */ export async function swapPuzzlePieces( runId: string, payload: SwapPuzzlePiecesRequest, ) { return requestRuntimeJson({ url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'swap'), method: 'POST', jsonBody: payload, fallbackMessage: '交换拼图块失败', retry: PUZZLE_RUNTIME_WRITE_RETRY, }); } /** * 提交拖拽拼图块或已合并拼图组后的目标格。 */ export async function dragPuzzlePieceOrGroup( runId: string, payload: DragPuzzlePieceRequest, ) { return requestRuntimeJson({ url: buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'drag'), method: 'POST', jsonBody: payload, fallbackMessage: '拖动拼图块失败', retry: PUZZLE_RUNTIME_WRITE_RETRY, }); } /** * 进入推荐出的下一关。 */ export async function advancePuzzleNextLevel( runId: string, payload: AdvancePuzzleNextLevelRequest = {}, options: PuzzleRuntimeRequestOptions = {}, ) { const targetProfileId = payload.targetProfileId?.trim() ?? ''; const preferSimilarWork = payload.preferSimilarWork === true; const requestPayload = { ...(targetProfileId ? { targetProfileId } : {}), ...(preferSimilarWork ? { preferSimilarWork: true } : {}), }; const hasRequestPayload = Object.keys(requestPayload).length > 0; return requestRuntimeJson({ 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({ 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 requestJson( buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'pause'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, '更新拼图计时状态失败', { retry: PUZZLE_RUNTIME_WRITE_RETRY, authImpact: options.authImpact, skipRefresh: options.skipRefresh, notifyAuthStateChange: options.notifyAuthStateChange, clearAuthOnUnauthorized: options.clearAuthOnUnauthorized, }, ); } /** * 使用正式拼图道具,服务端负责扣除泥点并更新运行态。 */ export async function usePuzzleRuntimeProp( runId: string, payload: UsePuzzleRuntimePropRequest, options: PuzzleRuntimeRequestOptions = {}, ) { return requestJson( buildRuntimeApiPath(PUZZLE_RUNTIME_API_BASE, runId, 'props'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, '使用拼图道具失败', { retry: PUZZLE_RUNTIME_WRITE_RETRY, authImpact: options.authImpact, skipRefresh: options.skipRefresh, notifyAuthStateChange: options.notifyAuthStateChange, clearAuthOnUnauthorized: options.clearAuthOnUnauthorized, }, ); } export const puzzleRuntimeClient = { advanceNextLevel: advancePuzzleNextLevel, drag: dragPuzzlePieceOrGroup, getRun: getPuzzleRun, submitLeaderboard: submitPuzzleLeaderboard, startRun: startPuzzleRun, swap: swapPuzzlePieces, updatePause: updatePuzzleRunPause, useProp: usePuzzleRuntimeProp, };