Remove manual SW code, Workbox handles everything via pwa:create:sw

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-20 19:48:31 +01:00
parent 350a6be2e5
commit acb7e15b43

View File

@@ -1,47 +1,682 @@
const CACHE_VERSION = 'v1-' + Date.now()
const CACHE_NAME = 'e-ticket-' + CACHE_VERSION
const PRECACHE_URLS = [
'/',
'/evenements',
'/organisateurs',
'/favicon.png',
'/logo.png',
]
/**************************************************** WORKBOX IMPORT ****************************************************/
// The configuration is set to use Workbox
// The following code will import Workbox from CDN or public URL
// Import from public URL
importScripts('/workbox/workbox-sw.js');
importScripts('/idb/umd.js');
workbox.setConfig({modulePathPrefix: '/workbox'});
/**************************************************** END WORKBOX IMPORT ****************************************************/
function registerCacheFirst(routeMatchFn, cacheName, plugins = []) {
const strategy = new workbox.strategies.CacheFirst({ cacheName, plugins });
workbox.routing.registerRoute(routeMatchFn, strategy);
return strategy;
}
function precacheResources(strategy, resourceList, event) {
if (!(event instanceof ExtendableEvent)) {
throw new Error("precacheResources needs a valid ExtendableEvent");
}
return Promise.all(resourceList.map(path =>
strategy.handleAll({
event,
request: new Request(path),
})[1]
));
}
function createBackgroundSyncPlugin(queueName, maxRetentionTime = 2880, forceSyncFallback = false) {
return new workbox.backgroundSync.BackgroundSyncPlugin(queueName, {
maxRetentionTime,
forceSyncFallback
});
}
function createBackgroundSyncPluginWithBroadcast(queueName, channelName, maxRetentionTime = 2880, forceSyncFallback = false) {
const queue = new workbox.backgroundSync.Queue(queueName, {
maxRetentionTime,
forceSyncFallback,
});
const bc = new BroadcastChannel(channelName);
const replayQueueWithProgress = async () => {
let entry;
let successCount = 0;
let failureCount = 0;
const total = (await queue.getAll()).length;
while ((entry = await queue.shiftRequest())) {
try {
await fetch(entry.request.clone());
successCount++;
} catch (error) {
failureCount++;
await queue.unshiftRequest(entry);
throw error;
} finally {
const remaining = (await queue.getAll()).length;
bc.postMessage({ name: queueName, replaying: true, remaining });
}
}
const remaining = (await queue.getAll()).length;
bc.postMessage({
name: queueName,
replayed: true,
remaining,
successCount,
failureCount,
});
};
bc.onmessage = async (event) => {
if (event.data?.type === 'status-request') {
const entries = await queue.getAll();
bc.postMessage({ name: queueName, remaining: entries.length });
}
if (event.data?.type === 'replay-request') {
try {
await replayQueueWithProgress();
} catch (error) {
const entries = await queue.getAll();
bc.postMessage({
name: queueName,
replayed: false,
remaining: entries.length,
error: error.message,
});
}
}
};
return {
fetchDidFail: async ({ request }) => {
await queue.pushRequest({ request });
const entries = await queue.getAll();
bc.postMessage({ name: queueName, remaining: entries.length });
},
onSync: async () => {
await replayQueueWithProgress();
},
};
}
const messageTasks = [];
function registerMessageTask(callback) {
messageTasks.push(callback);
}
self.addEventListener('message', (event) => {
event.waitUntil(
messageTasks.reduce(
(chain, task) => chain.then(() => task(event)),
Promise.resolve()
)
);
});
const installTasks = [];
function registerInstallTask(callback, priority = 100) {
installTasks.push({
callback: (event) => {
const result = callback(event);
if (!result?.then) console.warn("Install task did not return a Promise");
return result;
},
priority,
});
}
self.addEventListener('install', (event) => {
event.waitUntil(
installTasks
.sort((a, b) => a.priority - b.priority)
.reduce(
(chain, task) => chain.then(() => task.callback(event)),
Promise.resolve()
)
);
});
function statusGuard(min, max) {
return {
fetchDidSucceed: ({ response }) => {
if (response.status >= min && response.status <= max) {
throw new Error(`Server error: ${response.status}`);
}
return response;
}
};
}
registerMessageTask(async (event) => {
if (event.data?.type === 'SKIP_WAITING') {
await self.skipWaiting();
await self.clients.claim();
}
});
const usedCacheNames = new Set();
function registerCacheName(name) {
usedCacheNames.add(name);
return name;
}
async function openBackgroundFetchDatabase() {
return await self.idb.openDB('bgfetch-completed', 1, {
upgrade(db) {
if (!db.objectStoreNames.contains('files')) {
db.createObjectStore('files', { keyPath: 'id' });
}
if (!db.objectStoreNames.contains('chunks')) {
const store = db.createObjectStore('chunks', { keyPath: ['id', 'index'] });
store.createIndex('by-id', 'id');
} else {
const store = db.transaction.objectStore('chunks');
if (!Array.from(store.indexNames).includes('by-id')) {
store.createIndex('by-id', 'id');
}
}
}
});
}
const bgFetchMetadata = new Map();
const bgFetchChannel = new BroadcastChannel('bg-fetch');
bgFetchChannel.onmessage = async (event) => {
const { type, id, meta } = event.data || {};
switch (type) {
case 'register-meta':
bgFetchMetadata.set(id, meta);
break;
case 'get-meta':
const metadata = bgFetchMetadata.get(id) || null;
bgFetchChannel.postMessage({ type: 'meta-response', id, metadata });
break;
case 'clear-meta':
bgFetchMetadata.delete(id);
break;
case 'list-stored-files':
{
const db = await openBackgroundFetchDatabase();
const files = await db.getAll('files');
bgFetchChannel.postMessage({ type: 'stored-files', files });
break;
}
case 'delete-stored-file':
{
const name = event.data.name;
const db = await openBackgroundFetchDatabase();
const allFiles = await db.getAll('files');
const target = allFiles.find(f => f.name === name);
if (target) {
await db.delete('files', target.id);
let index = 0;
while (await db.get('chunks', [target.id, index])) {
await db.delete('chunks', [target.id, index++]);
}
}
break;
}
}
};
const backgroundFetchTasks = {
click: [],
success: [],
fail: [],
};
function registerBackgroundFetchTask(type, callback, priority = 100) {
if (!backgroundFetchTasks[type]) {
throw new Error(`Unknown background fetch event type: ${type}`);
}
backgroundFetchTasks[type].push({
callback: (event) => {
const result = callback(event);
if (!result?.then) {
console.warn(`[${type}] task did not return a Promise`);
}
return result;
},
priority,
});
}
function runBackgroundFetchTasks(type, event) {
const tasks = backgroundFetchTasks[type] ?? [];
return tasks
.sort((a, b) => a.priority - b.priority)
.reduce(
(chain, task) => chain.then(() => task.callback(event)),
Promise.resolve()
);
}
self.addEventListener('backgroundfetchclick', (event) => {
event.waitUntil(runBackgroundFetchTasks('click', event));
});
self.addEventListener('backgroundfetchsuccess', (event) => {
event.waitUntil(runBackgroundFetchTasks('success', event));
});
self.addEventListener('backgroundfetchfail', (event) => {
event.waitUntil(runBackgroundFetchTasks('fail', event));
});
const pushTasks = [];
function registerPushTask(callback) {
pushTasks.push(callback);
}
self.addEventListener('push', (event) => {
if (!(self.Notification && self.Notification.permission === 'granted')) {
return;
}
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(PRECACHE_URLS))
.then(() => self.skipWaiting())
pushTasks.reduce(
(chain, task) => chain.then(() => task(event)),
Promise.resolve()
)
);
});
const notificationActionHandlers = new Map();
function registerNotificationAction(actionName, handler) {
notificationActionHandlers.set(actionName, handler);
}
self.addEventListener('notificationclick', event => {
event.notification.close();
const action = event.action || "";
const promises = [];
const specificHandler = notificationActionHandlers.get(action);
if (typeof specificHandler === 'function') {
promises.push(Promise.resolve(specificHandler(event)));
}
const wildcardHandler = notificationActionHandlers.get('*');
if (typeof wildcardHandler === 'function') {
promises.push(Promise.resolve(wildcardHandler(event)));
}
if (promises.length > 0) {
event.waitUntil(Promise.all(promises));
}
});
const structuredPushNotificationSupport = (event) => {
const {data} = event;
const sendNotification = response => {
const {title, options} = JSON.parse(response);
return self.registration.showNotification(title, options);
};
if (data) {
const message = data.text();
event.waitUntil(sendNotification(message));
}
}
function simplePushNotificationSupport(event) {
const { data } = event;
if (!data) return;
const message = data.text();
const sendNotification = (text) => {
return self.registration.showNotification('Notification', {
body: text
});
};
event.waitUntil(sendNotification(message));
}
const periodicSyncTasks = new Map();
function registerPeriodicSyncTask(tag, callback, priority = 100) {
if (!periodicSyncTasks.has(tag)) {
periodicSyncTasks.set(tag, []);
}
periodicSyncTasks.get(tag).push({
priority,
callback: (event) => {
const result = callback(event);
if (!result?.then) console.warn(`[${tag}] periodic sync task did not return a Promise`);
return result;
}
});
}
async function runPeriodicSyncTasks(tag, event) {
const tasks = periodicSyncTasks.get(tag) ?? [];
return tasks
.sort((a, b) => a.priority - b.priority)
.reduce(
(chain, task) => chain.then(() => task.callback(event)),
Promise.resolve()
);
}
self.addEventListener('periodicsync', event => {
event.waitUntil(runPeriodicSyncTasks(event.tag, event));
});
const periodicChannel = new BroadcastChannel('periodic-sync');
function notifyPeriodicSyncClients(tag, payload = {}) {
periodicChannel.postMessage({
type: 'periodic-sync-update',
tag,
timestamp: Date.now(),
...payload
});
}
const cacheInstances = new Map();
async function openCache(name) {
if (!cacheInstances.has(name)) {
const cache = await caches.open(name);
cacheInstances.set(name, cache);
}
return cacheInstances.get(name);
}
/**************************************************** NAVIGATION PRELOAD ****************************************************/
// Navigation Preload is enabled
// This speeds up navigation requests by making the network request in parallel with service worker boot-up
// See: https://developer.chrome.com/docs/workbox/modules/workbox-navigation-preload/
workbox.navigationPreload.enable();
/**************************************************** END NAVIGATION PRELOAD ****************************************************/
/**************************************************** CACHE STRATEGY ****************************************************/
// Strategy: CacheFirst
/* Match: ({url}) => url.pathname.startsWith('/assets') */
// Cache Name: assets
// Enabled: 1
// Needs Workbox: 1
// Method:
// 1. Creation of the Workbox Cache Strategy object
// 2. Register the route with the Workbox Router
// 3. Add the assets to the cache when the service worker is installed
const cache_0_0 = new workbox.strategies.CacheFirst({
cacheName: registerCacheName('assets'),plugins: [new workbox.expiration.ExpirationPlugin({
"maxEntries": 84,
"maxAgeSeconds": 31536000
})]
});
workbox.routing.registerRoute(({url}) => url.pathname.startsWith('/assets'),cache_0_0);
registerInstallTask((event) => precacheResources(cache_0_0, [
"/assets/@spomky-labs/pwa-bundle/badge_controller-fxsMWke.js",
"/assets/@spomky-labs/pwa-bundle/connection-status_controller-a6qV4RW.js",
"/assets/@spomky-labs/pwa-bundle/abstract_controller-lHossdH.js",
"/assets/@spomky-labs/pwa-bundle/capture_controller-yP__Kii.js",
"/assets/@spomky-labs/pwa-bundle/install_controller-TRkeV12.js",
"/assets/@spomky-labs/pwa-bundle/prefetch-on-demand_controller-EGnyR3S.js",
"/assets/@spomky-labs/pwa-bundle/fullscreen_controller-cDz4gEY.js",
"/assets/@spomky-labs/pwa-bundle/barcode-detection_controller-2wM_rT7.js",
"/assets/@spomky-labs/pwa-bundle/web-share_controller-WHj4CRa.js",
"/assets/@spomky-labs/pwa-bundle/file-handling_controller-F0Y1cZK.js",
"/assets/@spomky-labs/pwa-bundle/device-orientation_controller-IHb4jim.js",
"/assets/@spomky-labs/pwa-bundle/touch_controller-mAGtlT0.js",
"/assets/@spomky-labs/pwa-bundle/battery_controller-1zvUlM8.js",
"/assets/@spomky-labs/pwa-bundle/service-worker_controller-rj3_R_-.js",
"/assets/@spomky-labs/pwa-bundle/network-information_controller-a36uKfa.js",
"/assets/@spomky-labs/pwa-bundle/backgroundsync-queue_controller-GzsTYhv.js",
"/assets/@spomky-labs/pwa-bundle/device-motion_controller-Vn1KsVn.js",
"/assets/@spomky-labs/pwa-bundle/vibration_controller-WWNgRJt.js",
"/assets/@spomky-labs/pwa-bundle/backgroundsync-form_controller-gK5yd8x.js",
"/assets/@spomky-labs/pwa-bundle/web-push_controller-czf9Gth.js",
"/assets/@spomky-labs/pwa-bundle/geolocation_controller-eLT6hhX.js",
"/assets/@spomky-labs/pwa-bundle/jwt_signer-rMDxy1b.js",
"/assets/@spomky-labs/pwa-bundle/speech-synthesis_controller-P10zBDz.js",
"/assets/@spomky-labs/pwa-bundle/presentation_controller-Ja4LWIU.js",
"/assets/@spomky-labs/pwa-bundle/receiver_controller-auggO9d.js",
"/assets/@spomky-labs/pwa-bundle/background-fetch_controller-RRspbNT.js",
"/assets/@spomky-labs/pwa-bundle/wake-lock_controller-AZuUNNo.js",
"/assets/@spomky-labs/pwa-bundle/picture-in-picture_controller-BzBogPR.js",
"/assets/@spomky-labs/pwa-bundle/helpers-jYq9oR_.js",
"/assets/@spomky-labs/pwa-bundle/index-2g1hp95.js",
"/assets/@spomky-labs/pwa-bundle/contact_controller--bz1Lhb.js",
"/assets/modules/cookie-consent-zZYidFI.js",
"/assets/modules/editor-zH-esRJ.js",
"/assets/modules/tabs-Z12uekg.js",
"/assets/modules/mobile-menu-UYAlwem.js",
"/assets/modules/event-map-SaY902U.js",
"/assets/vendor/idb/idb.index-4Dldwsm.js",
"/assets/vendor/idb-keyval/idb-keyval.index-qD0OEME.js",
"/assets/vendor/@hotwired/stimulus/stimulus.index-S4zNcea.js",
"/assets/styles/app-cT2YKNc.css",
"/assets/admin-78uUCmt.js",
"/assets/app-ATNZmm5.js"
], event));
/**************************************************** END CACHE STRATEGY ****************************************************/
/**************************************************** CACHE STRATEGY ****************************************************/
// Strategy: CacheFirst
/* Match: ({request}) => request.destination === 'font' */
// Cache Name: fonts
// Enabled: 1
// Needs Workbox: 1
// Method: GET
// 1. Creation of the Workbox Cache Strategy object
// 2. Register the route with the Workbox Router
// 3. Add the assets to the cache when the service worker is installed
const cache_2_0 = new workbox.strategies.CacheFirst({
cacheName: registerCacheName('fonts'),plugins: [new workbox.cacheableResponse.CacheableResponsePlugin({
"statuses": [
0,
200
]
}), new workbox.expiration.ExpirationPlugin({
"maxEntries": 10,
"maxAgeSeconds": 2592000
})]
});
workbox.routing.registerRoute(({request}) => request.destination === 'font',cache_2_0,'GET');
/**************************************************** END CACHE STRATEGY ****************************************************/
/**************************************************** CACHE STRATEGY ****************************************************/
// Strategy: StaleWhileRevalidate
/* Match: ({url}) => url.origin === 'https://fonts.googleapis.com' */
// Cache Name: google-fonts-stylesheets
// Enabled: 1
// Needs Workbox: 1
// Method:
// 1. Creation of the Workbox Cache Strategy object
// 2. Register the route with the Workbox Router
// 3. Add the assets to the cache when the service worker is installed
const cache_3_0 = new workbox.strategies.StaleWhileRevalidate({
cacheName: registerCacheName('google-fonts-stylesheets'),plugins: []
});
workbox.routing.registerRoute(({url}) => url.origin === 'https://fonts.googleapis.com',cache_3_0);
/**************************************************** END CACHE STRATEGY ****************************************************/
/**************************************************** CACHE STRATEGY ****************************************************/
// Strategy: CacheFirst
/* Match: ({url}) => url.origin === 'https://fonts.gstatic.com' */
// Cache Name: google-fonts-webfonts
// Enabled: 1
// Needs Workbox: 1
// Method:
// 1. Creation of the Workbox Cache Strategy object
// 2. Register the route with the Workbox Router
// 3. Add the assets to the cache when the service worker is installed
const cache_3_1 = new workbox.strategies.CacheFirst({
cacheName: registerCacheName('google-fonts-webfonts'),plugins: [new workbox.cacheableResponse.CacheableResponsePlugin({
"statuses": [
0,
200
]
}), new workbox.expiration.ExpirationPlugin({
"maxEntries": 30,
"maxAgeSeconds": 31536000
})]
});
workbox.routing.registerRoute(({url}) => url.origin === 'https://fonts.gstatic.com',cache_3_1);
/**************************************************** END CACHE STRATEGY ****************************************************/
/**************************************************** CACHE STRATEGY ****************************************************/
// Strategy: CacheFirst
/* Match: ({request, url}) => (request.destination === 'image' && !url.pathname.startsWith('/assets')) */
// Cache Name: images
// Enabled: 1
// Needs Workbox: 1
// Method:
// 1. Creation of the Workbox Cache Strategy object
// 2. Register the route with the Workbox Router
// 3. Add the assets to the cache when the service worker is installed
const cache_4_0 = new workbox.strategies.CacheFirst({
cacheName: registerCacheName('images'),plugins: []
});
workbox.routing.registerRoute(({request, url}) => (request.destination === 'image' && !url.pathname.startsWith('/assets')),cache_4_0);
/**************************************************** END CACHE STRATEGY ****************************************************/
/**************************************************** CACHE STRATEGY ****************************************************/
// Strategy: StaleWhileRevalidate
/* Match: ({request}) => {
if (request.mode !== 'navigate') {
return false;
}
const acceptHeader = request.headers.get('Accept') || '';
if (acceptHeader.includes('text/vnd.turbo-stream.html')) {
return false;
}
if (request.headers.get('Turbo-Frame')) {
return false;
}
return true;
} */
// Cache Name: pages
// Enabled: 1
// Needs Workbox: 1
// Method:
// 1. Creation of the Workbox Cache Strategy object
// 2. Register the route with the Workbox Router
// 3. Add the assets to the cache when the service worker is installed
const cache_5_0 = new workbox.strategies.StaleWhileRevalidate({
cacheName: registerCacheName('pages'),plugins: [new workbox.cacheableResponse.CacheableResponsePlugin({
"statuses": [
0,
200
]
}), new workbox.broadcastUpdate.BroadcastUpdatePlugin({
"headersToCheck": [
"Content-Length",
"ETag",
"Last-Modified"
]
})]
});
workbox.routing.registerRoute(({request}) => {
if (request.mode !== 'navigate') {
return false;
}
const acceptHeader = request.headers.get('Accept') || '';
if (acceptHeader.includes('text/vnd.turbo-stream.html')) {
return false;
}
if (request.headers.get('Turbo-Frame')) {
return false;
}
return true;
},cache_5_0);
/**************************************************** END CACHE STRATEGY ****************************************************/
/**************************************************** CACHE CLEAR ****************************************************/
// The configuration is set to clear the cache on each install event
// The following code will remove all the caches
registerInstallTask(() =>
caches.keys().then(keys =>
Promise.all(
keys
.filter(k => usedCacheNames.has(k))
.map(k => caches.delete(k))
)
})
)
, 0);
/**************************************************** END CACHE CLEAR ****************************************************/
/**************************************************** SKIP WAITING ****************************************************/
// The configuration is set to skip waiting on each install event
registerInstallTask(() => self.skipWaiting(), 5);
self.addEventListener("activate", function (event) {
event.waitUntil(self.clients.claim());
});
/**************************************************** END SKIP WAITING ****************************************************/
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) =>
Promise.all(
cacheNames
.filter((name) => name.startsWith('e-ticket-') && name !== CACHE_NAME)
.map((name) => caches.delete(name))
)
).then(() => self.clients.claim())
)
})
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return
event.respondWith(
fetch(event.request)
.then((response) => {
if (response.ok) {
const clone = response.clone()
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone))
}
return response
})
.catch(() => caches.match(event.request))
)
})