441 lines
11 KiB
TypeScript
441 lines
11 KiB
TypeScript
import type { ListCustomWorldWorksResponse } from '../../packages/shared/src/contracts/customWorldAgent';
|
|
import type {
|
|
BasicOkResult,
|
|
CustomWorldGalleryDetailResponse,
|
|
CustomWorldGalleryResponse,
|
|
CustomWorldLibraryEntry,
|
|
CustomWorldLibraryMutationResponse,
|
|
CustomWorldLibraryResponse,
|
|
PlatformBrowseHistoryBatchSyncRequest,
|
|
PlatformBrowseHistoryEntry,
|
|
PlatformBrowseHistoryResponse,
|
|
PlatformBrowseHistoryWriteEntry,
|
|
ProfileDashboardSummary,
|
|
ProfileSaveArchiveListResponse,
|
|
ProfileSaveArchiveResumeResponse,
|
|
ProfileSaveArchiveSummary,
|
|
ProfilePlayStatsResponse,
|
|
ProfileWalletLedgerResponse,
|
|
RuntimeSettings,
|
|
} from '../../packages/shared/src/contracts/runtime';
|
|
import type { SavedGameSnapshotInput } from '../persistence/gameSaveStorage';
|
|
import { rehydrateSavedSnapshot } from '../persistence/runtimeSnapshot';
|
|
import type { HydratedSavedGameSnapshot } from '../persistence/runtimeSnapshotTypes';
|
|
import type { CustomWorldProfile } from '../types';
|
|
import { type ApiRetryOptions, requestJson } from './apiClient';
|
|
|
|
const RUNTIME_API_BASE = '/api/runtime';
|
|
const RUNTIME_READ_RETRY: ApiRetryOptions = {
|
|
maxRetries: 1,
|
|
baseDelayMs: 180,
|
|
maxDelayMs: 480,
|
|
};
|
|
const RUNTIME_WRITE_RETRY: ApiRetryOptions = {
|
|
maxRetries: 1,
|
|
baseDelayMs: 240,
|
|
maxDelayMs: 640,
|
|
retryUnsafeMethods: true,
|
|
};
|
|
|
|
export type RuntimeRequestOptions = {
|
|
signal?: AbortSignal;
|
|
retry?: ApiRetryOptions;
|
|
skipAuth?: boolean;
|
|
skipRefresh?: boolean;
|
|
};
|
|
|
|
function requestRuntimeJson<T>(
|
|
path: string,
|
|
init: RequestInit,
|
|
fallbackMessage: string,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const method = (init.method ?? 'GET').toUpperCase();
|
|
const retry =
|
|
options.retry ??
|
|
(method === 'GET' ? RUNTIME_READ_RETRY : RUNTIME_WRITE_RETRY);
|
|
|
|
return requestJson<T>(
|
|
`${RUNTIME_API_BASE}${path}`,
|
|
{
|
|
...init,
|
|
signal: options.signal,
|
|
},
|
|
fallbackMessage,
|
|
{
|
|
retry,
|
|
skipAuth: options.skipAuth,
|
|
skipRefresh: options.skipRefresh,
|
|
},
|
|
);
|
|
}
|
|
|
|
function requestPublicRuntimeJson<T>(
|
|
path: string,
|
|
init: RequestInit,
|
|
fallbackMessage: string,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
return requestRuntimeJson<T>(path, init, fallbackMessage, {
|
|
...options,
|
|
skipAuth: true,
|
|
skipRefresh: true,
|
|
});
|
|
}
|
|
|
|
export async function getSaveSnapshot(options: RuntimeRequestOptions = {}) {
|
|
const snapshot = await requestRuntimeJson<HydratedSavedGameSnapshot | null>(
|
|
'/save/snapshot',
|
|
{ method: 'GET' },
|
|
'读取存档失败',
|
|
options,
|
|
);
|
|
|
|
return snapshot ? rehydrateSavedSnapshot(snapshot) : null;
|
|
}
|
|
|
|
export async function putSaveSnapshot(
|
|
snapshot: SavedGameSnapshotInput,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const savedSnapshot = await requestRuntimeJson<HydratedSavedGameSnapshot>(
|
|
'/save/snapshot',
|
|
{
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(snapshot),
|
|
},
|
|
'保存存档失败',
|
|
options,
|
|
);
|
|
|
|
return rehydrateSavedSnapshot(savedSnapshot);
|
|
}
|
|
|
|
export async function deleteSaveSnapshot(options: RuntimeRequestOptions = {}) {
|
|
return requestRuntimeJson<BasicOkResult>(
|
|
'/save/snapshot',
|
|
{ method: 'DELETE' },
|
|
'删除存档失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function getSettings(options: RuntimeRequestOptions = {}) {
|
|
return requestRuntimeJson<RuntimeSettings>(
|
|
'/settings',
|
|
{ method: 'GET' },
|
|
'读取设置失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function getProfileDashboard(options: RuntimeRequestOptions = {}) {
|
|
return requestRuntimeJson<ProfileDashboardSummary>(
|
|
'/profile/dashboard',
|
|
{ method: 'GET' },
|
|
'读取个人看板失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function getProfileWalletLedger(
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
return requestRuntimeJson<ProfileWalletLedgerResponse>(
|
|
'/profile/wallet-ledger',
|
|
{ method: 'GET' },
|
|
'读取资产流水失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function getProfilePlayStats(options: RuntimeRequestOptions = {}) {
|
|
return requestRuntimeJson<ProfilePlayStatsResponse>(
|
|
'/profile/play-stats',
|
|
{ method: 'GET' },
|
|
'读取游玩统计失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function listProfileSaveArchives(
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<ProfileSaveArchiveListResponse>(
|
|
'/profile/save-archives',
|
|
{ method: 'GET' },
|
|
'读取存档列表失败',
|
|
options,
|
|
);
|
|
|
|
return Array.isArray(response?.entries) ? response.entries : [];
|
|
}
|
|
|
|
export async function resumeProfileSaveArchive(
|
|
worldKey: string,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<
|
|
ProfileSaveArchiveResumeResponse
|
|
>(
|
|
`/profile/save-archives/${encodeURIComponent(worldKey)}`,
|
|
{ method: 'POST' },
|
|
'恢复存档失败',
|
|
options,
|
|
);
|
|
|
|
return {
|
|
entry: response.entry,
|
|
snapshot: rehydrateSavedSnapshot(
|
|
response.snapshot as HydratedSavedGameSnapshot,
|
|
),
|
|
};
|
|
}
|
|
|
|
export async function putSettings(
|
|
settings: RuntimeSettings,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
return requestRuntimeJson<RuntimeSettings>(
|
|
'/settings',
|
|
{
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(settings),
|
|
},
|
|
'保存设置失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function listCustomWorldLibrary(
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<
|
|
CustomWorldLibraryResponse<CustomWorldProfile>
|
|
>(
|
|
'/custom-world-library',
|
|
{ method: 'GET' },
|
|
'读取自定义世界库失败',
|
|
options,
|
|
);
|
|
|
|
return Array.isArray(response?.entries) ? response.entries : [];
|
|
}
|
|
|
|
export async function listCustomWorldWorks(
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<ListCustomWorldWorksResponse>(
|
|
'/custom-world/works',
|
|
{ method: 'GET' },
|
|
'读取创作作品列表失败',
|
|
options,
|
|
);
|
|
|
|
return Array.isArray(response?.items) ? response.items : [];
|
|
}
|
|
|
|
export async function upsertCustomWorldProfile(
|
|
profile: CustomWorldProfile,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<
|
|
CustomWorldLibraryMutationResponse<CustomWorldProfile>
|
|
>(
|
|
`/custom-world-library/${encodeURIComponent(profile.id)}`,
|
|
{
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
profile,
|
|
}),
|
|
},
|
|
'保存自定义世界失败',
|
|
options,
|
|
);
|
|
|
|
return {
|
|
entry: response.entry,
|
|
entries: Array.isArray(response?.entries) ? response.entries : [],
|
|
};
|
|
}
|
|
|
|
export async function deleteCustomWorldProfile(
|
|
profileId: string,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<
|
|
CustomWorldLibraryResponse<CustomWorldProfile>
|
|
>(
|
|
`/custom-world-library/${encodeURIComponent(profileId)}`,
|
|
{ method: 'DELETE' },
|
|
'删除自定义世界失败',
|
|
options,
|
|
);
|
|
|
|
return Array.isArray(response?.entries) ? response.entries : [];
|
|
}
|
|
|
|
export async function publishCustomWorldProfile(
|
|
profileId: string,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<
|
|
CustomWorldLibraryMutationResponse<CustomWorldProfile>
|
|
>(
|
|
`/custom-world-library/${encodeURIComponent(profileId)}/publish`,
|
|
{ method: 'POST' },
|
|
'发布自定义世界失败',
|
|
options,
|
|
);
|
|
|
|
return {
|
|
entry: response.entry,
|
|
entries: Array.isArray(response?.entries) ? response.entries : [],
|
|
};
|
|
}
|
|
|
|
export async function unpublishCustomWorldProfile(
|
|
profileId: string,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<
|
|
CustomWorldLibraryMutationResponse<CustomWorldProfile>
|
|
>(
|
|
`/custom-world-library/${encodeURIComponent(profileId)}/unpublish`,
|
|
{ method: 'POST' },
|
|
'下架自定义世界失败',
|
|
options,
|
|
);
|
|
|
|
return {
|
|
entry: response.entry,
|
|
entries: Array.isArray(response?.entries) ? response.entries : [],
|
|
};
|
|
}
|
|
|
|
export async function listCustomWorldGallery(
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestPublicRuntimeJson<CustomWorldGalleryResponse>(
|
|
'/custom-world-gallery',
|
|
{ method: 'GET' },
|
|
'读取作品广场失败',
|
|
options,
|
|
);
|
|
|
|
return Array.isArray(response?.entries) ? response.entries : [];
|
|
}
|
|
|
|
export async function getCustomWorldGalleryDetail(
|
|
ownerUserId: string,
|
|
profileId: string,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestPublicRuntimeJson<
|
|
CustomWorldGalleryDetailResponse<CustomWorldProfile>
|
|
>(
|
|
`/custom-world-gallery/${encodeURIComponent(ownerUserId)}/${encodeURIComponent(profileId)}`,
|
|
{ method: 'GET' },
|
|
'读取作品详情失败',
|
|
options,
|
|
);
|
|
|
|
return response.entry;
|
|
}
|
|
|
|
export async function listProfileBrowseHistory(
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<PlatformBrowseHistoryResponse>(
|
|
'/profile/browse-history',
|
|
{ method: 'GET' },
|
|
'读取浏览历史失败',
|
|
options,
|
|
);
|
|
|
|
return Array.isArray(response?.entries) ? response.entries : [];
|
|
}
|
|
|
|
export async function upsertProfileBrowseHistory(
|
|
entry: PlatformBrowseHistoryWriteEntry,
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<PlatformBrowseHistoryResponse>(
|
|
'/profile/browse-history',
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(entry),
|
|
},
|
|
'写入浏览历史失败',
|
|
options,
|
|
);
|
|
|
|
return Array.isArray(response?.entries) ? response.entries : [];
|
|
}
|
|
|
|
export async function syncProfileBrowseHistory(
|
|
entries: PlatformBrowseHistoryWriteEntry[],
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<PlatformBrowseHistoryResponse>(
|
|
'/profile/browse-history',
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
entries,
|
|
} satisfies PlatformBrowseHistoryBatchSyncRequest),
|
|
},
|
|
'同步浏览历史失败',
|
|
options,
|
|
);
|
|
|
|
return Array.isArray(response?.entries) ? response.entries : [];
|
|
}
|
|
|
|
export async function clearProfileBrowseHistory(
|
|
options: RuntimeRequestOptions = {},
|
|
) {
|
|
const response = await requestRuntimeJson<PlatformBrowseHistoryResponse>(
|
|
'/profile/browse-history',
|
|
{ method: 'DELETE' },
|
|
'清空浏览历史失败',
|
|
options,
|
|
);
|
|
|
|
return Array.isArray(response?.entries) ? response.entries : [];
|
|
}
|
|
|
|
export const runtimeStorageClient = {
|
|
getSaveSnapshot,
|
|
putSaveSnapshot,
|
|
deleteSaveSnapshot,
|
|
getSettings,
|
|
putSettings,
|
|
getProfileDashboard,
|
|
getProfileWalletLedger,
|
|
getProfilePlayStats,
|
|
listProfileSaveArchives,
|
|
resumeProfileSaveArchive,
|
|
listCustomWorldLibrary,
|
|
listCustomWorldWorks,
|
|
upsertCustomWorldProfile,
|
|
deleteCustomWorldProfile,
|
|
publishCustomWorldProfile,
|
|
unpublishCustomWorldProfile,
|
|
listCustomWorldGallery,
|
|
getCustomWorldGalleryDetail,
|
|
listProfileBrowseHistory,
|
|
upsertProfileBrowseHistory,
|
|
syncProfileBrowseHistory,
|
|
clearProfileBrowseHistory,
|
|
};
|
|
|
|
export type { CustomWorldLibraryEntry };
|
|
export type { PlatformBrowseHistoryEntry };
|
|
export type { ProfileSaveArchiveSummary };
|