diff --git a/Cargo.lock b/Cargo.lock index 7342f4c..f2a7d26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1590,7 +1590,7 @@ dependencies = [ [[package]] name = "rs_maps" -version = "0.3.1" +version = "0.3.2" dependencies = [ "anyhow", "argon2", diff --git a/Cargo.toml b/Cargo.toml index 0217058..de1e7a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ # Cargo.toml [package] name = "rs_maps" -version = "0.3.1" +version = "0.3.2" edition = "2021" [dependencies] diff --git a/frontend/app.js b/frontend/app.js index 680ae77..8e9ed26 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -26,7 +26,19 @@ const NOMINATIM = 'https://nominatim.openstreetmap.org/search'; // Nominatim's search endpoint doesn't return object tags, so opening hours, // phone and website need a second lookup by OSM id. Overpass is donated // infrastructure like Nominatim: fine on an explicit click, not per result. -const OVERPASS = 'https://overpass-api.de/api/interpreter'; +// +// Tried in order. The main overpass-api.de instance is by far the busiest and +// returns 504 under load even for a trivial single-object query, so the mirrors +// are a practical necessity rather than belt and braces. +const OVERPASS_ENDPOINTS = [ + 'https://overpass.kumi.systems/api/interpreter', + 'https://overpass-api.de/api/interpreter', + 'https://overpass.private.coffee/api/interpreter', +]; + +// Past this, move on to the next mirror. A single-object lookup that hasn't +// answered in 8s isn't going to. +const OVERPASS_TIMEOUT_MS = 8000; const COLOURS = ['#4E9C6B', '#D2467F', '#E2A93C', '#4E8FC9', '#B07BD4', '#D3574B']; const DEFAULT_COLOUR = COLOURS[0]; @@ -693,16 +705,38 @@ async function fetchOsmTags(osmType, osmId) { const short = OSM_SHORT[osmType]; if (!short) return null; - const query = `[out:json][timeout:20];${short}(${osmId});out tags;`; - const res = await fetch(OVERPASS, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: 'data=' + encodeURIComponent(query), - }); - if (!res.ok) throw new Error('overpass ' + res.status); + const query = `[out:json][timeout:10];${short}(${osmId});out tags;`; + let lastError = null; - const body = await res.json(); - return (body.elements && body.elements[0] && body.elements[0].tags) || {}; + for (const endpoint of OVERPASS_ENDPOINTS) { + // AbortController rather than relying on the server: a hung connection + // would otherwise block the fallback for as long as the browser allows. + const abort = new AbortController(); + const timer = setTimeout(() => abort.abort(), OVERPASS_TIMEOUT_MS); + + try { + const res = await fetch(endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'data=' + encodeURIComponent(query), + signal: abort.signal, + }); + + // 429 and 504 mean this mirror is busy, not that the object is missing — + // worth asking someone else. + if (!res.ok) throw new Error('overpass ' + res.status); + + const body = await res.json(); + return (body.elements && body.elements[0] && body.elements[0].tags) || {}; + } catch (err) { + lastError = err; + console.warn('overpass mirror failed:', endpoint, err.message); + } finally { + clearTimeout(timer); + } + } + + throw lastError || new Error('no overpass endpoint responded'); } /* Renders whatever came back. Deliberately raw values — no "open now", no @@ -875,7 +909,10 @@ function showSearchHit(hit) { } catch { more.disabled = false; more.textContent = 'More details'; - toast('Could not load details right now', true); + // Every mirror was busy or unreachable. This is a load problem at + // their end, not missing data — worth saying so, since retrying in a + // minute usually works. + toast('OpenStreetMap details are busy — try again shortly', true); } popup.update(); // re-measure after the content grew });