From 351ba7eb03fb73c0c7090dab8b5471e5c0c78b93 Mon Sep 17 00:00:00 2001 From: Fovway Date: Thu, 6 Nov 2025 22:02:07 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20=D1=81=D0=B5=D1=82=D0=B5?= =?UTF-8?q?=D0=B2=D0=BE=D0=B3=D0=BE=20=D1=81=D1=82=D0=B0=D1=82=D1=83=D1=81?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?initOfflineMode,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D0=BF?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE?= =?UTF-8?q?=D1=84=D1=84=D0=BB=D0=B0=D0=B9=D0=BD-=D1=80=D0=B5=D0=B6=D0=B8?= =?UTF-8?q?=D0=BC=D0=B0.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BE=D0=BA,=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D1=81=D0=B5=D1=82=D0=B5?= =?UTF-8?q?=D0=B2=D1=8B=D1=85=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=B8=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BE=D0=BA.=20=D0=9E=D0=BF?= =?UTF-8?q?=D1=82=D0=B8=D0=BC=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F=20che?= =?UTF-8?q?ckNetworkStatus=20=D0=B4=D0=BB=D1=8F=20=D0=B1=D0=BE=D0=BB=D0=B5?= =?UTF-8?q?=D0=B5=20=D1=82=D0=BE=D1=87=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BE?= =?UTF-8?q?=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=D0=B5=D1=82=D0=B8=20=D1=81=20=D1=83=D1=87=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=BC=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B0=D1=83=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=B8=20=D1=80=D0=B0=D0=B7=D0=BB=D0=B8=D1=87=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D1=82=D0=B8=D0=BF=D0=BE=D0=B2=20=D0=BE=D1=88=D0=B8?= =?UTF-8?q?=D0=B1=D0=BE=D0=BA.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/offlineNotesApi.ts | 60 ++++++++++++++++++++++++++++++++++--- src/main.tsx | 24 ++++++++++++++- src/utils/offlineManager.ts | 34 +++++++++++++++++++-- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/src/api/offlineNotesApi.ts b/src/api/offlineNotesApi.ts index 4858784..125d121 100644 --- a/src/api/offlineNotesApi.ts +++ b/src/api/offlineNotesApi.ts @@ -239,10 +239,62 @@ export const offlineNotesApi = { store.dispatch(addNote(noteWithSyncStatus)); return noteWithSyncStatus; - } catch (error) { - console.error("Error creating note, falling back to local:", error); - // Fallback на локальное создание - return offlineNotesApi.create(note); + } catch (error: any) { + // Проверяем, является ли это сетевой ошибкой + const isNetworkError = + !error.response && + (error.code === 'ERR_NETWORK' || + error.message === 'Network Error' || + error.message?.includes('ERR_INTERNET_DISCONNECTED') || + error.message?.includes('Failed to fetch')); + + if (isNetworkError) { + console.error("Network error creating note, falling back to local:", error); + + // Принудительно обновляем статус сети при ошибке + lastNetworkCheck = { time: Date.now(), status: false }; + store.dispatch(setOfflineMode(true)); + + // Fallback на локальное создание напрямую, без рекурсии + console.log("[Offline] Creating note locally after network error"); + + const tempId = generateTempId(); + const now = new Date().toISOString(); + const newNote: Note = { + ...note, + id: tempId, + user_id: userId || 0, + created_at: now, + updated_at: now, + is_pinned: 0, + is_archived: 0, + images: [], + files: [], + syncStatus: "pending", + }; + + // Сохраняем в IndexedDB + await dbManager.saveNote(newNote); + + // Добавляем в очередь синхронизации + await dbManager.addToSyncQueue({ + type: "create", + noteId: tempId, + data: note, + timestamp: Date.now(), + retries: 0, + }); + + // Обновляем UI + store.dispatch(addNote(newNote)); + await updatePendingSyncCount(); + + return newNote; + } else { + // Если это не сетевая ошибка, пробрасываем её дальше + console.error("Error creating note (not a network error):", error); + throw error; + } } }, diff --git a/src/main.tsx b/src/main.tsx index bce2974..aa6a96b 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -24,7 +24,29 @@ async function initOfflineMode() { console.log('[Init] IndexedDB initialized'); // Проверка состояния сети - const isOnline = await checkNetworkStatus(); + // Сначала проверяем navigator.onLine для быстрой проверки + let isOnline: boolean = navigator.onLine; + + // Если navigator.onLine = false, точно оффлайн (не нужно делать fetch) + if (!navigator.onLine) { + isOnline = false; + } else { + // Если navigator.onLine = true, делаем дополнительную проверку через fetch + try { + isOnline = await checkNetworkStatus(); + } catch (error) { + // Если проверка сети упала с ошибкой, скорее всего мы оффлайн + console.warn('[Init] Network status check failed, assuming offline:', error); + isOnline = false; + } + } + + // Финальная проверка: если navigator.onLine = false, точно оффлайн + // Это важно, так как navigator.onLine может обновиться во время проверки + if (!navigator.onLine) { + isOnline = false; + } + store.dispatch(setOfflineMode(!isOnline)); console.log(`[Init] Network status: ${isOnline ? 'online' : 'offline'}`); diff --git a/src/utils/offlineManager.ts b/src/utils/offlineManager.ts index 5bfb948..066741f 100644 --- a/src/utils/offlineManager.ts +++ b/src/utils/offlineManager.ts @@ -47,6 +47,7 @@ export async function checkNetworkStatus(): Promise { } // Дополнительная проверка через fetch с коротким таймаутом + // Используем короткий таймаут для быстрого определения оффлайна try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 2000); @@ -61,9 +62,36 @@ export async function checkNetworkStatus(): Promise { clearTimeout(timeoutId); return response.ok; - } catch (error) { - // Если запрос не удался, но navigator.onLine = true, считаем что онлайн - // (возможно, просто таймаут или другая проблема) + } catch (error: any) { + // Если запрос не удался, проверяем тип ошибки + const isAbortError = error.name === 'AbortError'; // Таймаут + const isNetworkError = + error.message === 'Failed to fetch' || + error.message?.includes('NetworkError') || + error.message?.includes('ERR_INTERNET_DISCONNECTED') || + error.message?.includes('ERR_NETWORK') || + error.message?.includes('network request failed'); + + // Если это явная сетевая ошибка (не таймаут), точно оффлайн + if (isNetworkError && !isAbortError) { + return false; + } + + // Если это таймаут, проверяем navigator.onLine + // Таймаут может быть как из-за оффлайна, так и из-за медленного соединения + if (isAbortError) { + // Если navigator.onLine = false, точно оффлайн + if (!navigator.onLine) { + return false; + } + // Если navigator.onLine = true, но таймаут - возможно медленное соединение + // Но для безопасности считаем оффлайном, так как запрос не прошел + return false; + } + + // Если это не сетевая ошибка и не таймаут (например, CORS или другая проблема), + // но navigator.onLine = true, считаем что онлайн + // (возможно, просто другая проблема, но сеть есть) return navigator.onLine; } }