feat: publish unlisted GTIN tool
This commit is contained in:
173
site/gtin/sw.js
Normal file
173
site/gtin/sw.js
Normal file
@@ -0,0 +1,173 @@
|
||||
/* Service Worker des GTIN-Generators.
|
||||
*
|
||||
* Nach dem ersten Laden liegen HTML, CSS, JavaScript, Manifest und Icons im
|
||||
* Cache. Danach funktioniert die Anwendung ohne WLAN, ohne Mobilfunk und ohne
|
||||
* erreichbaren Server.
|
||||
*
|
||||
* CACHE_VERSION wird beim Build aus dem Inhalt der Anwendungsdateien ersetzt.
|
||||
* Dadurch erkennt der Browser ein neues Release und raeumt den alten Cache auf.
|
||||
*/
|
||||
|
||||
const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self));
|
||||
|
||||
const CACHE_VERSION = '15d23ab0263e';
|
||||
const CACHE_PREFIX = 'desfoto-gtin-generator-';
|
||||
const CACHE_NAME = `${CACHE_PREFIX}${CACHE_VERSION}`;
|
||||
|
||||
// PRECACHE
|
||||
const PRECACHE = [
|
||||
'./',
|
||||
'./index.html',
|
||||
'./css/app.css',
|
||||
'./js/app.js',
|
||||
'./js/gtin.js',
|
||||
'./js/barcode.js',
|
||||
'./js/liste.js',
|
||||
'./manifest.webmanifest',
|
||||
'./icons/favicon.svg',
|
||||
'./icons/icon-192.png',
|
||||
'./icons/icon-512.png',
|
||||
'./icons/icon-maskable-512.png',
|
||||
];
|
||||
// END PRECACHE
|
||||
|
||||
/** @param {string} pfad */
|
||||
function scopeUrl(pfad) {
|
||||
return new URL(pfad, sw.registration.scope).toString();
|
||||
}
|
||||
|
||||
const INDEX_URL = scopeUrl('./index.html');
|
||||
|
||||
/** Pfad des Geltungsbereichs, z. B. "/gtin/". */
|
||||
const SCOPE_PFAD = new URL(sw.registration.scope).pathname;
|
||||
|
||||
/**
|
||||
* Kennung der ausgelieferten Anwendungsquelle. Nur eine Antwort, die diese
|
||||
* Kennung enthaelt, ist die echte index.html dieses Projekts.
|
||||
*/
|
||||
const SHELL_KENNUNG = 'id="artikelnummer"';
|
||||
|
||||
/**
|
||||
* Entscheidet, ob eine Antwort die vorgehaltene Anwendungshuelle ersetzen darf.
|
||||
*
|
||||
* Nur eine erfolgreiche HTML-Antwort der eigenen Anwendung ist zulaessig. Eine
|
||||
* Anmeldeseite der Steuerungsebene, eine Magic-Link-Seite oder eine Fehlerseite
|
||||
* (z. B. "App deployment in progress") wuerde die Anwendung sonst offline
|
||||
* unbrauchbar machen, weil sie die echte Seite aus dem Cache verdraengt.
|
||||
*
|
||||
* @param {Response} antwort
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async function istAnwendungshuelle(antwort) {
|
||||
if (!antwort.ok || antwort.type !== 'basic') return false;
|
||||
const inhaltstyp = antwort.headers.get('content-type') ?? '';
|
||||
if (!inhaltstyp.includes('text/html')) return false;
|
||||
const endgueltig = new URL(antwort.url);
|
||||
if (endgueltig.origin !== sw.location.origin) return false;
|
||||
if (!endgueltig.pathname.startsWith(SCOPE_PFAD)) return false;
|
||||
try {
|
||||
return (await antwort.text()).includes(SHELL_KENNUNG);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sw.addEventListener('install', (ereignis) => {
|
||||
ereignis.waitUntil(
|
||||
caches
|
||||
.open(CACHE_NAME)
|
||||
.then((cache) =>
|
||||
cache.addAll(
|
||||
PRECACHE.map((pfad) => new Request(scopeUrl(pfad), { cache: 'reload' })),
|
||||
),
|
||||
)
|
||||
.then(() => sw.skipWaiting()),
|
||||
);
|
||||
});
|
||||
|
||||
sw.addEventListener('activate', (ereignis) => {
|
||||
ereignis.waitUntil(
|
||||
caches
|
||||
.keys()
|
||||
.then((namen) =>
|
||||
Promise.all(
|
||||
namen
|
||||
.filter((name) => name.startsWith(CACHE_PREFIX) && name !== CACHE_NAME)
|
||||
.map((name) => caches.delete(name)),
|
||||
),
|
||||
)
|
||||
.then(() => sw.clients.claim()),
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Bedient einen Navigationsaufruf.
|
||||
*
|
||||
* Bewusst aus dem Cache: Nur so stammen HTML und Assets immer aus derselben
|
||||
* Cache-Generation. Der neue Service Worker uebernimmt dank
|
||||
* skipWaiting()/clients.claim() sofort; ein neues Release ist damit beim
|
||||
* naechsten Laden konsistent aktiv (kein "neues HTML, altes JavaScript").
|
||||
*
|
||||
* @param {FetchEvent} ereignis
|
||||
* @param {Request} anfrage
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
function bedieneNavigation(ereignis, anfrage) {
|
||||
return caches.match(INDEX_URL).then((treffer) => {
|
||||
if (treffer) return treffer;
|
||||
return fetch(anfrage)
|
||||
.then((antwort) => {
|
||||
const pruefung = antwort.clone();
|
||||
const kopie = antwort.clone();
|
||||
// waitUntil haelt die Cache-Schreiboperation am Leben. Ein blosses
|
||||
// "fire and forget" innerhalb von respondWith kann abgebrochen werden.
|
||||
ereignis.waitUntil(
|
||||
istAnwendungshuelle(pruefung)
|
||||
.then((istHuelle) =>
|
||||
istHuelle
|
||||
? caches.open(CACHE_NAME).then((cache) => cache.put(INDEX_URL, kopie))
|
||||
: undefined,
|
||||
)
|
||||
.catch(() => undefined),
|
||||
);
|
||||
return antwort;
|
||||
})
|
||||
.catch(() => caches.match(INDEX_URL).then((treffer) => treffer ?? Response.error()));
|
||||
});
|
||||
}
|
||||
|
||||
sw.addEventListener('fetch', (ereignis) => {
|
||||
const anfrage = ereignis.request;
|
||||
if (anfrage.method !== 'GET') return;
|
||||
|
||||
const ziel = new URL(anfrage.url);
|
||||
if (ziel.origin !== sw.location.origin) return;
|
||||
if (!ziel.pathname.startsWith(SCOPE_PFAD)) return;
|
||||
if (ziel.pathname.endsWith('/health')) return;
|
||||
|
||||
if (anfrage.mode === 'navigate') {
|
||||
const appStart = new URL('./', sw.registration.scope).pathname;
|
||||
const appIndex = new URL('./index.html', sw.registration.scope).pathname;
|
||||
if (ziel.pathname !== appStart && ziel.pathname !== appIndex) return;
|
||||
ereignis.respondWith(bedieneNavigation(ereignis, anfrage));
|
||||
return;
|
||||
}
|
||||
|
||||
ereignis.respondWith(
|
||||
caches.match(anfrage).then((treffer) => {
|
||||
if (treffer) return treffer;
|
||||
return fetch(anfrage).then((antwort) => {
|
||||
if (antwort.ok && antwort.type === 'basic') {
|
||||
const kopie = antwort.clone();
|
||||
ereignis.waitUntil(
|
||||
caches
|
||||
.open(CACHE_NAME)
|
||||
.then((cache) => cache.put(anfrage, kopie))
|
||||
.catch(() => undefined),
|
||||
);
|
||||
}
|
||||
return antwort;
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user