This commit is contained in:
Vladislav 2026-07-31 06:07:12 -04:00
parent 8f785f7a7d
commit b1135bc9ed
6 changed files with 365 additions and 142 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -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
View 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();
});