Compare commits

..

No commits in common. "e6acd8c5dff261e4508e1c1a6ace9c017e74e501" and "e6ebf2cbff367d1a78966140353d433a2d315da1" have entirely different histories.

11 changed files with 205 additions and 507 deletions

File diff suppressed because one or more lines are too long

View File

@ -158,7 +158,7 @@
<!-- Manifest -->
<link rel="manifest" href="/manifest.json" />
<script type="module" crossorigin src="/assets/index-B61qRIc-.js"></script>
<script type="module" crossorigin src="/assets/index-42KwbWCP.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DK8OUj6L.css">
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
<body>

View File

@ -1 +0,0 @@
if('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/sw.js', { scope: '/' })})}

View File

@ -1 +1 @@
if(!self.define){let e,i={};const n=(n,c)=>(n=new URL(n+".js",c).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didnt register its module`);return e}));self.define=(c,o)=>{const s=e||("document"in self?document.currentScript.src:"")||location.href;if(i[s])return;let a={};const d=e=>n(e,s),r={module:{uri:s},exports:a,require:d};i[s]=Promise.all(c.map(e=>r[e]||d(e))).then(e=>(o(...e),a))}}define(["./workbox-e20531c6"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"assets/index-B61qRIc-.js",revision:"96888e49126c254a0b6fb7a9428bddb6"},{url:"assets/index-DK8OUj6L.css",revision:"b1e2c4e8724be2f2bcee585338910e99"},{url:"icon.svg",revision:"0ec61aab261526d4c491e887a6f3374e"},{url:"icons/icon-128x128.png",revision:"fa71db17e345406d5f7d847f88c65ac4"},{url:"icons/icon-144x144.png",revision:"e790ff42758ea1a2a46eb84201630757"},{url:"icons/icon-152x152.png",revision:"88f2400f6617a32cc9cd62c70fb49a05"},{url:"icons/icon-16x16.png",revision:"101c13808e9fd0956f247bc446a8ac1e"},{url:"icons/icon-192x192.png",revision:"7d86d2d2ada99d7cee015dff0fdcb497"},{url:"icons/icon-32x32.png",revision:"22ee5d42535bc339ab0e19cb496378a5"},{url:"icons/icon-384x384.png",revision:"c601fa602952a903389e5e8f8a699617"},{url:"icons/icon-48x48.png",revision:"cfdd3bebd931375f2e0277d638ec8781"},{url:"icons/icon-512x512.png",revision:"8731edef999b9e7deba310d72a739925"},{url:"icons/icon-72x72.png",revision:"6b3cb1b2537ec91921698260a9c2f47c"},{url:"icons/icon-96x96.png",revision:"7efd757a81217207d981de88ef199d86"},{url:"index.html",revision:"45ec10836831308e81415ddb8cc82efd"},{url:"logo.svg",revision:"5962d0d24d9cd26cd8aaff9cb6f54a5a"},{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"icon.svg",revision:"0ec61aab261526d4c491e887a6f3374e"},{url:"icons/icon-192x192.png",revision:"7d86d2d2ada99d7cee015dff0fdcb497"},{url:"icons/icon-512x512.png",revision:"8731edef999b9e7deba310d72a739925"},{url:"icons/icon-72x72.png",revision:"6b3cb1b2537ec91921698260a9c2f47c"},{url:"icons/icon-96x96.png",revision:"7efd757a81217207d981de88ef199d86"},{url:"icons/icon-128x128.png",revision:"fa71db17e345406d5f7d847f88c65ac4"},{url:"icons/icon-144x144.png",revision:"e790ff42758ea1a2a46eb84201630757"},{url:"icons/icon-152x152.png",revision:"88f2400f6617a32cc9cd62c70fb49a05"},{url:"icons/icon-384x384.png",revision:"c601fa602952a903389e5e8f8a699617"},{url:"manifest.webmanifest",revision:"1c071cadebd7a1b0dc1eeb0270e73fb8"}],{ignoreURLParametersMatching:[/^utm_/,/^fbclid$/]}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api/,/^\/uploads/]})),e.registerRoute(/^https:\/\/api\./,new e.NetworkFirst({cacheName:"api-cache",plugins:[new e.ExpirationPlugin({maxEntries:50,maxAgeSeconds:3600})]}),"GET"),e.registerRoute(/\/api\//,new e.NetworkFirst({cacheName:"api-cache-local",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:100,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/uploads\//,new e.CacheFirst({cacheName:"uploads-cache",plugins:[new e.ExpirationPlugin({maxEntries:200,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\.(?:png|jpg|jpeg|svg|gif|webp)$/,new e.CacheFirst({cacheName:"images-cache",plugins:[new e.ExpirationPlugin({maxEntries:100,maxAgeSeconds:2592e3})]}),"GET")});
if(!self.define){let e,i={};const n=(n,c)=>(n=new URL(n+".js",c).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didnt register its module`);return e}));self.define=(c,o)=>{const s=e||("document"in self?document.currentScript.src:"")||location.href;if(i[s])return;let r={};const d=e=>n(e,s),a={module:{uri:s},exports:r,require:d};i[s]=Promise.all(c.map(e=>a[e]||d(e))).then(e=>(o(...e),r))}}define(["./workbox-57555046"],function(e){"use strict";self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.precacheAndRoute([{url:"assets/index-CRKRzJj1.js",revision:null},{url:"assets/index-QEK5TGz3.css",revision:null},{url:"assets/workbox-window.prod.es5-B9K5rw8f.js",revision:null},{url:"icon.svg",revision:"537ae73d8f9e90e6a01816aa6d527d16"},{url:"icons/icon-128x128.png",revision:"fa71db17e345406d5f7d847f88c65ac4"},{url:"icons/icon-144x144.png",revision:"e790ff42758ea1a2a46eb84201630757"},{url:"icons/icon-152x152.png",revision:"88f2400f6617a32cc9cd62c70fb49a05"},{url:"icons/icon-16x16.png",revision:"101c13808e9fd0956f247bc446a8ac1e"},{url:"icons/icon-192x192.png",revision:"7d86d2d2ada99d7cee015dff0fdcb497"},{url:"icons/icon-32x32.png",revision:"22ee5d42535bc339ab0e19cb496378a5"},{url:"icons/icon-384x384.png",revision:"c601fa602952a903389e5e8f8a699617"},{url:"icons/icon-48x48.png",revision:"cfdd3bebd931375f2e0277d638ec8781"},{url:"icons/icon-512x512.png",revision:"8731edef999b9e7deba310d72a739925"},{url:"icons/icon-72x72.png",revision:"6b3cb1b2537ec91921698260a9c2f47c"},{url:"icons/icon-96x96.png",revision:"7efd757a81217207d981de88ef199d86"},{url:"index.html",revision:"52c85beb0841c0c7c8ddf774370cff39"},{url:"logo.svg",revision:"11616ede8898b4c24203e331b3ec6dc3"},{url:"icons/icon-72x72.png",revision:"6b3cb1b2537ec91921698260a9c2f47c"},{url:"icons/icon-96x96.png",revision:"7efd757a81217207d981de88ef199d86"},{url:"icons/icon-128x128.png",revision:"fa71db17e345406d5f7d847f88c65ac4"},{url:"icons/icon-144x144.png",revision:"e790ff42758ea1a2a46eb84201630757"},{url:"icons/icon-152x152.png",revision:"88f2400f6617a32cc9cd62c70fb49a05"},{url:"icons/icon-192x192.png",revision:"7d86d2d2ada99d7cee015dff0fdcb497"},{url:"icons/icon-384x384.png",revision:"c601fa602952a903389e5e8f8a699617"},{url:"icons/icon-512x512.png",revision:"8731edef999b9e7deba310d72a739925"},{url:"manifest.webmanifest",revision:"1c071cadebd7a1b0dc1eeb0270e73fb8"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html"))),e.registerRoute(/^https:\/\/api\./i,new e.NetworkFirst({cacheName:"api-cache",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:50,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:png|jpg|jpeg|svg|gif|webp)$/i,new e.CacheFirst({cacheName:"image-cache",plugins:[new e.ExpirationPlugin({maxEntries:100,maxAgeSeconds:2592e3})]}),"GET")});

File diff suppressed because one or more lines are too long

View File

@ -785,8 +785,7 @@ app.get("/api/auth/status", (req, res) => {
username: req.session.username,
});
} else {
// Возвращаем 200, так как неавторизованное состояние - это норма, а не ошибка
res.status(200).json({ authenticated: false });
res.status(401).json({ authenticated: false });
}
});

View File

@ -42,23 +42,13 @@ axiosClient.interceptors.response.use(
"/user/delete-account", // Удаление аккаунта
];
// URL, где 401 не должен обрабатываться как ошибка сессии
const statusCheckUrls = [
"/auth/status", // Проверка статуса аутентификации
];
// Проверяем, является ли это запросом с проверкой пароля
const isPasswordProtected = passwordProtectedUrls.some((url) =>
error.config?.url?.includes(url)
);
// Проверяем, является ли это запросом проверки статуса
const isStatusCheck = statusCheckUrls.some((url) =>
error.config?.url?.includes(url)
);
// Разлогиниваем только если это НЕ запрос с проверкой пароля и НЕ проверка статуса
if (!isPasswordProtected && !isStatusCheck) {
// Разлогиниваем только если это НЕ запрос с проверкой пароля
if (!isPasswordProtected) {
// Очищаем IndexedDB при автоматическом разлогинивании
dbManager.clearAll().catch((err) => {
console.error("Ошибка очистки IndexedDB при 401:", err);

View File

@ -239,62 +239,10 @@ export const offlineNotesApi = {
store.dispatch(addNote(noteWithSyncStatus));
return noteWithSyncStatus;
} 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;
}
} catch (error) {
console.error("Error creating note, falling back to local:", error);
// Fallback на локальное создание
return offlineNotesApi.create(note);
}
},

View File

@ -24,29 +24,7 @@ async function initOfflineMode() {
console.log('[Init] IndexedDB initialized');
// Проверка состояния сети
// Сначала проверяем 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;
}
const isOnline = await checkNetworkStatus();
store.dispatch(setOfflineMode(!isOnline));
console.log(`[Init] Network status: ${isOnline ? 'online' : 'offline'}`);

View File

@ -47,7 +47,6 @@ export async function checkNetworkStatus(): Promise<boolean> {
}
// Дополнительная проверка через fetch с коротким таймаутом
// Используем короткий таймаут для быстрого определения оффлайна
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 2000);
@ -62,36 +61,9 @@ export async function checkNetworkStatus(): Promise<boolean> {
clearTimeout(timeoutId);
return response.ok;
} 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, считаем что онлайн
// (возможно, просто другая проблема, но сеть есть)
} catch (error) {
// Если запрос не удался, но navigator.onLine = true, считаем что онлайн
// (возможно, просто таймаут или другая проблема)
return navigator.onLine;
}
}

