baustelle-pwa/sw.js
Eduard Wisch 91a6560f7e
All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 1s
feat: Neues PWA-Logo — Klemmbrett mit Schraubenschlüssel (SVG + PNG)
Icon ersetzt das PHP/GD-generierte Placeholder durch ein echtes
Baustelle-Doku-Motiv:
- Dunkelblauer Radial-Gradient Hintergrund (rounded rectangle)
- Oranges Klemmbrett mit weißem Papier, Text-Zeilen und grüner
  Checkbox (symbolisiert fertige Arbeit)
- Metallische Klemme oben mit Riegel
- Blauer Schraubenschlüssel diagonal über dem Brett

SVG als Single-Source-of-Truth, PNG-Versionen in 192 und 512 px via
Chromium-Headless aus HTML-Wrapper gerendert (damit viewBox richtig
skaliert wird).

index.html verlinkt jetzt zusätzlich icon.svg als Favicon für Browser
die SVG-Favicons unterstützen.

SW-Cache auf v7.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 01:12:47 +02:00

106 lines
3.6 KiB
JavaScript

/* Baustelle PWA Service Worker.
* Cache-Strategie:
* - App-Shell (HTML/CSS/JS): cache-first, network update
* - API-Calls: network-first, kein offline-cache (da auth-pflichtig)
*/
const CACHE = 'baustelle-v7';
const SHELL = [
'./',
'./index.html',
'./share.html',
'./app.css',
'./app.js',
'./manifest.webmanifest',
'./lib/idb.js',
'./lib/api.js',
'./lib/offline.js',
'./lib/router.js',
'./icons/icon-192.png',
'./icons/icon-512.png',
];
// Web Share Target: eingehende POSTs an share.html abfangen und in IDB zwischenspeichern
async function handleShareTarget(request) {
const fd = await request.formData();
// Alle bekannten Feldnamen durchprobieren — verschiedene Android-Gallery-Apps
// nutzen unterschiedliche Namen
let files = [];
for (const key of ['photos', 'file', 'files', 'image', 'images']) {
const v = fd.getAll(key);
if (v && v.length) files = files.concat(v);
}
// Fallback: alle File-Entries im FormData durchsuchen
if (!files.length) {
for (const [k, v] of fd.entries()) {
if (v && typeof v === 'object' && v.size !== undefined) files.push(v);
}
}
if (files.length) {
const db = await new Promise((res, rej) => {
const req = indexedDB.open('baustelle-pwa-v1', 1);
req.onupgradeneeded = () => {
const d = req.result;
if (!d.objectStoreNames.contains('kv')) d.createObjectStore('kv');
if (!d.objectStoreNames.contains('queue')) d.createObjectStore('queue', { keyPath: 'id', autoIncrement: true });
};
req.onsuccess = () => res(req.result);
req.onerror = () => rej(req.error);
});
const tx = db.transaction('kv', 'readwrite');
tx.objectStore('kv').put(files.map(f => ({ name: f.name, type: f.type, data: f })), 'shared_files');
await new Promise(res => tx.oncomplete = res);
}
return Response.redirect('./share.html', 303);
}
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL).catch(() => null)));
self.skipWaiting();
});
self.addEventListener('activate', (e) => {
e.waitUntil(
caches.keys().then(keys => Promise.all(
keys.filter(k => k !== CACHE).map(k => caches.delete(k))
))
);
self.clients.claim();
});
self.addEventListener('fetch', (e) => {
const url = new URL(e.request.url);
// Web Share Target: POST auf share.html abfangen
if (e.request.method === 'POST' && url.pathname.endsWith('/share.html')) {
e.respondWith(handleShareTarget(e.request));
return;
}
// API-Requests: nicht cachen, durchreichen
if (url.pathname.includes('/custom/bericht/api/')) {
return; // default network
}
// App-Shell: cache-first
if (e.request.method === 'GET' && url.origin === location.origin) {
e.respondWith(
caches.match(e.request).then(hit => {
if (hit) {
// Background-Update
fetch(e.request).then(r => {
if (r.ok) caches.open(CACHE).then(c => c.put(e.request, r.clone()));
}).catch(() => null);
return hit;
}
return fetch(e.request).then(r => {
if (r.ok) {
const clone = r.clone();
caches.open(CACHE).then(c => c.put(e.request, clone));
}
return r;
});
})
);
}
});