feat
This commit is contained in:
parent
8f785f7a7d
commit
b1135bc9ed
BIN
static/Screenshot 2026-07-29 at 04-00-56 Партнеры.png
Normal file
BIN
static/Screenshot 2026-07-29 at 04-00-56 Партнеры.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
BIN
static/photo_2026-07-29_02-45-09.jpg
Normal file
BIN
static/photo_2026-07-29_02-45-09.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
BIN
static/photo_2026-07-29_02-45-17.jpg
Normal file
BIN
static/photo_2026-07-29_02-45-17.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
static/photo_2026-07-29_02-45-22.jpg
Normal file
BIN
static/photo_2026-07-29_02-45-22.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@ -1,177 +1,200 @@
|
|||||||
|
importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-sw.js');
|
||||||
|
|
||||||
|
workbox.setConfig({ debug: false });
|
||||||
|
|
||||||
const CACHE_NAME = 'app-cache-v2.1.4';
|
const CACHE_NAME = 'app-cache-v2.1.4';
|
||||||
|
const MIN_VALID_SIZE = 500;
|
||||||
|
|
||||||
const ASSETS_TO_CACHE = [
|
const ASSETS_TO_CACHE = [
|
||||||
'/pwa2',
|
'/pwa2',
|
||||||
'/icon-192x192.png',
|
'/icon-192x192.png',
|
||||||
'/icon-512x512.png',
|
'/icon-512x512.png',
|
||||||
'/offline.html',
|
'/offline.html',
|
||||||
'/static/Inter-Bold.woff2',
|
'/static/Inter-Bold.woff2',
|
||||||
'/static/Inter-SemiBold.woff2',
|
'/static/Inter-SemiBold.woff2',
|
||||||
'/static/Inter-Regular.woff2',
|
'/static/Inter-Regular.woff2',
|
||||||
'/static/painter.svg',
|
'/static/painter.svg',
|
||||||
'/static/event1.jpg',
|
'/static/event1.jpg',
|
||||||
'/static/event2.jpg',
|
'/static/event2.jpg',
|
||||||
'/static/event3.jpg',
|
'/static/event3.jpg',
|
||||||
'/static/event4.jpg',
|
'/static/event4.jpg',
|
||||||
'/static/event5.jpg',
|
'/static/event5.jpg',
|
||||||
'/static/event6.jpg',
|
'/static/event6.jpg',
|
||||||
'/static/event7.webp',
|
'/static/event7.webp',
|
||||||
|
'/partner',
|
||||||
];
|
];
|
||||||
const MIN_VALID_SIZE = 500;
|
|
||||||
|
|
||||||
const NOCACHE_PATHS = [
|
const NOCACHE_PATHS = [
|
||||||
'/api',
|
'/api',
|
||||||
'/form',
|
'/form',
|
||||||
|
];
|
||||||
|
|
||||||
|
const VALIDATE_ON_MODIFY_PATHS = [
|
||||||
|
'/',
|
||||||
|
'/pwa',
|
||||||
|
'/paintings',
|
||||||
'/profile',
|
'/profile',
|
||||||
'/competitions',
|
'/competitions',
|
||||||
'/competition',
|
'/competition',
|
||||||
'/exhibitions',
|
'/exhibitions',
|
||||||
'/exhibition',
|
'/exhibition',
|
||||||
'/login',
|
'/login',
|
||||||
'/registration'
|
'/registration',
|
||||||
];
|
|
||||||
|
|
||||||
const VALIDATE_ON_MODIFY_PATHS = [
|
|
||||||
'/artists',
|
'/artists',
|
||||||
|
'/my-paints',
|
||||||
'/partner',
|
'/partner',
|
||||||
'/news',
|
'/news',
|
||||||
'/',
|
|
||||||
'/pwa',
|
|
||||||
'/paintings',
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
self.addEventListener('install', (e) => {
|
// ——— Lifecycle ———
|
||||||
e.waitUntil(
|
self.skipWaiting();
|
||||||
caches.open(CACHE_NAME).then((cache) => {
|
workbox.core.clientsClaim();
|
||||||
console.log('Caching static assets...');
|
|
||||||
return cache.addAll(ASSETS_TO_CACHE);
|
// Clean old caches + notify clients
|
||||||
})
|
self.addEventListener('activate', (event) => {
|
||||||
.then(() => {
|
event.waitUntil(
|
||||||
console.log('All assets cached successfully.');
|
(async () => {
|
||||||
return self.skipWaiting();
|
const keys = await caches.keys();
|
||||||
})
|
await Promise.all(
|
||||||
.catch((error) => {
|
keys
|
||||||
console.error('Failed to cache assets during install:', error);
|
.filter((key) => key !== CACHE_NAME)
|
||||||
})
|
.map((key) => {
|
||||||
);
|
console.log(`Deleting old cache: ${key}`);
|
||||||
|
return caches.delete(key);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const clients = await self.clients.matchAll();
|
||||||
|
clients.forEach((client) => client.postMessage({ type: 'SW_UPDATED' }));
|
||||||
|
console.log('Service Worker activated.');
|
||||||
|
})()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('activate', (e) => {
|
// ——— Precaching ———
|
||||||
e.waitUntil(
|
workbox.precaching.precacheAndRoute(
|
||||||
caches.keys().then((keys) =>
|
ASSETS_TO_CACHE.map((url) => ({ url, revision: null })),
|
||||||
Promise.all(
|
{
|
||||||
keys.map((key) => {
|
cacheName: CACHE_NAME,
|
||||||
if (key !== CACHE_NAME) {
|
}
|
||||||
console.log(`Deleting old cache: ${key}`);
|
);
|
||||||
return caches.delete(key);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
console.log('Service Worker activated.');
|
|
||||||
return self.clients.claim();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return self.clients.matchAll().then((clients) => {
|
|
||||||
clients.forEach((client) =>
|
|
||||||
client.postMessage({ type: 'SW_UPDATED' })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
self.addEventListener('fetch', (e) => {
|
|
||||||
if (e.request.method !== 'GET') return;
|
|
||||||
|
|
||||||
const url = new URL(e.request.url);
|
// ——— Runtime routes ———
|
||||||
|
|
||||||
if (NOCACHE_PATHS.some(path =>
|
// 1. Never cache these paths (NetworkOnly)
|
||||||
url.pathname === path || url.pathname.startsWith(path + '/')
|
workbox.routing.registerRoute(
|
||||||
)) {
|
({ url }) =>
|
||||||
return;
|
NOCACHE_PATHS.some(
|
||||||
}
|
(path) => url.pathname === path || url.pathname.startsWith(path + '/')
|
||||||
|
),
|
||||||
|
new workbox.strategies.NetworkOnly()
|
||||||
|
);
|
||||||
|
|
||||||
e.respondWith(
|
// 2. Paths that should revalidate on Last-Modified / Content-Length
|
||||||
(async () => {
|
workbox.routing.registerRoute(
|
||||||
const cache = await caches.open(CACHE_NAME);
|
({ url }) =>
|
||||||
const cachedResponse = await caches.match(e.request);
|
VALIDATE_ON_MODIFY_PATHS.some(
|
||||||
|
(path) => url.pathname === path || url.pathname.startsWith(path + '/')
|
||||||
|
),
|
||||||
|
new workbox.strategies.NetworkFirst({
|
||||||
|
cacheName: CACHE_NAME,
|
||||||
|
plugins: [
|
||||||
|
new workbox.cacheableResponse.CacheableResponsePlugin({
|
||||||
|
statuses: [0, 200],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
// Custom plugin: only update cache when content actually changed
|
||||||
|
// or when the cached entry is suspiciously small
|
||||||
|
cacheWillUpdate: async ({ request, response, event }) => {
|
||||||
|
if (!response || response.status !== 200) return null;
|
||||||
|
|
||||||
const shouldValidate = VALIDATE_ON_MODIFY_PATHS.some(path =>
|
const cache = await caches.open(CACHE_NAME);
|
||||||
url.pathname === path || url.pathname.startsWith(path + '/')
|
const cached = await cache.match(request);
|
||||||
|
|
||||||
|
if (!cached) return response;
|
||||||
|
|
||||||
|
const contentLength = parseInt(
|
||||||
|
cached.headers.get('Content-Length') || '0',
|
||||||
|
10
|
||||||
|
);
|
||||||
|
const isTooSmall = contentLength > 0 && contentLength < MIN_VALID_SIZE;
|
||||||
|
|
||||||
|
if (isTooSmall) {
|
||||||
|
console.warn(
|
||||||
|
`Cached file ${request.url} was too small (${contentLength} bytes). Forcing update.`
|
||||||
);
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
let isCachedTooSmall = false;
|
const [cachedModified, netModified] = await Promise.all([
|
||||||
if (cachedResponse) {
|
cached.headers.get('Last-Modified'),
|
||||||
const contentLength = parseInt(cachedResponse.headers.get('Content-Length') || '0', 10);
|
response.headers.get('Last-Modified'),
|
||||||
isCachedTooSmall = contentLength > 0 && contentLength < MIN_VALID_SIZE;
|
]);
|
||||||
}
|
const [cachedLen, netLen] = await Promise.all([
|
||||||
|
cached.headers.get('Content-Length'),
|
||||||
|
response.headers.get('Content-Length'),
|
||||||
|
]);
|
||||||
|
|
||||||
if (cachedResponse && !shouldValidate && !isCachedTooSmall) {
|
if (cachedModified === netModified && cachedLen === netLen) {
|
||||||
return cachedResponse;
|
console.log(
|
||||||
}
|
`Content unchanged for ${new URL(request.url).pathname}, keeping cached version`
|
||||||
|
);
|
||||||
|
return null; // do not update cache
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
console.log(`Caching/updating: ${new URL(request.url).pathname}`);
|
||||||
const networkResponse = await fetch(e.request);
|
return response;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (networkResponse && networkResponse.status === 200) {
|
// 3. Everything else → CacheFirst (with size safeguard)
|
||||||
const responseClone = networkResponse.clone();
|
workbox.routing.registerRoute(
|
||||||
|
({ request }) => request.method === 'GET',
|
||||||
|
new workbox.strategies.CacheFirst({
|
||||||
|
cacheName: CACHE_NAME,
|
||||||
|
plugins: [
|
||||||
|
new workbox.cacheableResponse.CacheableResponsePlugin({
|
||||||
|
statuses: [0, 200],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
// Force update when cached response is too small
|
||||||
|
cachedResponseWillBeUsed: async ({
|
||||||
|
cacheName,
|
||||||
|
request,
|
||||||
|
cachedResponse,
|
||||||
|
}) => {
|
||||||
|
if (!cachedResponse) return null;
|
||||||
|
|
||||||
let shouldUpdateCache = true;
|
const contentLength = parseInt(
|
||||||
|
cachedResponse.headers.get('Content-Length') || '0',
|
||||||
|
10
|
||||||
|
);
|
||||||
|
if (contentLength > 0 && contentLength < MIN_VALID_SIZE) {
|
||||||
|
console.warn(
|
||||||
|
`Cached file ${request.url} was too small (${contentLength} bytes). Ignoring cache.`
|
||||||
|
);
|
||||||
|
return null; // force network
|
||||||
|
}
|
||||||
|
return cachedResponse;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldValidate && cachedResponse) {
|
// ——— Offline fallback for navigation requests ———
|
||||||
const [cachedModified, netModified] = await Promise.all([
|
workbox.routing.setCatchHandler(async ({ event }) => {
|
||||||
cachedResponse.headers.get('Last-Modified'),
|
if (event.request.mode === 'navigate') {
|
||||||
networkResponse.headers.get('Last-Modified')
|
const offline = await caches.match('/offline.html');
|
||||||
]);
|
if (offline) return offline;
|
||||||
|
|
||||||
const [cachedLen, netLen] = await Promise.all([
|
const root = await caches.match('/');
|
||||||
cachedResponse.headers.get('Content-Length'),
|
if (root) return root;
|
||||||
networkResponse.headers.get('Content-Length')
|
}
|
||||||
]);
|
|
||||||
|
|
||||||
if (cachedModified === netModified && cachedLen === netLen) {
|
// For non-navigation requests just return whatever we have (or fail)
|
||||||
shouldUpdateCache = false;
|
return Response.error();
|
||||||
console.log(`Content unchanged for ${url.pathname}, keeping cached version`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCachedTooSmall) {
|
|
||||||
console.warn(`Cached file ${url.pathname} was too small (${cachedResponse.headers.get('Content-Length')} bytes). Forcing update.`);
|
|
||||||
shouldUpdateCache = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldUpdateCache) {
|
|
||||||
console.log(`Caching/updating: ${url.pathname}`);
|
|
||||||
await cache.put(e.request, responseClone);
|
|
||||||
}
|
|
||||||
|
|
||||||
return networkResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cachedResponse) return cachedResponse;
|
|
||||||
|
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Fetch failed:', err);
|
|
||||||
|
|
||||||
if (e.request.mode === 'navigate') {
|
|
||||||
const offlinePage = await caches.match('/offline.html');
|
|
||||||
if (offlinePage) return offlinePage;
|
|
||||||
|
|
||||||
const rootPage = await caches.match('/');
|
|
||||||
if (rootPage) return rootPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cachedResponse) {
|
|
||||||
console.warn(`Returning cached response despite small size due to offline state: ${url.pathname}`);
|
|
||||||
return cachedResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
200
static/sw.js
Normal file
200
static/sw.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-sw.js');
|
||||||
|
|
||||||
|
workbox.setConfig({ debug: false });
|
||||||
|
|
||||||
|
const CACHE_NAME = 'app-cache-v2.1.4';
|
||||||
|
const MIN_VALID_SIZE = 500;
|
||||||
|
|
||||||
|
const ASSETS_TO_CACHE = [
|
||||||
|
'/pwa2',
|
||||||
|
'/icon-192x192.png',
|
||||||
|
'/icon-512x512.png',
|
||||||
|
'/offline.html',
|
||||||
|
'/static/Inter-Bold.woff2',
|
||||||
|
'/static/Inter-SemiBold.woff2',
|
||||||
|
'/static/Inter-Regular.woff2',
|
||||||
|
'/static/painter.svg',
|
||||||
|
'/static/event1.jpg',
|
||||||
|
'/static/event2.jpg',
|
||||||
|
'/static/event3.jpg',
|
||||||
|
'/static/event4.jpg',
|
||||||
|
'/static/event5.jpg',
|
||||||
|
'/static/event6.jpg',
|
||||||
|
'/static/event7.webp',
|
||||||
|
'/partner',
|
||||||
|
];
|
||||||
|
|
||||||
|
const NOCACHE_PATHS = [
|
||||||
|
'/api',
|
||||||
|
'/form',
|
||||||
|
];
|
||||||
|
|
||||||
|
const VALIDATE_ON_MODIFY_PATHS = [
|
||||||
|
'/',
|
||||||
|
'/pwa',
|
||||||
|
'/paintings',
|
||||||
|
'/profile',
|
||||||
|
'/competitions',
|
||||||
|
'/competition',
|
||||||
|
'/exhibitions',
|
||||||
|
'/exhibition',
|
||||||
|
'/login',
|
||||||
|
'/registration',
|
||||||
|
'/artists',
|
||||||
|
'/my-paints',
|
||||||
|
'/partner',
|
||||||
|
'/news',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ——— Lifecycle ———
|
||||||
|
self.skipWaiting();
|
||||||
|
workbox.core.clientsClaim();
|
||||||
|
|
||||||
|
// Clean old caches + notify clients
|
||||||
|
self.addEventListener('activate', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
(async () => {
|
||||||
|
const keys = await caches.keys();
|
||||||
|
await Promise.all(
|
||||||
|
keys
|
||||||
|
.filter((key) => key !== CACHE_NAME)
|
||||||
|
.map((key) => {
|
||||||
|
console.log(`Deleting old cache: ${key}`);
|
||||||
|
return caches.delete(key);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const clients = await self.clients.matchAll();
|
||||||
|
clients.forEach((client) => client.postMessage({ type: 'SW_UPDATED' }));
|
||||||
|
console.log('Service Worker activated.');
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ——— Precaching ———
|
||||||
|
workbox.precaching.precacheAndRoute(
|
||||||
|
ASSETS_TO_CACHE.map((url) => ({ url, revision: null })),
|
||||||
|
{
|
||||||
|
cacheName: CACHE_NAME,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ——— Runtime routes ———
|
||||||
|
|
||||||
|
// 1. Never cache these paths (NetworkOnly)
|
||||||
|
workbox.routing.registerRoute(
|
||||||
|
({ url }) =>
|
||||||
|
NOCACHE_PATHS.some(
|
||||||
|
(path) => url.pathname === path || url.pathname.startsWith(path + '/')
|
||||||
|
),
|
||||||
|
new workbox.strategies.NetworkOnly()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Paths that should revalidate on Last-Modified / Content-Length
|
||||||
|
workbox.routing.registerRoute(
|
||||||
|
({ url }) =>
|
||||||
|
VALIDATE_ON_MODIFY_PATHS.some(
|
||||||
|
(path) => url.pathname === path || url.pathname.startsWith(path + '/')
|
||||||
|
),
|
||||||
|
new workbox.strategies.NetworkFirst({
|
||||||
|
cacheName: CACHE_NAME,
|
||||||
|
plugins: [
|
||||||
|
new workbox.cacheableResponse.CacheableResponsePlugin({
|
||||||
|
statuses: [0, 200],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
// Custom plugin: only update cache when content actually changed
|
||||||
|
// or when the cached entry is suspiciously small
|
||||||
|
cacheWillUpdate: async ({ request, response, event }) => {
|
||||||
|
if (!response || response.status !== 200) return null;
|
||||||
|
|
||||||
|
const cache = await caches.open(CACHE_NAME);
|
||||||
|
const cached = await cache.match(request);
|
||||||
|
|
||||||
|
if (!cached) return response;
|
||||||
|
|
||||||
|
const contentLength = parseInt(
|
||||||
|
cached.headers.get('Content-Length') || '0',
|
||||||
|
10
|
||||||
|
);
|
||||||
|
const isTooSmall = contentLength > 0 && contentLength < MIN_VALID_SIZE;
|
||||||
|
|
||||||
|
if (isTooSmall) {
|
||||||
|
console.warn(
|
||||||
|
`Cached file ${request.url} was too small (${contentLength} bytes). Forcing update.`
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [cachedModified, netModified] = await Promise.all([
|
||||||
|
cached.headers.get('Last-Modified'),
|
||||||
|
response.headers.get('Last-Modified'),
|
||||||
|
]);
|
||||||
|
const [cachedLen, netLen] = await Promise.all([
|
||||||
|
cached.headers.get('Content-Length'),
|
||||||
|
response.headers.get('Content-Length'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (cachedModified === netModified && cachedLen === netLen) {
|
||||||
|
console.log(
|
||||||
|
`Content unchanged for ${new URL(request.url).pathname}, keeping cached version`
|
||||||
|
);
|
||||||
|
return null; // do not update cache
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Caching/updating: ${new URL(request.url).pathname}`);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Everything else → CacheFirst (with size safeguard)
|
||||||
|
workbox.routing.registerRoute(
|
||||||
|
({ request }) => request.method === 'GET',
|
||||||
|
new workbox.strategies.CacheFirst({
|
||||||
|
cacheName: CACHE_NAME,
|
||||||
|
plugins: [
|
||||||
|
new workbox.cacheableResponse.CacheableResponsePlugin({
|
||||||
|
statuses: [0, 200],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
// Force update when cached response is too small
|
||||||
|
cachedResponseWillBeUsed: async ({
|
||||||
|
cacheName,
|
||||||
|
request,
|
||||||
|
cachedResponse,
|
||||||
|
}) => {
|
||||||
|
if (!cachedResponse) return null;
|
||||||
|
|
||||||
|
const contentLength = parseInt(
|
||||||
|
cachedResponse.headers.get('Content-Length') || '0',
|
||||||
|
10
|
||||||
|
);
|
||||||
|
if (contentLength > 0 && contentLength < MIN_VALID_SIZE) {
|
||||||
|
console.warn(
|
||||||
|
`Cached file ${request.url} was too small (${contentLength} bytes). Ignoring cache.`
|
||||||
|
);
|
||||||
|
return null; // force network
|
||||||
|
}
|
||||||
|
return cachedResponse;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// ——— Offline fallback for navigation requests ———
|
||||||
|
workbox.routing.setCatchHandler(async ({ event }) => {
|
||||||
|
if (event.request.mode === 'navigate') {
|
||||||
|
const offline = await caches.match('/offline.html');
|
||||||
|
if (offline) return offline;
|
||||||
|
|
||||||
|
const root = await caches.match('/');
|
||||||
|
if (root) return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-navigation requests just return whatever we have (or fail)
|
||||||
|
return Response.error();
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user