View File

@ -89,29 +89,6 @@ export default defineConfig({
},
workbox: {
globPatterns: ["**/*.{js,css,html,ico,png,svg,woff,woff2,ttf,eot}"],
// Игнорируем параметры URL при кешировании
ignoreURLParametersMatching: [/^utm_/, /^fbclid$/],
// Обработка ошибок при precaching - не падаем на 404
navigateFallback: "/index.html",
navigateFallbackDenylist: [/^\/api/, /^\/uploads/],
// Обработка ошибок при загрузке файлов для precaching
dontCacheBustURLsMatching: /\.\w{8}\./,
// Фильтруем манифест, чтобы исключить несуществующие файлы и дубликаты
manifestTransforms: [
async (manifestEntries) => {
// Фильтруем дубликаты
const seen = new Set<string>();
const filtered = manifestEntries.filter((entry) => {
// Удаляем дубликаты
if (seen.has(entry.url)) {
return false;
}
seen.add(entry.url);
return true;
});
return { manifest: filtered, warnings: [] };
},
],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\./,
@ -162,8 +139,6 @@ export default defineConfig({
cleanupOutdatedCaches: true,
skipWaiting: true,
clientsClaim: true,
// Обработка ошибок при precaching - игнорируем 404 ошибки
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5MB
},
registerType: "prompt",
devOptions: {