From 544a76d2dafb6cc5ad7a4132002a49b53d08cd33 Mon Sep 17 00:00:00 2001 From: Hardik-369 Date: Sat, 27 Jun 2026 10:04:07 +0530 Subject: [PATCH] fix(maps): increase Overpass timeout and add instance-specific User-Agent The POI search endpoint (/api/maps/pois) returned 502 errors because: 1. OVERPASS_TIMEOUT_MS (12s) was shorter than mirror response times (kumi.systems takes ~25.7s to first byte). Increased to 25s to match the [timeout:20] query timeout. 2. The static User-Agent string was indistinguishable between instances, making rate-limiting and throttling more likely. The new userAgent() function appends the instance's APP_URL so each deployment identifies itself uniquely, following Overpass API best practices. --- server/src/services/mapsService.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/server/src/services/mapsService.ts b/server/src/services/mapsService.ts index 070d1c4a..2160f519 100644 --- a/server/src/services/mapsService.ts +++ b/server/src/services/mapsService.ts @@ -68,7 +68,11 @@ interface GooglePlaceDetails extends GooglePlaceResult { // ── Constants ──────────────────────────────────────────────────────────────── -const UA = 'TREK Travel Planner (https://github.com/mauriceboe/TREK)'; +function userAgent(): string { + const base = 'TREK Travel Planner (https://github.com/mauriceboe/TREK)'; + try { const url = getAppUrl(); if (url) return `${base}; ${url}`; } catch { /* ignore */ } + return base; +} // TREK's internal language codes mostly coincide with valid BCP-47 codes, but a // couple don't: 'br' is Brazilian Portuguese here (BCP-47 'pt-BR'; bare 'br' is @@ -153,7 +157,7 @@ export async function searchNominatim(query: string, lang?: string) { 'accept-language': toApiLang(lang), }); const response = await fetch(`https://nominatim.openstreetmap.org/search?${params}`, { - headers: { 'User-Agent': UA }, + headers: { 'User-Agent': userAgent() }, }); if (!response.ok) { const text = await response.text().catch(() => ''); @@ -188,7 +192,7 @@ export async function lookupNominatim(osmType: string, osmId: string, lang?: str }); try { const res = await fetch(`https://nominatim.openstreetmap.org/lookup?${params}`, { - headers: { 'User-Agent': UA }, + headers: { 'User-Agent': userAgent() }, }); if (!res.ok) return null; const data = await res.json() as NominatimResult[]; @@ -213,7 +217,7 @@ export async function fetchOverpassDetails(osmType: string, osmId: string): Prom try { const res = await fetch('https://overpass-api.de/api/interpreter', { method: 'POST', - headers: { 'User-Agent': UA, 'Content-Type': 'application/x-www-form-urlencoded' }, + headers: { 'User-Agent': userAgent(), 'Content-Type': 'application/x-www-form-urlencoded' }, body: `data=${encodeURIComponent(query)}`, }); if (!res.ok) return null; @@ -290,7 +294,7 @@ const OVERPASS_MIRRORS = [ ]; // Per-mirror cap. Because mirrors race in parallel this is also the worst-case // total wait before every mirror is given up on and a 502 is returned. -const OVERPASS_TIMEOUT_MS = 12000; +const OVERPASS_TIMEOUT_MS = 25000; // Largest viewport side we send to Overpass. A country/continent-sized bbox makes // Overpass scan millions of elements and time out; clamping to a centred window // keeps the query cheap so the explore pill returns fast at ANY zoom level. @@ -319,7 +323,7 @@ async function overpassFetch(query: string): Promise { try { const res = await fetch(url, { method: 'POST', - headers: { 'User-Agent': UA, 'Content-Type': 'application/x-www-form-urlencoded' }, + headers: { 'User-Agent': userAgent(), 'Content-Type': 'application/x-www-form-urlencoded' }, body, signal: ctrl.signal, }); @@ -515,7 +519,7 @@ export async function fetchWikimediaPhoto(lat: number, lng: number, name?: strin pilimit: '1', redirects: '1', }); - const res = await fetch(`https://en.wikipedia.org/w/api.php?${searchParams}`, { headers: { 'User-Agent': UA } }); + const res = await fetch(`https://en.wikipedia.org/w/api.php?${searchParams}`, { headers: { 'User-Agent': userAgent() } }); if (res.ok) { const data = await res.json() as { query?: { pages?: Record } }; const pages = data.query?.pages; @@ -544,7 +548,7 @@ export async function fetchWikimediaPhoto(lat: number, lng: number, name?: strin iiurlwidth: '400', }); try { - const res = await fetch(`https://commons.wikimedia.org/w/api.php?${params}`, { headers: { 'User-Agent': UA } }); + const res = await fetch(`https://commons.wikimedia.org/w/api.php?${params}`, { headers: { 'User-Agent': userAgent() } }); if (!res.ok) return null; const data = await res.json() as { query?: { pages?: Record } }; const pages = data.query?.pages; @@ -993,7 +997,7 @@ export async function reverseGeocode(lat: string, lng: string, lang?: string): P 'accept-language': toApiLang(lang), }); const response = await fetch(`https://nominatim.openstreetmap.org/reverse?${params}`, { - headers: { 'User-Agent': UA }, + headers: { 'User-Agent': userAgent() }, }); if (!response.ok) return { name: null, address: null }; const data = await response.json() as { name?: string; display_name?: string; address?: Record };