Initial commit MKD fixes

This commit is contained in:
Arsen
2026-02-04 00:17:04 +05:00
commit de94ad707b
312 changed files with 138754 additions and 0 deletions

206
services/connectionService.ts Executable file
View File

@@ -0,0 +1,206 @@
// Сервис управления подключением к серверу
export type ConnectionStatus = 'connected' | 'connecting' | 'disconnected';
type ConnectionStatusCallback = (status: ConnectionStatus) => void;
class ConnectionService {
private status: ConnectionStatus = 'disconnected';
private listeners: Set<ConnectionStatusCallback> = new Set();
private reconnectInterval: number | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = Infinity; // Бесконечные попытки
private reconnectDelay = 3000; // 3 секунды между попытками
private checkInterval: number | null = null;
private apiBaseUrl: string;
constructor() {
this.apiBaseUrl = import.meta.env.VITE_API_BASE_URL || '';
// Если API не настроен, сразу устанавливаем статус connected
if (!this.apiBaseUrl) {
this.setStatus('connected');
return;
}
this.startConnectionCheck();
this.initializeConnection();
}
// Инициализация подключения
private async initializeConnection() {
if (!this.apiBaseUrl) {
// Если API не настроен, считаем что подключение не требуется (используется localStorage)
this.setStatus('connected');
return;
}
this.setStatus('connecting');
await this.checkConnection();
}
// Проверка подключения
private async checkConnection(): Promise<boolean> {
if (!this.apiBaseUrl) {
this.setStatus('connected');
return true;
}
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 секунды таймаут
// Пробуем несколько endpoints для проверки подключения
const endpoints = ['/health', '/districts', '/buildings'];
let connected = false;
let lastError: Error | null = null;
for (const endpoint of endpoints) {
try {
const response = await fetch(`${this.apiBaseUrl}${endpoint}`, {
method: 'GET',
signal: controller.signal,
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
},
});
// Если получили ответ (даже с ошибкой 404, 403 и т.д.), сервер доступен
// 404 означает, что сервер работает, но endpoint не найден - это нормально
if (response.status < 500) {
connected = true;
break;
}
} catch (e) {
lastError = e instanceof Error ? e : new Error(String(e));
// Пробуем следующий endpoint
continue;
}
}
clearTimeout(timeoutId);
if (connected) {
this.setStatus('connected');
this.reconnectAttempts = 0;
this.stopReconnect();
return true;
} else {
// Проверяем, не была ли это ошибка сети (CORS, таймаут и т.д.)
const isNetworkError = lastError && (
lastError.name === 'AbortError' ||
lastError.message.includes('Failed to fetch') ||
lastError.message.includes('NetworkError') ||
lastError.message.includes('CORS')
);
if (isNetworkError) {
this.setStatus('disconnected');
this.startReconnect();
return false;
} else {
// Если это не ошибка сети, возможно сервер работает, но endpoint недоступен
// В этом случае считаем что подключение есть
this.setStatus('connected');
this.stopReconnect();
return true;
}
}
} catch (error) {
// Общая ошибка - считаем что нет подключения
this.setStatus('disconnected');
this.startReconnect();
return false;
}
}
// Установка статуса и уведомление слушателей
private setStatus(newStatus: ConnectionStatus) {
if (this.status !== newStatus) {
this.status = newStatus;
this.listeners.forEach(callback => callback(newStatus));
}
}
// Начало процесса переподключения
private startReconnect() {
if (this.reconnectInterval) {
return; // Уже переподключаемся
}
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
return; // Достигнут лимит попыток
}
this.reconnectInterval = window.setInterval(async () => {
this.reconnectAttempts++;
this.setStatus('connecting');
const connected = await this.checkConnection();
if (connected) {
this.stopReconnect();
}
}, this.reconnectDelay);
}
// Остановка процесса переподключения
private stopReconnect() {
if (this.reconnectInterval) {
clearInterval(this.reconnectInterval);
this.reconnectInterval = null;
}
this.reconnectAttempts = 0;
}
// Периодическая проверка подключения
private startConnectionCheck() {
// Проверяем каждые 10 секунд
this.checkInterval = window.setInterval(async () => {
if (this.status === 'connected') {
await this.checkConnection();
}
}, 10000);
}
// Получить текущий статус
getStatus(): ConnectionStatus {
return this.status;
}
// Подписаться на изменения статуса
subscribe(callback: ConnectionStatusCallback): () => void {
this.listeners.add(callback);
// Сразу вызываем callback с текущим статусом
callback(this.status);
// Возвращаем функцию отписки
return () => {
this.listeners.delete(callback);
};
}
// Принудительная проверка подключения
async forceCheck(): Promise<boolean> {
if (!this.apiBaseUrl) {
// Если API не настроен, считаем что подключение не требуется
this.setStatus('connected');
return true;
}
this.setStatus('connecting');
return await this.checkConnection();
}
// Очистка ресурсов
destroy() {
this.stopReconnect();
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
this.listeners.clear();
}
}
// Создаём единственный экземпляр сервиса
export const connectionService = new ConnectionService();