loko/streetup/controls/static/controls/service-worker.js
2026-07-22 14:48:40 +02:00

125 lines
4.9 KiB
JavaScript

// Service Worker — Contrôles StreetUp
// Stratégie :
// • Pages de navigation /controls/ et /mobile/controls/ → Network-first, fallback cache, puis page offline
// • Assets statiques /static/ → Cache-first, mise en cache au premier accès
const CACHE_VERSION = 'v6';
const PAGES_CACHE = `streetup-controls-pages-${CACHE_VERSION}`;
const STATIC_CACHE = `streetup-controls-static-${CACHE_VERSION}`;
// URL de la page de fallback offline (pré-cachée à l'installation)
const OFFLINE_URL = '/controls/offline/';
// ─── Installation ────────────────────────────────────────────────────────────
self.addEventListener('install', event => {
console.log(`[Controls SW] install ${CACHE_VERSION}`);
self.skipWaiting();
event.waitUntil(
caches.open(PAGES_CACHE).then(cache => cache.addAll([OFFLINE_URL]))
);
});
// ─── Activation — nettoyage des anciens caches ───────────────────────────────
self.addEventListener('activate', event => {
console.log(`[Controls SW] activate ${CACHE_VERSION}`);
const knownCaches = [PAGES_CACHE, STATIC_CACHE];
event.waitUntil(
caches.keys()
.then(names => Promise.all(
names
.filter(name => !knownCaches.includes(name))
.map(name => caches.delete(name))
))
.then(() => self.clients.claim())
);
});
// ─── Interception des requêtes ───────────────────────────────────────────────
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// Uniquement les GET sur la même origine
if (request.method !== 'GET' || url.origin !== self.location.origin) return;
// Assets statiques : cache-first
if (url.pathname.startsWith('/static/')) {
event.respondWith(cacheFirst(request, STATIC_CACHE));
return;
}
// Catalogue de traductions i18n : network-first avec fallback cache
if (url.pathname === '/jsi18n/') {
event.respondWith(networkFirstCache(request, PAGES_CACHE));
return;
}
// Pages de navigation des contrôles (desktop + mobile) : network-first avec fallback
const isControlsNav = request.mode === 'navigate' && (
url.pathname.startsWith('/controls/') ||
url.pathname.startsWith('/mobile/controls/')
);
if (isControlsNav) {
event.respondWith(networkFirstControls(request));
return;
}
});
// ─── Stratégie Cache-first (assets statiques) ────────────────────────────────
async function cacheFirst(request, cacheName) {
const cached = await caches.match(request);
if (cached) return cached;
try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(cacheName);
cache.put(request, response.clone());
}
return response;
} catch {
return new Response('', { status: 503 });
}
}
// ─── Stratégie Network-first générique (sans fallback page) ──────────────────
// Utilisée pour les ressources dynamiques (ex. /jsi18n/) : réseau d'abord,
// sinon cache, sinon réponse vide.
async function networkFirstCache(request, cacheName) {
const cache = await caches.open(cacheName);
try {
const response = await fetch(request);
if (response.ok) {
cache.put(request, response.clone());
}
return response;
} catch {
const cached = await cache.match(request);
if (cached) return cached;
return new Response('', { status: 503 });
}
}
// ─── Stratégie Network-first (pages controls) ────────────────────────────────
// 1. Réseau → mise en cache si succès
// 2. Réseau KO → cache existant
// 3. Pas en cache → page offline pré-cachée
async function networkFirstControls(request) {
const cache = await caches.open(PAGES_CACHE);
try {
const response = await fetch(request);
if (response.ok) {
cache.put(request, response.clone());
}
return response;
} catch {
const cached = await cache.match(request);
if (cached) return cached;
// Fallback page offline
const offlinePage = await caches.match(OFFLINE_URL);
return offlinePage || new Response(
'<!DOCTYPE html><html><head><meta charset="utf-8"></head><body><h2>Hors ligne</h2><p>Connexion indisponible.</p></body></html>',
{ status: 503, headers: { 'Content-Type': 'text/html; charset=utf-8' } }
);
}
}