noteJS-react/src/utils/offlineManager.ts

148 lines
4.3 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.

/**
* Генератор временных 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<IDBDatabase> {
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<boolean> {
// Простая проверка 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<string> {
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();