Fix XSS, validation, and CSS regressions; refactor proxy retry logic

- Sanitize geocode suggestion rendering to prevent XSS
- Add lat/lon validation and cache eviction to weather proxy endpoints
- Extract shared fetchWithRetry helper, dedupe retry/timeout logic
- Stop leaking upstream error details to clients
- Fix radar provider metadata and drop dead nowcast field
- Fix missing --surface-radius-sm var and duplicate/conflicting CSS rules
This commit is contained in:
Nate Emmert committed 2026-06-15 08:51:18 -04:00
1 parent 49b54e0fe4
commit 1863f6784b
4 files changed
+104 -144

No files matched your search

+7 -10
View File
@@ -425,7 +425,6 @@ async function fetchWeather(lat, lon, tz) {
async function fetchAirQuality(lat, lon, tz) {
const url = `/api/air-quality?lat=${lat}&lon=${lon}&tz=${encodeURIComponent(tz||'auto')}`;
console.log('Fetching air quality from:', url);
const r = await fetch(url);
if (!r.ok) throw new Error(`Air quality fetch failed with status ${r.status}`);
return r.json();
@@ -641,13 +640,6 @@ function createRadar(mapEl, timelineEl, playBtn, tsEl, layerBtn) {
maxNativeZoom: f.maxNativeZoom,
isForecast: false,
}));
(data.radar?.nowcast || []).forEach(f => radarFrames.push({
time: f.time,
path: f.path,
url: f.url,
maxNativeZoom: f.maxNativeZoom,
isForecast: true,
}));
(data.satellite?.infrared || []).forEach(f => satelliteFrames.push({
time: f.time,
path: f.path,
@@ -1076,7 +1068,7 @@ function renderWeather(data, city) {
document.getElementById('cur-desc').textContent = w.desc;
document.getElementById('cur-temp').textContent = tempDisplay(c.temperature_2m);
document.querySelector('.deg').textContent = `°${tempUnit()}`;
document.querySelector('.feels').innerHTML = `${apparentLabel(c.temperature_2m, c.apparent_temperature)} <span id="cur-feels">${tempDisplay(c.apparent_temperature)}</span>°${tempUnit()}`;
document.getElementById('feels-line').innerHTML = `${apparentLabel(c.temperature_2m, c.apparent_temperature)} <span id="cur-feels">${tempDisplay(c.apparent_temperature)}</span>°${tempUnit()}`;
document.getElementById('cur-hi').textContent = tempDisplay(d.temperature_2m_max[0]);
document.getElementById('cur-lo').textContent = tempDisplay(d.temperature_2m_min[0]);
document.getElementById('cur-precip').textContent = c.precipitation > 0 ? `${precipDisplay(c.precipitation)} ${state.units === 'us' ? 'in' : 'mm'} precip` : 'No precipitation';
@@ -1668,7 +1660,12 @@ searchInput.addEventListener('input', () => {
results.forEach(r => {
const li = document.createElement('li');
li.innerHTML = `${r.name}<span class="sug-country">${r.admin1 ? r.admin1 + ', ' : ''}${r.country || ''}</span>`;
const nameText = document.createTextNode(r.name);
const countrySpan = document.createElement('span');
countrySpan.className = 'sug-country';
countrySpan.textContent = `${r.admin1 ? r.admin1 + ', ' : ''}${r.country || ''}`;
li.appendChild(nameText);
li.appendChild(countrySpan);
li.addEventListener('click', () => {
searchInput.value = r.name;
hide(suggestEl);