PageSourceSearch

https://meteozentrale.de/sw.js?v=mz-pwa-v81-1

js meteozentrale.de collected 2026-10-02 17:51:39 UTC 7,820 bytes, 280 lines download raw bytes

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.