Files
popa-launcher/src/renderer/api.ts
2025-12-13 20:14:24 +05:00

1181 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

export const API_BASE_URL = 'https://minecraft.api.popa-popa.ru';
export interface Player {
uuid: string;
username: string;
skin_url: string;
cloak_url: string;
coins: number;
is_active: boolean;
created_at: string;
}
export interface CoinsResponse {
username: string;
coins: number;
total_time_played: {
seconds: number;
formatted: string;
};
}
export interface Cape {
cape_id: string;
cape_name: string;
cape_description: string;
image_url: string;
purchased_at: string;
is_active: boolean;
}
export type CapesResponse = Cape[];
export interface StoreCape {
id: string;
name: string;
description: string;
price: number;
image_url: string;
}
export type StoreCapesResponse = StoreCape[];
export interface ApiError {
message: string;
details?: string;
}
export interface Server {
id: string;
name: string;
ip: string;
port: number;
description: string;
online_players: number;
max_players: number;
last_activity: string;
}
export interface ActiveServersResponse {
servers: Server[];
}
export interface OnlinePlayersResponse {
server: {
id: string;
name: string;
};
online_players: {
// Это массив объектов, а не один объект
username: string;
uuid: string;
online_since: string;
}[]; // Добавьте [] здесь чтобы указать, что это массив
count: number;
}
export interface MarketplaceResponse {
items: [
{
_id: string;
id: string;
material: string;
amount: number;
price: number;
seller_name: string;
server_ip: string;
display_name: string | null;
lore: string | null;
enchants: string | null;
item_data: {
slot: number;
material: string;
amount: number;
};
created_at: string;
},
];
total: number;
page: number;
pages: number;
}
export interface MarketplaceItemResponse {
_id: string;
id: string;
material: string;
amount: number;
price: number;
seller_name: string;
server_ip: string;
display_name: string | null;
lore: string | null;
enchants: string | null;
item_data: {
slot: number;
material: string;
amount: number;
};
created_at: string;
}
export interface SellItemResponse {
message: string;
}
export interface BuyItemResponse {
message: string;
}
export interface PlayerInventoryResponse {
status: string;
request_id: string;
}
export interface PlayerInventory {
status: string;
result: {
player_name: string;
server_ip: string;
inventory_data: PlayerInventoryItem[];
};
}
export interface PlayerInventoryItem {
slot: number;
material: string;
amount: number;
enchants: {
[key: string]: number;
};
}
export interface MarketplaceOperation {
id: string;
type: 'sell' | 'buy';
player_name: string;
slot_index?: number;
amount?: number;
price: number;
server_ip: string;
status: 'pending' | 'completed' | 'failed';
item_id?: string;
error?: string;
created_at: string;
item_data?: any;
}
export interface OperationsResponse {
operations: MarketplaceOperation[];
}
export interface RegisterUserResponse {
status: string;
uuid: string;
}
export interface GenerateVerificationCodeResponse {
status: string;
code: string;
}
export interface VerificationStatusResponse {
is_verified: boolean;
}
export interface NewsItem {
id: string;
title: string;
markdown: string;
preview?: string;
tags?: string[];
is_published?: boolean;
created_at: string;
updated_at: string;
}
export interface CreateNewsPayload {
title: string;
preview?: string;
markdown: string;
is_published: boolean;
}
export interface MeResponse {
username: string;
uuid: string;
is_admin: boolean;
}
// ===== БОНУСЫ / ПРОКАЧКА =====
export interface UserBonus {
id: string;
bonus_type_id: string;
name: string;
description: string;
effect_type: string;
effect_value: number;
level: number;
purchased_at: string;
can_upgrade: boolean;
upgrade_price: number;
is_active: boolean;
is_permanent: boolean;
expires_at?: string;
image_url?: string;
}
export type UserBonusesResponse = {
bonuses: UserBonus[];
};
export interface BonusType {
id: string;
name: string;
description: string;
effect_type: string;
base_effect_value: number;
effect_increment: number;
price: number;
upgrade_price: number;
duration: number;
max_level: number;
image_url?: string;
}
export type BonusTypesResponse = {
bonuses: BonusType[];
};
export interface DailyQuest {
key: string;
title: string;
event?: string;
target?: string;
required: number;
progress: number;
reward: number;
status: 'active' | 'completed' | 'claimed';
claimed_at?: string;
completed_at?: string;
}
export interface DailyQuestsStatusResponse {
ok: boolean;
day: string;
was_online_today: boolean;
seconds_to_next: number;
next_reset_at_utc: string;
next_reset_at_local: string;
quests: DailyQuest[];
}
export interface DailyQuestClaimResponse {
claimed: boolean;
coins_added?: number;
reason?: string;
message?: string;
}
export async function fetchBonusTypes(): Promise<BonusType[]> {
const response = await fetch(`${API_BASE_URL}/api/bonuses/types`);
if (!response.ok) {
throw new Error('Не удалось получить список прокачек');
}
const data: BonusTypesResponse = await response.json();
return data.bonuses || [];
}
export async function fetchUserBonuses(username: string): Promise<UserBonus[]> {
const response = await fetch(`${API_BASE_URL}/api/bonuses/user/${username}`);
if (!response.ok) {
throw new Error('Не удалось получить бонусы игрока');
}
const data: UserBonusesResponse = await response.json();
return data.bonuses || [];
}
export async function purchaseBonus(
username: string,
bonus_type_id: string,
): Promise<{
status: string;
message: string;
remaining_coins?: number;
}> {
const response = await fetch(`${API_BASE_URL}/api/bonuses/purchase`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
bonus_type_id,
}),
});
if (!response.ok) {
let msg = 'Не удалось купить прокачку';
try {
const errorData = await response.json();
if (errorData.message) msg = errorData.message;
else if (Array.isArray(errorData.detail)) {
msg = errorData.detail.map((d: any) => d.msg).join(', ');
} else if (typeof errorData.detail === 'string') {
msg = errorData.detail;
}
} catch {
// оставляем дефолтное сообщение
}
throw new Error(msg);
}
return await response.json();
}
export async function upgradeBonus(
username: string,
bonus_id: string,
): Promise<{
status: string;
message: string;
bonus_id: string;
new_level?: number;
}> {
const response = await fetch(`${API_BASE_URL}/api/bonuses/upgrade`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
bonus_id,
}),
});
if (!response.ok) {
let msg = 'Не удалось улучшить бонус';
try {
const errorData = await response.json();
if (errorData.message) msg = errorData.message;
else if (Array.isArray(errorData.detail)) {
msg = errorData.detail.map((d: any) => d.msg).join(', ');
} else if (typeof errorData.detail === 'string') {
msg = errorData.detail;
}
} catch {
// оставляем дефолтное сообщение
}
throw new Error(msg);
}
return await response.json();
}
export async function toggleBonusActivation(
username: string,
bonus_id: string,
): Promise<{
status: string;
message: string;
bonus_id: string;
is_active: boolean;
}> {
const response = await fetch(
`${API_BASE_URL}/api/bonuses/toggle-activation`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
bonus_id,
}),
},
);
if (!response.ok) {
let msg = 'Не удалось переключить бонус';
try {
const errorData = await response.json();
if (errorData.message) msg = errorData.message;
else if (Array.isArray(errorData.detail)) {
msg = errorData.detail.map((d: any) => d.msg).join(', ');
} else if (typeof errorData.detail === 'string') {
msg = errorData.detail;
}
} catch {
// оставляем дефолтное сообщение
}
throw new Error(msg);
}
return await response.json();
}
// ===== КЕЙСЫ =====
export interface CaseItemMeta {
display_name?: string | null;
lore?: string[] | null;
}
export interface CaseItem {
id: string;
name?: string;
material: string;
amount: number;
weight?: number;
meta?: CaseItemMeta;
}
export interface Case {
id: string;
name: string;
description?: string;
price: number;
image_url?: string;
server_ids?: string[];
items_count?: number;
items?: CaseItem[];
}
export interface OpenCaseResponse {
status: string;
message: string;
operation_id: string;
balance: number;
reward: CaseItem;
}
export async function fetchCases(): Promise<Case[]> {
const response = await fetch(`${API_BASE_URL}/cases`);
if (!response.ok) {
throw new Error('Не удалось получить список кейсов');
}
return await response.json();
}
// Если у тебя есть отдельный эндпоинт деталей кейса, можно использовать это:
export async function fetchCase(case_id: string): Promise<Case> {
const response = await fetch(`${API_BASE_URL}/cases/${case_id}`);
if (!response.ok) {
throw new Error('Не удалось получить информацию о кейсе');
}
return await response.json();
}
export async function openCase(
case_id: string,
username: string,
server_id: string,
): Promise<OpenCaseResponse> {
// Формируем URL с query-параметрами, как любит текущий бэкенд
const url = new URL(`${API_BASE_URL}/cases/${case_id}/open`);
url.searchParams.append('username', username);
url.searchParams.append('server_id', server_id);
const response = await fetch(url.toString(), {
method: 'POST',
});
if (!response.ok) {
let msg = 'Не удалось открыть кейс';
try {
const errorData = await response.json();
if (errorData.message) {
msg = errorData.message;
} else if (Array.isArray(errorData.detail)) {
msg = errorData.detail.map((d: any) => d.msg).join(', ');
} else if (typeof errorData.detail === 'string') {
msg = errorData.detail;
}
} catch {
// если бэкенд вернул не-JSON, оставляем дефолтное сообщение
}
throw new Error(msg);
}
return await response.json();
}
// ===== КЕЙСЫ ===== \\
// ===== Ежедневная награда =====
export interface DailyStatusResponse {
ok: boolean;
can_claim: boolean;
seconds_to_next: number;
was_online_today: boolean;
next_claim_at_utc: string;
next_claim_at_local: string;
streak: number;
}
export interface DailyClaimResponse {
claimed: boolean;
coins_added?: number;
streak?: number;
reason?: string;
}
export interface DailyDaysResponse {
ok: boolean;
days: string[]; // YYYY-MM-DD (по ЕКБ)
count: number;
}
export async function fetchDailyClaimDays(limit = 120): Promise<DailyDaysResponse> {
const { accessToken, clientToken } = getAuthTokens();
if (!accessToken || !clientToken) {
throw new Error('Нет токенов авторизации');
}
const params = new URLSearchParams({
accessToken,
clientToken,
limit: String(limit),
});
const response = await fetch(
`${API_BASE_URL}/users/daily/days?${params.toString()}`,
);
if (!response.ok) {
throw new Error('Не удалось получить дни ежедневных наград');
}
return await response.json();
}
export async function fetchDailyStatus(): Promise<DailyStatusResponse> {
const { accessToken, clientToken } = getAuthTokens();
if (!accessToken || !clientToken) {
throw new Error('Нет токенов авторизации');
}
const params = new URLSearchParams({ accessToken, clientToken });
const response = await fetch(
`${API_BASE_URL}/users/daily/status?${params.toString()}`,
);
if (!response.ok) {
throw new Error('Не удалось получить статус ежедневной награды');
}
return await response.json();
}
export async function claimDaily(): Promise<DailyClaimResponse> {
const { accessToken, clientToken } = getAuthTokens();
if (!accessToken || !clientToken) {
throw new Error('Нет токенов авторизации');
}
const params = new URLSearchParams({ accessToken, clientToken });
const response = await fetch(
`${API_BASE_URL}/users/daily/claim?${params.toString()}`,
{ method: 'POST' },
);
if (!response.ok) {
let msg = 'Не удалось забрать ежедневную награду';
try {
const err = await response.json();
msg = err.message || err.detail || msg;
} catch {}
throw new Error(msg);
}
return await response.json();
}
// ===== Ежедневная награда ===== \\
// ===== Ежедневные квесты =====
export async function fetchDailyQuestsStatus(): Promise<DailyQuestsStatusResponse> {
const { accessToken, clientToken } = getAuthTokens();
if (!accessToken || !clientToken) {
throw new Error('Нет токенов авторизации');
}
const params = new URLSearchParams({ accessToken, clientToken });
const response = await fetch(
`${API_BASE_URL}/users/daily-quests/status?${params.toString()}`,
);
if (!response.ok) {
throw new Error('Не удалось получить статус ежедневных квестов');
}
return await response.json();
}
export async function claimDailyQuest(questKey: string): Promise<DailyQuestClaimResponse> {
const { accessToken, clientToken } = getAuthTokens();
if (!accessToken || !clientToken) {
throw new Error('Нет токенов авторизации');
}
const params = new URLSearchParams({ accessToken, clientToken, quest_key: questKey });
const response = await fetch(
`${API_BASE_URL}/users/daily-quests/claim?${params.toString()}`,
{ method: 'POST' },
);
if (!response.ok) {
let msg = 'Не удалось забрать награду за квест';
try {
const err = await response.json();
msg = err.message || err.detail || msg;
} catch {}
throw new Error(msg);
}
return await response.json();
}
// ===== Ежедневные квесты ===== \\
export async function fetchMe(): Promise<MeResponse> {
const { accessToken, clientToken } = getAuthTokens();
if (!accessToken || !clientToken) {
throw new Error('Нет токенов авторизации');
}
const params = new URLSearchParams({
accessToken,
clientToken,
});
const response = await fetch(`${API_BASE_URL}/auth/me?${params.toString()}`);
if (!response.ok) {
throw new Error('Не удалось получить данные пользователя');
}
return await response.json();
}
export async function createNews(payload: CreateNewsPayload) {
const { accessToken, clientToken } = getAuthTokens(); // ← используем launcher_config
if (!accessToken || !clientToken) {
throw new Error('Необходимо войти в лаунчер, чтобы публиковать новости');
}
const formData = new FormData();
formData.append('accessToken', accessToken);
formData.append('clientToken', clientToken);
formData.append('title', payload.title);
formData.append('markdown', payload.markdown);
formData.append('preview', payload.preview || '');
formData.append('is_published', String(payload.is_published));
const response = await fetch(`${API_BASE_URL}/news`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const text = await response.text();
throw new Error(text || 'Ошибка при создании новости');
}
return await response.json();
}
export async function deleteNews(id: string): Promise<void> {
const { accessToken, clientToken } = getAuthTokens();
if (!accessToken || !clientToken) {
throw new Error('Необходимо войти в лаунчер');
}
const params = new URLSearchParams({
accessToken,
clientToken,
});
const response = await fetch(
`${API_BASE_URL}/news/${id}?${params.toString()}`,
{
method: 'DELETE',
},
);
if (!response.ok) {
const text = await response.text();
throw new Error(text || 'Не удалось удалить новость');
}
}
export async function fetchNews(): Promise<NewsItem[]> {
const response = await fetch(`${API_BASE_URL}/news`);
if (!response.ok) {
throw new Error('Не удалось получить новости');
}
return await response.json();
}
export async function getVerificationStatus(
username: string,
): Promise<VerificationStatusResponse> {
const response = await fetch(
`${API_BASE_URL}/auth/verification_status/${username}`,
);
if (!response.ok) {
throw new Error('Не удалось получить статус верификации');
}
return await response.json();
}
export async function generateVerificationCode(
username: string,
): Promise<GenerateVerificationCodeResponse> {
const response = await fetch(
`${API_BASE_URL}/auth/generate_code?username=${username}`,
{
method: 'POST',
},
);
if (!response.ok) {
throw new Error('Не удалось сгенерировать код верификации');
}
return await response.json();
}
export async function registerUser(
username: string,
password: string,
): Promise<RegisterUserResponse> {
const response = await fetch(`${API_BASE_URL}/auth/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password,
}),
});
if (!response.ok) {
throw new Error('Не удалось зарегистрировать пользователя');
}
return await response.json();
}
export async function getPlayerInventory(
request_id: string,
): Promise<PlayerInventory> {
const response = await fetch(
`${API_BASE_URL}/api/server/inventory/${request_id}`,
);
if (!response.ok) {
throw new Error('Не удалось получить инвентарь игрока');
}
return await response.json();
}
export async function RequestPlayerInventory(
server_ip: string,
player_name: string,
): Promise<PlayerInventoryResponse> {
const response = await fetch(`${API_BASE_URL}/api/server/inventory`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
server_ip: server_ip,
player_name: player_name,
}),
});
if (!response.ok) {
throw new Error('Не удалось получить инвентарь игрока');
}
return await response.json();
}
export async function buyItem(
buyer_username: string,
item_id: string,
): Promise<{ status: string; operation_id: string; message: string }> {
const response = await fetch(
`${API_BASE_URL}/api/marketplace/items/buy/${item_id}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: buyer_username,
}),
},
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.message || errorData.detail || 'Не удалось купить предмет',
);
}
return await response.json();
}
export async function confirmMarketplaceOperation(
operation_id: string,
status: string = 'success',
error?: string,
): Promise<{ status: string }> {
const response = await fetch(
`${API_BASE_URL}/api/marketplace/operations/confirm`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
operation_id,
status,
error,
}),
},
);
if (!response.ok) {
throw new Error('Не удалось подтвердить операцию');
}
return await response.json();
}
export async function submitItemDetails(
operation_id: string,
item_data: any,
): Promise<{ status: string }> {
const response = await fetch(
`${API_BASE_URL}/api/marketplace/items/details`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
operation_id,
item_data,
}),
},
);
if (!response.ok) {
throw new Error('Не удалось отправить данные предмета');
}
return await response.json();
}
export async function sellItem(
username: string,
slot_index: number,
amount: number,
price: number,
server_ip: string,
): Promise<{ status: string; operation_id: string }> {
const response = await fetch(`${API_BASE_URL}/api/marketplace/items/sell`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
slot_index,
amount,
price,
server_ip,
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.message ||
errorData.detail ||
'Не удалось выставить предмет на продажу',
);
}
return await response.json();
}
export async function fetchMarketplaceItem(
item_id: string,
): Promise<MarketplaceItemResponse> {
const response = await fetch(
`${API_BASE_URL}/api/marketplace/items/${item_id}`,
);
if (!response.ok) {
throw new Error('Не удалось получить рынок');
}
return await response.json();
}
export async function fetchMarketplace(
server_ip: string,
page: number,
limit: number,
): Promise<MarketplaceResponse> {
// Создаем URL с параметрами запроса
const url = new URL(`${API_BASE_URL}/api/marketplace/items`);
url.searchParams.append('server_ip', server_ip);
url.searchParams.append('page', page.toString());
url.searchParams.append('limit', limit.toString());
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error('Не удалось получить предметы рынка');
}
return await response.json();
}
// Исправьте тип возвращаемого значения
export async function fetchActiveServers(): Promise<Server[]> {
const response = await fetch(`${API_BASE_URL}/api/pranks/servers`);
if (!response.ok) {
throw new Error('Не удалось получить активные сервера');
}
return await response.json();
}
export async function fetchOnlinePlayers(
server_id: string,
): Promise<OnlinePlayersResponse> {
const response = await fetch(
`${API_BASE_URL}/api/pranks/servers/${server_id}/players`,
);
if (!response.ok) {
throw new Error('Не удалось получить онлайн игроков');
}
return await response.json();
}
// Получение информации о игроке
export async function fetchPlayer(uuid: string): Promise<Player> {
try {
const response = await fetch(`${API_BASE_URL}/users/${uuid}`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Ошибка получения данных игрока');
}
return await response.json();
} catch (error) {
console.error('API ошибка:', error);
throw error;
}
}
export async function fetchCoins(username: string): Promise<CoinsResponse> {
try {
const response = await fetch(`${API_BASE_URL}/users/${username}/coins`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Ошибка получения данных игрока');
}
return await response.json();
} catch (error) {
console.error('API ошибка:', error);
throw error;
}
}
export async function fetchCapes(username: string): Promise<CapesResponse> {
try {
const response = await fetch(
`${API_BASE_URL}/store/user/${username}/capes`,
);
if (!response.ok) {
return []; // Если плащи не найдены, возвращаем пустой массив
}
return await response.json();
} catch (error) {
console.error('API ошибка:', error);
return []; // В случае ошибки возвращаем пустой массив
}
}
export async function purchaseCape(
username: string,
cape_id: string,
): Promise<void> {
// Создаем URL с query-параметрами
const url = new URL(`${API_BASE_URL}/store/purchase/cape`);
url.searchParams.append('username', username);
url.searchParams.append('cape_id', cape_id);
const response = await fetch(url.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
// Не нужно отправлять тело запроса
// body: JSON.stringify({
// username: username,
// cape_id: cape_id,
// }),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.message ||
errorData.detail?.toString() ||
'Не удалось купить плащ',
);
}
}
export async function fetchCapesStore(): Promise<StoreCape[]> {
try {
const response = await fetch(`${API_BASE_URL}/store/capes`);
if (!response.ok) {
return [];
}
return await response.json();
} catch (error) {
console.error('API ошибка:', error);
return [];
}
}
export async function activateCape(
username: string,
cape_id: string,
): Promise<void> {
const response = await fetch(
`${API_BASE_URL}/store/user/${username}/capes/activate/${cape_id}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cape_id: cape_id,
}),
},
);
if (!response.ok) {
throw new Error('Не удалось активировать плащ');
}
}
export async function deactivateCape(
username: string,
cape_id: string,
): Promise<void> {
const response = await fetch(
`${API_BASE_URL}/store/user/${username}/capes/deactivate/${cape_id}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cape_id: cape_id,
}),
},
);
if (!response.ok) {
throw new Error('Не удалось деактивировать плащ');
}
}
// Загрузка скина
export async function uploadSkin(
username: string,
skinFile: File,
skinModel: string,
): Promise<void> {
const savedConfig = localStorage.getItem('launcher_config');
let accessToken = '';
let clientToken = '';
if (savedConfig) {
const config = JSON.parse(savedConfig);
accessToken = config.accessToken || '';
clientToken = config.clientToken || '';
}
const formData = new FormData();
formData.append('skin_file', skinFile);
formData.append('skin_model', skinModel);
formData.append('accessToken', accessToken);
formData.append('clientToken', clientToken);
const response = await fetch(`${API_BASE_URL}/user/${username}/skin`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Не удалось загрузить скин');
}
}
// Получение токенов из локального хранилища
export function getAuthTokens(): { accessToken: string; clientToken: string } {
const savedConfig = localStorage.getItem('launcher_config');
let accessToken = '';
let clientToken = '';
if (savedConfig) {
const config = JSON.parse(savedConfig);
accessToken = config.accessToken || '';
clientToken = config.clientToken || '';
}
return { accessToken, clientToken };
}
// Загрузка плаща
export async function uploadCape(
username: string,
capeFile: File,
): Promise<void> {
const { accessToken, clientToken } = getAuthTokens();
const formData = new FormData();
formData.append('cape_file', capeFile);
formData.append('accessToken', accessToken);
formData.append('clientToken', clientToken);
const response = await fetch(`${API_BASE_URL}/user/${username}/cape`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Не удалось загрузить плащ');
}
}