From be8801aef7f39cbabb0505314c0c09e05abdffc9 Mon Sep 17 00:00:00 2001 From: Ulas Date: Wed, 1 Apr 2026 09:57:48 +0200 Subject: [PATCH] fix: proxy weather icons through server to fix PWA standalone on Android MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit External image requests to openweathermap.org fail silently in Chrome Android PWA standalone mode. Icons are now proxied via GET /api/v1/weather/icon/:code, making them same-origin — cacheable by the service worker and free of CORS/CSP issues. Tightened CSP: removed openweathermap.org from imgSrc (no longer needed). Co-Authored-By: Claude Sonnet 4.6 --- public/pages/dashboard.js | 6 +++--- server/index.js | 2 +- server/routes/weather.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/public/pages/dashboard.js b/public/pages/dashboard.js index e43655a..2d58090 100644 --- a/public/pages/dashboard.js +++ b/public/pages/dashboard.js @@ -265,7 +265,7 @@ function renderPinnedNotes(notes) { // Wetter-Widget // -------------------------------------------------------- -const WEATHER_ICON_BASE = 'https://openweathermap.org/img/wn/'; +const WEATHER_ICON_BASE = '/api/v1/weather/icon/'; function renderWeatherWidget(weather) { if (!weather) return ''; @@ -279,7 +279,7 @@ function renderWeatherWidget(weather) { return `
${label}
- ${d.desc}
${d.temp_max}° @@ -303,7 +303,7 @@ function renderWeatherWidget(weather) { ${t('dashboard.weatherFeelsLike', { temp: current.feels_like, humidity: current.humidity, wind: current.wind_speed })}
- ${current.desc} ${forecast.length ? `
${forecastHtml}
` : ''} diff --git a/server/index.js b/server/index.js index 7a1ebec..86ca38f 100644 --- a/server/index.js +++ b/server/index.js @@ -43,7 +43,7 @@ app.use(helmet({ 'https://cdn.jsdelivr.net', ], styleSrc: ["'self'", "'unsafe-inline'"], - imgSrc: ["'self'", 'data:', 'https://openweathermap.org'], + imgSrc: ["'self'", 'data:'], connectSrc: ["'self'"], fontSrc: ["'self'"], objectSrc: ["'none'"], diff --git a/server/routes/weather.js b/server/routes/weather.js index a555662..152d232 100644 --- a/server/routes/weather.js +++ b/server/routes/weather.js @@ -92,4 +92,33 @@ router.get('/', async (req, res) => { } }); +// -------------------------------------------------------- +// GET /api/v1/weather/icon/:code +// Proxy für OpenWeatherMap-Icons — vermeidet externe Bild-Requests +// im PWA-Standalone-Modus (CORS/CSP-Probleme auf Android Chrome). +// Erlaubte Codes: 2–4 alphanumerische Zeichen (z.B. "01d", "10n"). +// Response: PNG-Bild mit 24h-Cache +// -------------------------------------------------------- +router.get('/icon/:code', async (req, res) => { + const { code } = req.params; + if (!/^[a-zA-Z0-9]{2,4}$/.test(code)) { + return res.status(400).json({ error: 'Ungültiger Icon-Code.', code: 400 }); + } + + try { + const { default: fetch } = await import('node-fetch'); + const url = `https://openweathermap.org/img/wn/${code}@2x.png`; + const upstream = await fetch(url, { signal: AbortSignal.timeout(5000) }); + if (!upstream.ok) { + return res.status(502).json({ error: 'Icon nicht verfügbar.', code: 502 }); + } + res.setHeader('Content-Type', 'image/png'); + res.setHeader('Cache-Control', 'public, max-age=86400'); // 24 Stunden + upstream.body.pipe(res); + } catch (err) { + console.warn('[Weather] Icon-Proxy Fehler:', err.message); + res.status(502).json({ error: 'Icon-Proxy fehlgeschlagen.', code: 502 }); + } +}); + module.exports = router;