1/* Meteozentrale Root PWA Service Worker 2 * Version: MZ-PWA-ROOT-V81.2.1-APP-QUALITY 3 * Ziel: App/PWA-Dateien frisch halten und WordPress-Admin/REST nie abfangen. 4 */ 5const MZ_SW_VERSION = 'mz-pwa-root-v81.2.1'; 6const STATIC_CACHE = `${ MZ_SW_VERSION }-static`; 7const OFFLINE_URL = '/offline.html'; 8const APP_SHELL_URL = '/app/'; 9 10// Webpushr wird ausschlieÃlich im gemeinsamen Wrapper /webpushr-sw.js geladen. 11// Dadurch läuft dessen Push-Handler nicht doppelt. 12// Diese Kern-Dateien halten die App auch nach einem Serverausfall startfähig. 13// Im Normalbetrieb werden sie weiterhin zuerst aus dem Netz aktualisiert. 14const CORE_PRECACHE_URLS = [ 15 OFFLINE_URL, 16 APP_SHELL_URL, 17 '/manifest.webmanifest', 18 '/assets/pwa/app-v51.css?v=51.2.1', 19 '/assets/pwa/mz-location-core.js?v=20', 20 '/assets/pwa/app-v51.js?v=51.2.1' 21]; 22 23const OPTIONAL_PRECACHE_URLS = [ 24 '/assets/pwa/icons/meteozentrale-app-192.png', 25 '/assets/pwa/icons/meteozentrale-app-512.png', 26 '/assets/pwa/icons/meteozentrale-app-maskable-192.png', 27 '/assets/pwa/icons/meteozentrale-app-maskable-512.png' 28]; 29 30const WORDPRESS_BYPASS_PATTERNS = [ 31 /^\/wp-admin(?:\/|$)/i, 32 /^\/wp-login\.php$/i, 33 /^\/wp-json(?:\/|$)/i, 34 /^\/wp-cron\.php$/i 35]; 36 37const NETWORK_ONLY_PATTERNS = [ 38 /\/app\/app-location-api\.php/i, 39 /\/app\/app-live-api\.php/i, 40 /\/app\/pwa-install-counter\.php/i, 41 /\/sw\.js$/i, 42 /\/staticguard_data\.js/i, 43 /\/data\.js/i, 44 /\/api\//i, 45 /\/ajax/i, 46 /\/output_radar\/staticguard\//i, 47 /\/Orte_(Sturm|Niederschlag|Unwetter)\//i, 48 /\/recent_searches/i, 49 /\/sitemap_/i, 50 /\/canonical_map_/i 51]; 52 53const APP_SHELL_ASSET_PATTERNS = [ 54 /\/assets\/pwa\/(?:app(?:-v\d+)?|mz-location-core)\.(?:js|css)$/i, 55 /\/manifest\.webmanifest$/i 56]; 57 58const STATIC_ASSET_PATTERNS = [ 59 /\/assets\/pwa\/icons\//i, 60 /\/wp-content\/themes\//i, 61 /\/wp-content\/plugins\//i, 62 /\/wp-includes\/js\//i, 63 /\.(?:woff2?|ttf|eot|svg|png|jpg|jpeg|webp|gif|ico)$/i 64]; 65 66self.addEventListener( 'install', event => { 67 event.waitUntil( ( async () => { 68 const cache = await caches.open( STATIC_CACHE ); 69 // Fehlt eine Kern-Datei, bleibt der bisherige funktionsfähige Worker aktiv. 70 await Promise.all( CORE_PRECACHE_URLS.map( async url => { 71 const request = new Request( url, { cache: 'reload' } ); 72 const response = await fetch( request ); 73 if ( ! response || ! response.ok ) throw new Error( `Precache fehlgeschlagen: ${ url }` ); 74 const target = isAppShellAsset( new URL( request.url ) ) ? appShellCacheRequest( request ) : request; 75 await cache.put( target, response ); 76 } ) ); 77 // Ein einzelnes fehlendes Icon darf die Installation nicht verhindern. 78 await Promise.allSettled( OPTIONAL_PRECACHE_URLS.map( async url => { 79 const request = new Request( url, { cache: 'reload' } ); 80 const response = await fetch( request ); 81 if ( response && response.ok ) await cache.put( request, response ); 82 } ) ); 83 await self.skipWaiting(); 84 } )() ); 85} ); 86 87self.addEventListener( 'activate', event => { 88 event.waitUntil( ( async () => { 89 const keys = await caches.keys(); 90 91 await Promise.all( keys.map( key => { 92 if ( 93 key !== STATIC_CACHE 94 && ( 95 key.startsWith( 'mz-pwa-root-' ) 96 || key.startsWith( 'weather-app-' ) 97 || key.startsWith( 'meteozentrale-pwa-' ) 98 ) 99 ) { 100 return caches.delete( key ); 101 } 102 103 return Promise.resolve( false ); 104 } ) ); 105 106 if ( self.registration.navigationPreload ) { 107 try { await self.registration.navigationPreload.enable(); } catch ( error ) {} 108 } 109 await self.clients.claim(); 110 111 const clients = await self.clients.matchAll( { 112 type: 'window', 113 includeUncontrolled: true 114 } ); 115 116 clients.forEach( client => client.postMessage( { 117 type: 'MZ_SW_UPDATED', 118 version: MZ_SW_VERSION 119 } ) ); 120 } )() ); 121} ); 122 123function sameOrigin( url ) { 124 return url.origin === self.location.origin; 125} 126 127function mustBypassWordPress( request, url ) { 128 if ( 129 WORDPRESS_BYPASS_PATTERNS.some( pattern => pattern.test( url.pathname ) ) 130 || url.searchParams.has( 'rest_route' ) 131 ) { 132 return true; 133 } 134 135 if ( ! request.referrer ) { 136 return false; 137 } 138 139 try { 140 const referrerUrl = new URL( request.referrer ); 141 142 return sameOrigin( referrerUrl ) 143 && WORDPRESS_BYPASS_PATTERNS.some( pattern => pattern.test( referrerUrl.pathname ) ); 144 } catch ( error ) { 145 return false;
146 } 147} 148 149function mustNetwork( url ) { 150 return NETWORK_ONLY_PATTERNS.some( pattern => pattern.test( url.pathname ) ); 151} 152 153function isStatic( url ) { 154 return STATIC_ASSET_PATTERNS.some( pattern => pattern.test( url.pathname ) ); 155} 156 157function isAppShellNavigation( url ) { 158 return /^\/app\/?(?:index\.php)?$/i.test( url.pathname ); 159} 160 161function isAppShellAsset( url ) { 162 return APP_SHELL_ASSET_PATTERNS.some( pattern => pattern.test( url.pathname ) ); 163} 164 165function appShellCacheRequest( request ) { 166 const url = new URL( request.url ); 167 return new Request( url.origin + url.pathname ); 168} 169 170async function networkOnly( request ) { 171 return fetch( request, { cache: 'no-store' } ); 172} 173 174async function navigationNetworkFirst( request, preloadResponse ) { 175 const url = new URL( request.url ); 176 const isAppShell = isAppShellNavigation( url ); 177 try { 178 const preload = await preloadResponse; 179 let response = preload && preload.ok ? preload : await fetch( request, { cache: 'no-store' } ); 180 if ( response && response.ok && isAppShell ) { 181 const cache = await caches.open( STATIC_CACHE ); 182 await cache.put( APP_SHELL_URL, response.clone() ); 183 } 184 if ( response && response.ok ) return response; 185 if ( isAppShell ) { 186 const cachedShell = await caches.match( APP_SHELL_URL ); 187 if ( cachedShell ) return cachedShell; 188 } 189 return response; 190 } catch ( error ) { 191 if ( isAppShell ) { 192 const cachedShell = await caches.match( APP_SHELL_URL ); 193 if ( cachedShell ) return cachedShell; 194 } 195 return await caches.match( OFFLINE_URL ) || Response.error(); 196 } 197} 198 199async function appShellNetworkFirst( request ) { 200 const cache = await caches.open( STATIC_CACHE ); 201 const cacheKey = appShellCacheRequest( request ); 202 const cached = await cache.match( cacheKey ); 203 try { 204 const response = await fetch( request, { cache: 'reload' } ); 205 if ( response && response.ok ) { 206 await cache.put( cacheKey, response.clone() ); 207 return response; 208 } 209 return cached || response; 210 } catch ( error ) { 211 return cached || Response.error(); 212 } 213} 214 215async function staleWhileRevalidate( request ) { 216 const cache = await caches.open( STATIC_CACHE ); 217 const cached = await cache.match( request ); 218 const fresh = ( async () => { 219 try { 220 const response = await fetch( request, { cache: 'reload' } ); 221 if ( response && response.ok ) await cache.put( request, response.clone() ); 222 return response; 223 } catch ( error ) { return cached; } 224 } )(); 225 226 return cached || fresh; 227} 228 229self.addEventListener( 'fetch', event => { 230 const request = event.request; 231 232 if ( request.method !== 'GET' ) { 233 return; 234 } 235 if ( request.headers.has( 'range' ) ) return; 236 237 const url = new URL( request.url ); 238 239 if ( ! sameOrigin( url ) ) { 240 return; 241 } 242 243 /* 244 * WordPress-Admin, Login und REST vollstaendig dem Browser ueberlassen. 245 * Auch Admin-Skripte werden anhand ihres Referrers ausgenommen. Ohne 246 * respondWith() kann der Service-Worker weder Antwort noch Cache veraendern. 247 */ 248 if ( mustBypassWordPress( request, url ) ) { 249 return; 250 } 251 252 if ( request.mode === 'navigate' ) { 253 event.respondWith( navigationNetworkFirst( request, event.preloadResponse ) ); 254 return; 255 } 256 257 if ( isAppShellAsset( url ) ) { 258 event.respondWith( appShellNetworkFirst( request ) ); 259 return; 260 } 261 262 if ( mustNetwork( url ) ) { 263 event.respondWith( networkOnly( request ) ); 264 return; 265 } 266 267 if ( isStatic( url ) ) { 268 event.respondWith( staleWhileRevalidate( request ) ); 269 } 270} ); 271 272self.addEventListener( 'message', event => { 273 if ( ! event.data ) { 274 return; 275 } 276 277 if ( event.data.type === 'SKIP_WAITING' ) { 278 self.skipWaiting(); 279 } 280} );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.