/** * Генератор временных ID для offline заметок */ export function generateTempId(): string { return `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Проверка, является ли ID временным */ export function isTempId(id: number | string): boolean { return typeof id === 'string' && id.startsWith('temp-'); } /** * Ожидание доступности IndexedDB */ export function waitForIndexedDB(): Promise { return new Promise((resolve, reject) => { const request = indexedDB.open('test-db', 1); request.onerror = () => { reject(request.error); }; request.onsuccess = () => { const db = request.result; db.close(); indexedDB.deleteDatabase('test-db'); resolve(request.result); }; request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result; db.createObjectStore('test'); }; }); } /** * Проверка состояния сети (более надежный метод) */ export async function checkNetworkStatus(): Promise { // Простая проверка navigator.onLine if (!navigator.onLine) { return false; } // Дополнительная проверка через fetch с коротким таймаутом try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 2000); // Используем /auth/status так как он всегда доступен при наличии сети const response = await fetch('/api/auth/status', { method: 'GET', signal: controller.signal, cache: 'no-cache', credentials: 'include', // Важно для cookie-based auth }); clearTimeout(timeoutId); return response.ok; } catch (error) { // Если запрос не удался, но navigator.onLine = true, считаем что онлайн // (возможно, просто таймаут или другая проблема) return navigator.onLine; } } /** * Проверка, доступна ли БД на клиенте */ export function isDatabaseAvailable(): boolean { return typeof indexedDB !== 'undefined'; } /** * Сохранение blob в base64 */ export function fileToBase64(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { if (typeof reader.result === 'string') { resolve(reader.result); } else { reject(new Error('Failed to convert file to base64')); } }; reader.onerror = reject; reader.readAsDataURL(file); }); } /** * Конвертация base64 обратно в Blob */ export function base64ToBlob(base64: string, mimeType: string): Blob { const byteCharacters = atob(base64.split(',')[1] || base64); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); return new Blob([byteArray], { type: mimeType }); } /** * Получение размера base64 строки в байтах */ export function getBase64Size(base64: string): number { const base64String = base64.split(',')[1] || base64; return Math.ceil((base64String.length * 3) / 4); } /** * Создание Listener для событий сети */ export class NetworkListener { private onlineHandler: ((event: Event) => void) | null = null; private offlineHandler: ((event: Event) => void) | null = null; onOnline(callback: (event: Event) => void): void { this.onlineHandler = callback; window.addEventListener('online', callback); } onOffline(callback: (event: Event) => void): void { this.offlineHandler = callback; window.addEventListener('offline', callback); } removeListeners(): void { if (this.onlineHandler) { window.removeEventListener('online', this.onlineHandler); this.onlineHandler = null; } if (this.offlineHandler) { window.removeEventListener('offline', this.offlineHandler); this.offlineHandler = null; } } } export const networkListener = new NetworkListener();