diora-web/static/js/sw.js
mrwnslz 1d6ddabb7b
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 8m24s
Test / e2e (push) Successful in 1m48s
Podcast-Player: abgelaufene Download-Links überstehen
SoundCloud leitet die Feed-Adresse auf einen signierten Link um, der nach
zwei Minuten abläuft. Das Audio-Element fragt für jeden weiteren Teil der
Datei diesen Link an, wird abgewiesen (MEDIA_ERR_NETWORK) und blieb danach
kaputt, bis die App neu startete. Jetzt wird die Folge bei einem Fehler an
derselben Stelle neu geladen (bis zu dreimal pro Minute), und nach dem
Aufgeben beim nächsten Abspielen frisch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:28:37 +02:00

137 lines
4.6 KiB
JavaScript

/**
* diora service worker — caches the app shell for offline use.
*/
const CACHE = 'diora-v111';
const SHELL = [
'/static/css/app.css',
'/static/js/app/01-core.js',
'/static/js/app/10-radio.js',
'/static/js/app/14-focus.js',
'/static/js/app/16-stations.js',
'/static/js/app/20-tabs.js',
'/static/js/app/24-ambient.js',
'/static/js/app/26-sidebar.js',
'/static/js/app/28-serviceworker.js',
'/static/js/app/29-m3u.js',
'/static/js/app/30-contrast.js',
'/static/js/app/32-crypto.js',
'/static/js/app/34-wallpaper.js',
'/static/js/app/40-epub.js',
'/static/js/app/44-books.js',
'/static/js/app/46-readpos.js',
'/static/js/app/48-book-cache.js',
'/static/js/app/50-reader-state.js',
'/static/js/app/52-book-list.js',
'/static/js/app/56-cloud-import.js',
'/static/js/app/58-pdf.js',
'/static/js/app/60-tts.js',
'/static/js/app/62-reader.js',
'/static/js/app/64-reader-settings.js',
'/static/js/app/66-pdf-paginated.js',
'/static/js/app/68-bookmarks.js',
'/static/js/app/70-search.js',
'/static/js/app/72-highlights.js',
'/static/js/app/74-margin.js',
'/static/js/app/76-export.js',
'/static/js/app/78-footnotes.js',
'/static/js/app/80-radio-sidebar.js',
'/static/js/app/99-init.js',
'/static/js/podcasts-web.js',
'/static/js/home.js',
'/static/js/feeds.js',
'/static/podcasts/native-podcast.js',
'/static/podcasts/podcast-ui.css',
'/static/podcasts/podcasts-web.css',
'/static/manifest.json',
'/static/ambient/index.html',
'/static/fonts/SourceSerif4-normal-latin.woff2',
'/static/fonts/SourceSerif4-normal-latin-ext.woff2',
'/static/fonts/SourceSerif4-italic-latin.woff2',
'/static/fonts/SourceSerif4-italic-latin-ext.woff2',
];
// Install: pre-cache the app shell
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(CACHE).then(function (cache) {
return cache.addAll(SHELL);
})
);
// Activate immediately without waiting for old tabs to close
self.skipWaiting();
});
// Activate: remove stale caches from previous versions (also the old
// 'diora-podcast-v1' of episodes the web app used to download)
self.addEventListener('activate', function (event) {
event.waitUntil(
caches.keys().then(function (keys) {
return Promise.all(
keys.filter(function (key) { return key !== CACHE; })
.map(function (key) { return caches.delete(key); })
);
}).then(function () {
return self.clients.claim();
}).then(function () {
// Tell all open tabs to reload so they get the latest cached assets
return self.clients.matchAll({type: 'window'}).then(function (clients) {
clients.forEach(function (client) {
client.postMessage({type: 'SW_ACTIVATED'});
});
});
})
);
});
// Fetch: serve from cache, fall back to network
self.addEventListener('fetch', function (event) {
// Only handle GET requests; let POST/SSE etc. pass through
if (event.request.method !== 'GET') return;
const url = new URL(event.request.url);
// Bypass for API/mutation endpoints
if (url.pathname.startsWith('/radio/sse/') ||
url.pathname.startsWith('/radio/record/') ||
url.pathname.startsWith('/radio/affiliate/') ||
url.pathname.startsWith('/tts/') ||
url.pathname.startsWith('/admin/') ||
url.pathname.startsWith('/podcasts/progress/') ||
url.pathname.startsWith('/podcasts/queue/') ||
url.pathname.startsWith('/podcasts/player/') ||
url.pathname === '/podcasts/feeds/add' ||
url.pathname.startsWith('/podcasts/feeds/add') ||
url.pathname.includes('/remove') ||
url.pathname.startsWith('/podcasts/feeds/refresh')) {
return;
}
// Cache-first for pre-defined shell assets
const isShell = SHELL.some(function (s) { return url.pathname === s; });
if (isShell) {
event.respondWith(
// ignoreSearch: the Ambient frame loads with ?embed=1&bg=…
caches.match(event.request, {ignoreSearch: true}).then(function (cached) {
return cached || fetch(event.request);
})
);
return;
}
// Navigation requests (the HTML page itself): network-first, fall back to
// last cached version so the app opens offline after being visited once.
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).then(function (response) {
if (response.ok) {
var clone = response.clone();
caches.open(CACHE).then(function (cache) { cache.put(event.request, clone); });
}
return response;
}).catch(function () {
return caches.match(event.request);
})
);
}
});