Use station data for current conditions; persist keys server-side

Station overlay:
- When Ambient Weather station data is present and fresh (<15 min),
  its readings replace the model's current conditions: temp, feels-like,
  humidity, dewpoint, wind speed/direction, gusts, pressure (inHg→hPa),
  UV index, rain today, wind peek summary, and data-panel readouts
- A small "📡 My Station" badge appears in the hero when active
- applyStationOverlay() is called at the end of renderWeather (instant
  if station data already loaded) and again whenever station data
  refreshes (so the display stays live every 60 s)
- Hourly/daily forecasts continue to use Open-Meteo

Server-side key persistence:
- POST /api/ambient-config saves keys to data/ambient-config.json
  (same persistent volume as VAPID and subscriptions)
- GET /api/ambient-config returns {configured: bool} without exposing keys
- /api/ambient checks query params → saved config → env vars in order
- Settings Save button posts keys to server after a successful test;
  confirms "saved for all devices" in the status line
- On page load, if no localStorage keys but server is configured,
  the station loads automatically on any device
This commit is contained in:
Claude committed 2026-08-19 15:53:10 +00:00
1 parent b9e2b463f5
commit 0005698264
4 files changed
+151 -4

No files matched your search

+32 -3
View File
@@ -441,10 +441,39 @@ app.get('/api/spc/reports', async (_req, res) => {
});
// ── Ambient Weather personal station ─────────────────────────────────────────
const ambientCaches = new Map(); // keyed by apiKey so each user's cache is independent
const AMBIENT_CONFIG_FILE = path.join(DATA_DIR, 'ambient-config.json');
let ambientServerConfig = { apiKey: '', appKey: '' };
try {
if (fs.existsSync(AMBIENT_CONFIG_FILE)) {
ambientServerConfig = JSON.parse(fs.readFileSync(AMBIENT_CONFIG_FILE, 'utf8'));
}
} catch {}
function saveAmbientConfig() {
try { fs.writeFileSync(AMBIENT_CONFIG_FILE, JSON.stringify(ambientServerConfig)); } catch {}
}
const ambientCaches = new Map();
// Save keys server-side so all devices share them
app.post('/api/ambient-config', (req, res) => {
const { apiKey, appKey } = req.body || {};
if (!apiKey || !appKey) return res.status(400).json({ error: 'apiKey and appKey required' });
ambientServerConfig = { apiKey, appKey };
saveAmbientConfig();
res.json({ ok: true });
});
// Return whether server-side keys exist (never expose the actual keys)
app.get('/api/ambient-config', (_req, res) => {
const hasKeys = !!(ambientServerConfig.apiKey && ambientServerConfig.appKey);
res.json({ configured: hasKeys });
});
app.get('/api/ambient', async (req, res) => {
const apiKey = req.query.apiKey || process.env.AMBIENT_API_KEY;
const appKey = req.query.appKey || process.env.AMBIENT_APP_KEY;
// Priority: query params (client override) → server-saved → env vars
const apiKey = req.query.apiKey || ambientServerConfig.apiKey || process.env.AMBIENT_API_KEY;
const appKey = req.query.appKey || ambientServerConfig.appKey || process.env.AMBIENT_APP_KEY;
if (!apiKey || !appKey) return res.status(503).json({ error: 'Ambient Weather keys not configured' });
const cacheKey = apiKey;