fix: cache canonical GTIN page without redirects

This commit is contained in:
desfoto automation
2026-09-25 20:02:16 +02:00
parent 8be63309d3
commit de6de3c6c4
4 changed files with 35 additions and 15 deletions

View File

@@ -67,7 +67,9 @@ Werkzeug selbst verlinkt auf seine eigenständigen Rechtstexte unter `/gtin/impr
- **GTIN-Generator.** Der Generator unter `/gtin/` rechnet vollständig im Browser, - **GTIN-Generator.** Der Generator unter `/gtin/` rechnet vollständig im Browser,
ohne Konto, externe API oder Serverübertragung. Er speichert bis zu 20 zuletzt ohne Konto, externe API oder Serverübertragung. Er speichert bis zu 20 zuletzt
erzeugte Einträge lokal im Browser; der Offline-Appcache enthält nur die erzeugte Einträge lokal im Browser; der Offline-Appcache enthält nur die
Anwendungsdateien. Die eigenständige Datenschutzerklärung liegt unter Anwendungsdateien. Der Service Worker speichert die kanonische Startadresse
`/gtin/` ohne Weiterleitung; jede Änderung am Werkzeug erhält eine neue
Cache-Version. Die eigenständige Datenschutzerklärung liegt unter
`/gtin/datenschutz/`; die Rechtstexte sind nicht mit denen des Hauptauftritts verknüpft. `/gtin/datenschutz/`; die Rechtstexte sind nicht mit denen des Hauptauftritts verknüpft.
## Herkunft der Inhalte ## Herkunft der Inhalte

View File

@@ -14,6 +14,7 @@ from __future__ import annotations
import base64 import base64
import datetime as dt import datetime as dt
import hashlib
import io import io
import json import json
import shutil import shutil
@@ -285,6 +286,7 @@ def build_html(table: dict[str, dict]) -> list[tuple[str, str]]:
print(f" [html] {rel}") print(f" [html] {rel}")
shutil.rmtree(SITE / "gtin", ignore_errors=True) shutil.rmtree(SITE / "gtin", ignore_errors=True)
shutil.copytree(SRC / "gtin", SITE / "gtin") shutil.copytree(SRC / "gtin", SITE / "gtin")
version_gtin_service_worker()
print(" [html] gtin/ (unlisted browser tool and standalone legal pages)") print(" [html] gtin/ (unlisted browser tool and standalone legal pages)")
not_found = pages.not_found().replace("og-desfoto.jpg", "og-desfoto.jpg") not_found = pages.not_found().replace("og-desfoto.jpg", "og-desfoto.jpg")
(SITE / "404.html").write_text(not_found, encoding="utf-8") (SITE / "404.html").write_text(not_found, encoding="utf-8")
@@ -292,6 +294,26 @@ def build_html(table: dict[str, dict]) -> list[tuple[str, str]]:
return written return written
def version_gtin_service_worker() -> None:
"""Give each GTIN source release its own offline cache."""
digest = hashlib.sha256()
source_root = SRC / "gtin"
for path in sorted(source_root.rglob("*")):
if not path.is_file():
continue
digest.update(path.relative_to(source_root).as_posix().encode("utf-8"))
digest.update(b"\0")
digest.update(path.read_bytes())
digest.update(b"\0")
worker = SITE / "gtin" / "sw.js"
script = worker.read_text(encoding="utf-8")
marker = "__BUILD_VERSION__"
if script.count(marker) != 1:
raise RuntimeError("GTIN service worker cache version marker is missing or duplicated")
worker.write_text(script.replace(marker, digest.hexdigest()[:12]), encoding="utf-8")
def build_meta_files(routes: list[tuple[str, str]]) -> None: def build_meta_files(routes: list[tuple[str, str]]) -> None:
today = dt.date.today().isoformat() today = dt.date.today().isoformat()
urls = [] urls = []

View File

@@ -10,14 +10,13 @@
const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self)); const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self));
const CACHE_VERSION = '15d23ab0263e'; const CACHE_VERSION = '403b5842d1cd';
const CACHE_PREFIX = 'desfoto-gtin-generator-'; const CACHE_PREFIX = 'desfoto-gtin-generator-';
const CACHE_NAME = `${CACHE_PREFIX}${CACHE_VERSION}`; const CACHE_NAME = `${CACHE_PREFIX}${CACHE_VERSION}`;
// PRECACHE // PRECACHE
const PRECACHE = [ const PRECACHE = [
'./', './',
'./index.html',
'./css/app.css', './css/app.css',
'./js/app.js', './js/app.js',
'./js/gtin.js', './js/gtin.js',
@@ -36,7 +35,7 @@ function scopeUrl(pfad) {
return new URL(pfad, sw.registration.scope).toString(); return new URL(pfad, sw.registration.scope).toString();
} }
const INDEX_URL = scopeUrl('./index.html'); const INDEX_URL = scopeUrl('./');
/** Pfad des Geltungsbereichs, z. B. "/gtin/". */ /** Pfad des Geltungsbereichs, z. B. "/gtin/". */
const SCOPE_PFAD = new URL(sw.registration.scope).pathname; const SCOPE_PFAD = new URL(sw.registration.scope).pathname;
@@ -59,7 +58,7 @@ const SHELL_KENNUNG = 'id="artikelnummer"';
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async function istAnwendungshuelle(antwort) { async function istAnwendungshuelle(antwort) {
if (!antwort.ok || antwort.type !== 'basic') return false; if (!antwort.ok || antwort.type !== 'basic' || antwort.redirected) return false;
const inhaltstyp = antwort.headers.get('content-type') ?? ''; const inhaltstyp = antwort.headers.get('content-type') ?? '';
if (!inhaltstyp.includes('text/html')) return false; if (!inhaltstyp.includes('text/html')) return false;
const endgueltig = new URL(antwort.url); const endgueltig = new URL(antwort.url);
@@ -147,8 +146,7 @@ sw.addEventListener('fetch', (ereignis) => {
if (anfrage.mode === 'navigate') { if (anfrage.mode === 'navigate') {
const appStart = new URL('./', sw.registration.scope).pathname; const appStart = new URL('./', sw.registration.scope).pathname;
const appIndex = new URL('./index.html', sw.registration.scope).pathname; if (ziel.pathname !== appStart) return;
if (ziel.pathname !== appStart && ziel.pathname !== appIndex) return;
ereignis.respondWith(bedieneNavigation(ereignis, anfrage)); ereignis.respondWith(bedieneNavigation(ereignis, anfrage));
return; return;
} }
@@ -157,7 +155,7 @@ sw.addEventListener('fetch', (ereignis) => {
caches.match(anfrage).then((treffer) => { caches.match(anfrage).then((treffer) => {
if (treffer) return treffer; if (treffer) return treffer;
return fetch(anfrage).then((antwort) => { return fetch(anfrage).then((antwort) => {
if (antwort.ok && antwort.type === 'basic') { if (antwort.ok && antwort.type === 'basic' && !antwort.redirected) {
const kopie = antwort.clone(); const kopie = antwort.clone();
ereignis.waitUntil( ereignis.waitUntil(
caches caches

View File

@@ -10,14 +10,13 @@
const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self)); const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self));
const CACHE_VERSION = '15d23ab0263e'; const CACHE_VERSION = '__BUILD_VERSION__';
const CACHE_PREFIX = 'desfoto-gtin-generator-'; const CACHE_PREFIX = 'desfoto-gtin-generator-';
const CACHE_NAME = `${CACHE_PREFIX}${CACHE_VERSION}`; const CACHE_NAME = `${CACHE_PREFIX}${CACHE_VERSION}`;
// PRECACHE // PRECACHE
const PRECACHE = [ const PRECACHE = [
'./', './',
'./index.html',
'./css/app.css', './css/app.css',
'./js/app.js', './js/app.js',
'./js/gtin.js', './js/gtin.js',
@@ -36,7 +35,7 @@ function scopeUrl(pfad) {
return new URL(pfad, sw.registration.scope).toString(); return new URL(pfad, sw.registration.scope).toString();
} }
const INDEX_URL = scopeUrl('./index.html'); const INDEX_URL = scopeUrl('./');
/** Pfad des Geltungsbereichs, z. B. "/gtin/". */ /** Pfad des Geltungsbereichs, z. B. "/gtin/". */
const SCOPE_PFAD = new URL(sw.registration.scope).pathname; const SCOPE_PFAD = new URL(sw.registration.scope).pathname;
@@ -59,7 +58,7 @@ const SHELL_KENNUNG = 'id="artikelnummer"';
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async function istAnwendungshuelle(antwort) { async function istAnwendungshuelle(antwort) {
if (!antwort.ok || antwort.type !== 'basic') return false; if (!antwort.ok || antwort.type !== 'basic' || antwort.redirected) return false;
const inhaltstyp = antwort.headers.get('content-type') ?? ''; const inhaltstyp = antwort.headers.get('content-type') ?? '';
if (!inhaltstyp.includes('text/html')) return false; if (!inhaltstyp.includes('text/html')) return false;
const endgueltig = new URL(antwort.url); const endgueltig = new URL(antwort.url);
@@ -147,8 +146,7 @@ sw.addEventListener('fetch', (ereignis) => {
if (anfrage.mode === 'navigate') { if (anfrage.mode === 'navigate') {
const appStart = new URL('./', sw.registration.scope).pathname; const appStart = new URL('./', sw.registration.scope).pathname;
const appIndex = new URL('./index.html', sw.registration.scope).pathname; if (ziel.pathname !== appStart) return;
if (ziel.pathname !== appStart && ziel.pathname !== appIndex) return;
ereignis.respondWith(bedieneNavigation(ereignis, anfrage)); ereignis.respondWith(bedieneNavigation(ereignis, anfrage));
return; return;
} }
@@ -157,7 +155,7 @@ sw.addEventListener('fetch', (ereignis) => {
caches.match(anfrage).then((treffer) => { caches.match(anfrage).then((treffer) => {
if (treffer) return treffer; if (treffer) return treffer;
return fetch(anfrage).then((antwort) => { return fetch(anfrage).then((antwort) => {
if (antwort.ok && antwort.type === 'basic') { if (antwort.ok && antwort.type === 'basic' && !antwort.redirected) {
const kopie = antwort.clone(); const kopie = antwort.clone();
ereignis.waitUntil( ereignis.waitUntil(
caches caches